Skip to content

Commit b49405a

Browse files
committed
day 21 part 2
1 parent a9e11f5 commit b49405a

File tree

13 files changed

+571
-411
lines changed

13 files changed

+571
-411
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,3 +144,20 @@ head against a wall. Which I did for WEEKS for some problems during AoC 2018. Wh
144144
(still later that day)
145145

146146
Hey I figured out day 14!
147+
148+
### 1/3/2025
149+
150+
I returned to day 21 and finished part 2. My initial naive attempt tried to generate the full strings of
151+
keypad presses, which, unsurprisingly, caused out of memory errors. Then I had the idea to represent the
152+
press sequences in a way that used less memory. After adding a shortcut after each robot's loop
153+
iteration, it finishes in about 2 minutes on my laptop.
154+
155+
I suspect this is one of those puzzles where there's a pattern in the growth of the sequences, and you can
156+
calculate the minimum size at n iterations without actually having to loop through them. I'm not that clever,
157+
sadly.
158+
159+
I'm still proud of my solution. It was tricky to model properly, and there were lots of hairy one-to-many
160+
transformations. I think it's pretty readable and easy to understand what's happening.
161+
162+
I'm running out of steam, so when I get around to it, I'm going to cheat for days 17 and 24 and see how someone
163+
else solved them. Implementation is still work and learning!
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
package com.codefork.aoc2024.day21;
2+
3+
public record Button(String symbol, Position pos) {
4+
5+
public boolean isAdjacent(Button other) {
6+
var xDiff = Math.abs(pos().x() - other.pos().x());
7+
var yDiff = Math.abs(pos().y() - other.pos().y());
8+
return xDiff + yDiff == 1;
9+
}
10+
11+
/**
12+
* returns the action symbol when navigating from this button to (adjacent) target
13+
*/
14+
public String getAction(Button target) {
15+
var xDiff = pos().x() - target.pos().x();
16+
var yDiff = pos().y() - target.pos().y();
17+
if (xDiff > 0) {
18+
return "<";
19+
} else if (xDiff < 0) {
20+
return ">";
21+
}
22+
if (yDiff > 0) {
23+
return "^";
24+
} else if (yDiff < 0) {
25+
return "v";
26+
}
27+
throw new RuntimeException("buttons aren't adjacent in getAction=" + this + "," + target);
28+
}
29+
30+
public String toString() {
31+
return symbol + "(" + pos.x() + "," + pos.y() + ")";
32+
}
33+
34+
@SuppressWarnings("unused")
35+
public char asChar() {
36+
return symbol.charAt(0);
37+
}
38+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.codefork.aoc2024.day21;
2+
3+
import static com.codefork.aoc2024.day21.Keypad.dirKeypad;
4+
5+
public class DirectionalKeypadNavigator extends KeypadNavigator {
6+
7+
public DirectionalKeypadNavigator() {
8+
super(dirKeypad);
9+
}
10+
11+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.codefork.aoc2024.day21;
2+
3+
import java.util.List;
4+
5+
public class DoorKeypadNavigator extends KeypadNavigator {
6+
7+
private static final List<Button> doorButtons = List.of(
8+
new Button("7", new Position(0, 0)),
9+
new Button("8", new Position(1, 0)),
10+
new Button("9", new Position(2, 0)),
11+
new Button("4", new Position(0, 1)),
12+
new Button("5", new Position(1, 1)),
13+
new Button("6", new Position(2, 1)),
14+
new Button("1", new Position(0, 2)),
15+
new Button("2", new Position(1, 2)),
16+
new Button("3", new Position(2, 2)),
17+
new Button("0", new Position(1, 3)),
18+
new Button("A", new Position(2, 3))
19+
);
20+
21+
private static final Keypad doorKeypad = new Keypad(doorButtons);
22+
23+
public DoorKeypadNavigator() {
24+
super(doorKeypad);
25+
}
26+
27+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
package com.codefork.aoc2024.day21;
2+
3+
import java.util.ArrayList;
4+
import java.util.HashMap;
5+
import java.util.List;
6+
import java.util.Map;
7+
import java.util.stream.Collectors;
8+
9+
public class Keypad {
10+
11+
// directional keypad buttons
12+
private static final List<Button> dirKeypadButtons = List.of(
13+
new Button("^", new Position(1, 0)),
14+
new Button("A", new Position(2, 0)),
15+
new Button("<", new Position(0, 1)),
16+
new Button("v", new Position(1, 1)),
17+
new Button(">", new Position(2, 1))
18+
);
19+
20+
// directional keypad: this is used by both navigator
21+
public static final Keypad dirKeypad = new Keypad(dirKeypadButtons);
22+
23+
private final List<Button> buttons;
24+
25+
private final Map<String, Button> buttonsMap;
26+
27+
// map of moves to every possible path for making that move
28+
private final Map<Move, List<String>> movesToPaths;
29+
30+
public Keypad(List<Button> buttons) {
31+
this.buttons = buttons;
32+
this.buttonsMap = buttons.stream()
33+
.collect(Collectors.toMap(
34+
Button::symbol,
35+
b -> b));
36+
37+
this.movesToPaths = createShortestPaths();
38+
}
39+
40+
private Map<Move, List<String>> createShortestPaths() {
41+
Map<Move, List<String>> result = new HashMap<>();
42+
43+
// generate graph of adjacent buttons
44+
var graph = new HashMap<Button, List<Button>>();
45+
for (var i = 0; i < buttons.size(); i++) {
46+
var button1 = buttons.get(i);
47+
for (var j = i; j < buttons.size(); j++) {
48+
var button2 = buttons.get(j);
49+
if (button1.isAdjacent(button2)) {
50+
if (!graph.containsKey(button1)) {
51+
graph.put(button1, new ArrayList<>());
52+
}
53+
graph.get(button1).add(button2);
54+
if (!graph.containsKey(button2)) {
55+
graph.put(button2, new ArrayList<>());
56+
}
57+
graph.get(button2).add(button1);
58+
}
59+
}
60+
}
61+
62+
// generate shortest paths for every button to every other button
63+
for (var i = 0; i < buttons.size(); i++) {
64+
var button1 = buttons.get(i);
65+
var shortestPaths = ShortestPaths.create(buttons, graph, button1);
66+
var otherButtons = buttons.stream().filter(b -> !b.equals(button1)).toList();
67+
for (var button2 : otherButtons) {
68+
var presses = shortestPaths.getPossiblePressSequences(button2);
69+
//System.out.println("move " + button1 + "->" + button2 + " adding presses=" + presses);
70+
result.put(new Move(button1, button2), presses);
71+
}
72+
}
73+
74+
// path from a button to itself it just "A"
75+
for (Button button : buttons) {
76+
result.put(new Move(button, button), List.of("A"));
77+
}
78+
79+
return result;
80+
}
81+
82+
public Map<String, Button> getButtonsMap() {
83+
return buttonsMap;
84+
}
85+
86+
public Button getButton(String symbol) {
87+
return buttonsMap.get(symbol);
88+
}
89+
90+
public Button getButton(char symbol) {
91+
return buttonsMap.get(String.valueOf(symbol));
92+
}
93+
94+
public Map<Move, List<String>> getMovesToPaths() {
95+
return movesToPaths;
96+
}
97+
}
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
package com.codefork.aoc2024.day21;
2+
3+
import java.util.ArrayList;
4+
import java.util.HashSet;
5+
import java.util.List;
6+
import java.util.Set;
7+
8+
/**
9+
* Base class representing a robot/person who interacts with a keypad.
10+
* Subclasses specify the particular kind of keypad.
11+
*/
12+
public class KeypadNavigator {
13+
14+
private final Keypad keypad;
15+
16+
public KeypadNavigator(Keypad keypad) {
17+
this.keypad = keypad;
18+
}
19+
20+
public Keypad getKeypad() {
21+
return keypad;
22+
}
23+
24+
/**
25+
* Transforms a PressSequence into its next possible iteration(s)
26+
*/
27+
public Set<PressSequence> getPossiblePressSequences(PressSequence seq) {
28+
List<PressSequence> results = new ArrayList<>();
29+
results.add(PressSequence.create());
30+
31+
//System.out.println("getting possible sequences for seq=" + seq);
32+
33+
// examine each move and append the possibilities to results
34+
for (var entry : seq.moves().entrySet()) {
35+
var newResults = new ArrayList<PressSequence>();
36+
var move = entry.getKey();
37+
var moveCount = entry.getValue();
38+
39+
//System.out.println("handling " + move + " (count=" + moveCount + ")");
40+
41+
for (var result : results) {
42+
var possibilities = addMove(result, move, moveCount);
43+
newResults.addAll(possibilities);
44+
}
45+
results = newResults;
46+
}
47+
48+
return new HashSet<>(results);
49+
}
50+
51+
/**
52+
* returns list of new PressSequences that are the possibilities that can result from adding the passed-in move
53+
*
54+
* @param keypad used to get the Button objects for the symbols, and to get the possible paths between buttons.
55+
*/
56+
public List<PressSequence> addMove(PressSequence seq, Move move, long multiplier) {
57+
var results = new ArrayList<PressSequence>();
58+
var paths = getKeypad().getMovesToPaths().get(move);
59+
60+
//System.out.println("from=" + _from + " to=" + _to + " paths=" + paths);
61+
62+
for (var path : paths) {
63+
var copy = seq.copy();
64+
65+
char first = path.charAt(0);
66+
67+
// note that we use dirKeypad here, since movements match up with the directional keypad
68+
69+
// add a transitional move from the last implicit "A"
70+
if (path.length() > 1 && first != 'A') {
71+
var transition = new Move(Keypad.dirKeypad.getButton("A"), Keypad.dirKeypad.getButton(first));
72+
copy.moves().merge(transition, multiplier, Long::sum);
73+
}
74+
75+
var from = first;
76+
for (var i = 1; i < path.length(); i++) {
77+
var to = path.charAt(i);
78+
79+
var moveInPath = new Move(Keypad.dirKeypad.getButton(from), Keypad.dirKeypad.getButton(to));
80+
copy.moves().merge(moveInPath, multiplier, Long::sum);
81+
82+
from = to;
83+
}
84+
85+
// this happens when a button repeats (i.e. moves from a button to itself)
86+
if (path.length() == 1) {
87+
var aButton = Keypad.dirKeypad.getButton("A");
88+
var moveInPath = new Move(aButton, aButton);
89+
copy.moves().merge(moveInPath, multiplier, Long::sum);
90+
}
91+
results.add(copy);
92+
}
93+
//System.out.println("addMove returning=" + results);
94+
95+
return results;
96+
}
97+
98+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.codefork.aoc2024.day21;
2+
3+
public record Move(Button from, Button to) {
4+
5+
}

src/main/java/com/codefork/aoc2024/day21/Part01.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,13 @@
33
import com.codefork.aoc2024.Problem;
44
import com.codefork.aoc2024.util.Assert;
55

6+
import java.util.ArrayList;
7+
import java.util.HashSet;
8+
import java.util.Set;
69
import java.util.stream.Stream;
710

11+
import static com.codefork.aoc2024.day21.ShipLock.getNumericPortion;
12+
813
public class Part01 extends Problem {
914

1015
public String solve(Stream<String> data) {

src/main/java/com/codefork/aoc2024/day21/Part02.java

Lines changed: 2 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,14 @@
11
package com.codefork.aoc2024.day21;
22

33
import com.codefork.aoc2024.Problem;
4-
import com.codefork.aoc2024.util.Assert;
54

6-
import java.util.ArrayList;
7-
import java.util.List;
85
import java.util.stream.Stream;
96

107
public class Part02 extends Problem {
118

129
public String solve(Stream<String> data) {
13-
// return String.valueOf(ShipLock.calculateSumOfComplexities(data, 25));
14-
15-
var navigator = new ShipLock.DirectionalKeypadNavigator("A");
16-
17-
// see what happens to a single move through 25 generations.
18-
// it doesn't even get past 4.
19-
// var pressSeqList = List.of("^");
20-
// for(var i = 0; i < 25; i++) {
21-
// System.out.println("i=" + i);
22-
// var newPressSeqList = new ArrayList<String>();
23-
// for (var pressSeq : pressSeqList) {
24-
// var possible = navigator.getPossiblePressSequences(pressSeq);
25-
// newPressSeqList.addAll(possible);
26-
// }
27-
// System.out.println(newPressSeqList);
28-
// pressSeqList = newPressSeqList;
29-
// }
30-
31-
return "UNFINISHED";
10+
System.out.println("TODO: This takes ~2 minutes to run, needs to be optimized");
11+
return String.valueOf(ShipLock.calculateSumOfComplexities(data, 25));
3212
}
3313

3414
@Override
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.codefork.aoc2024.day21;
2+
3+
public record Position(int x, int y) {
4+
5+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy