Skip to content

Commit 24330f3

Browse files
committed
day 21
1 parent 74ca528 commit 24330f3

File tree

2 files changed

+314
-0
lines changed

2 files changed

+314
-0
lines changed
Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
package com.codefork.aoc2024.day21;
2+
3+
import com.codefork.aoc2024.Problem;
4+
import com.codefork.aoc2024.util.Assert;
5+
6+
import java.util.ArrayList;
7+
import java.util.Comparator;
8+
import java.util.HashMap;
9+
import java.util.HashSet;
10+
import java.util.LinkedList;
11+
import java.util.List;
12+
import java.util.Map;
13+
import java.util.stream.Collectors;
14+
import java.util.stream.IntStream;
15+
import java.util.stream.Stream;
16+
17+
import static com.codefork.aoc2024.util.FoldLeft.foldLeft;
18+
19+
public class Part01 extends Problem {
20+
21+
public record Position(int x, int y) {
22+
23+
}
24+
25+
public record Button(String symbol, Position pos) {
26+
27+
public boolean isAdjacent(Button other) {
28+
var xDiff = Math.abs(pos().x() - other.pos().x());
29+
var yDiff = Math.abs(pos().y() - other.pos().y());
30+
return xDiff + yDiff == 1;
31+
}
32+
33+
/**
34+
* returns the action symbol when navigating from this button to (adjacent) target
35+
*/
36+
public String getAction(Button target) {
37+
var xDiff = pos().x() - target.pos().x();
38+
var yDiff = pos().y() - target.pos().y();
39+
if(xDiff > 0) {
40+
return "<";
41+
} else if(xDiff < 0) {
42+
return ">";
43+
}
44+
if(yDiff > 0) {
45+
return "^";
46+
} else if (yDiff < 0) {
47+
return "v";
48+
}
49+
throw new RuntimeException("buttons aren't adjacent in getAction=" + this + "," + target);
50+
}
51+
}
52+
53+
public record Move(Button from, Button to) {
54+
55+
}
56+
57+
public record ShortestPaths(Button source, Map<Button, Integer> dist, Map<Button, Button> prev) {
58+
59+
// Dijkstra, yet again.
60+
// this is probably overkill since there's not weighted edges in the graph
61+
// but I've already implemented it twice so far for AoC, so it's the easiest way
62+
// for me to create shortest paths at this point
63+
private static ShortestPaths create(List<Button> buttons, Map<Button, List<Button>> graph, Button source) {
64+
record ButtonWithDist(Button button, int dist) {
65+
66+
}
67+
var dist = new HashMap<Button, Integer>();
68+
var prev = new HashMap<Button, Button>();
69+
var q = new HashSet<Button>();
70+
71+
for(var button : buttons) {
72+
dist.put(button, Integer.MAX_VALUE);
73+
q.add(button);
74+
}
75+
dist.put(source, 0);
76+
77+
while(!q.isEmpty()) {
78+
var u = q.stream()
79+
.map(button -> new ButtonWithDist(button, dist.get(button)))
80+
.min(Comparator.comparingInt(ButtonWithDist::dist))
81+
.orElseThrow()
82+
.button();
83+
q.remove(u);
84+
85+
var neighborsInQ = graph.get(u).stream()
86+
.filter(q::contains)
87+
.toList();
88+
89+
for(var v : neighborsInQ) {
90+
var alt = dist.get(u) + 1;
91+
if (alt < dist.get(v)) {
92+
dist.put(v, alt);
93+
prev.put(v, u);
94+
}
95+
}
96+
}
97+
return new ShortestPaths(source, dist, prev);
98+
}
99+
100+
public List<Button> getPath(Button target) {
101+
var s = new LinkedList<Button>();
102+
var u = target;
103+
if(prev.containsKey(u) || u.equals(source)) {
104+
while(u != null) {
105+
s.addFirst(u);
106+
u = prev.get(u);
107+
}
108+
}
109+
return s;
110+
}
111+
112+
public String getPresses(Button target) {
113+
var path = getPath(target);
114+
var result = IntStream.range(1, path.size()).boxed()
115+
.collect(foldLeft(
116+
StringBuilder::new,
117+
(acc, i) -> {
118+
var button = path.get(i);
119+
var prevButton = path.get(i-1);
120+
var action = prevButton.getAction(button);
121+
acc.append(action);
122+
return acc;
123+
})
124+
);
125+
return result.append("A").toString();
126+
}
127+
}
128+
129+
public static class Keypad {
130+
private final List<Button> buttons;
131+
132+
private final Map<String, Button> buttonsMap;
133+
134+
private final Map<Move, String> movesToPaths;
135+
136+
public Keypad(List<Button> buttons) {
137+
this.buttons = buttons;
138+
this.buttonsMap = buttons.stream()
139+
.collect(Collectors.toMap(
140+
Button::symbol,
141+
b -> b));
142+
143+
this.movesToPaths = createShortestPaths();
144+
}
145+
146+
private Map<Move, String> createShortestPaths() {
147+
Map<Move, String> result = new HashMap<>();
148+
149+
// generate graph of adjacent buttons
150+
var graph = new HashMap<Button, List<Button>>();
151+
for(var i=0; i < buttons.size(); i++) {
152+
var button1 = buttons.get(i);
153+
for(var j=i; j < buttons.size(); j++) {
154+
var button2 = buttons.get(j);
155+
if(button1.isAdjacent(button2)) {
156+
if(!graph.containsKey(button1)) {
157+
graph.put(button1, new ArrayList<>());
158+
}
159+
graph.get(button1).add(button2);
160+
if(!graph.containsKey(button2)) {
161+
graph.put(button2, new ArrayList<>());
162+
}
163+
graph.get(button2).add(button1);
164+
}
165+
}
166+
}
167+
168+
// generate shortest paths for every button to every other button
169+
for(var i=0; i < buttons.size(); i++) {
170+
var button1 = buttons.get(i);
171+
var shortestPaths = ShortestPaths.create(buttons, graph, button1);
172+
var otherButtons = buttons.stream().filter(b -> !b.equals(button1)).toList();
173+
for (var button2 : otherButtons) {
174+
var presses = shortestPaths.getPresses(button2);
175+
result.put(new Move(button1, button2), presses);
176+
}
177+
}
178+
return result;
179+
}
180+
181+
public Map<String, Button> getButtonsMap() {
182+
return buttonsMap;
183+
}
184+
185+
public Map<Move, String> getMovesToPaths() {
186+
return movesToPaths;
187+
}
188+
}
189+
190+
public static class KeypadNavigator {
191+
192+
private final Keypad keypad;
193+
private final Button current;
194+
195+
public KeypadNavigator(Keypad keypad, String current) {
196+
this.keypad = keypad;
197+
this.current = this.keypad.getButtonsMap().get(current);
198+
}
199+
200+
public String getPresses(String seq) {
201+
StringBuilder presses = new StringBuilder();
202+
var c = current;
203+
while(!seq.isEmpty()) {
204+
var symbol = seq.substring(0, 1);
205+
var next = keypad.getButtonsMap().get(symbol);
206+
if(next.equals(c)) {
207+
presses.append("A");
208+
} else {
209+
var move = new Move(c, next);
210+
presses.append(keypad.getMovesToPaths().get(move));
211+
}
212+
c = next;
213+
seq = seq.substring(1);
214+
}
215+
return presses.toString();
216+
}
217+
218+
}
219+
220+
public static class DoorKeypadNavigator extends KeypadNavigator {
221+
222+
private static final List<Button> doorButtons = List.of(
223+
new Button("7", new Position(0, 0)),
224+
new Button("8", new Position(1, 0)),
225+
new Button("9", new Position(2, 0)),
226+
new Button("4", new Position(0, 1)),
227+
new Button("5", new Position(1, 1)),
228+
new Button("6", new Position(2, 1)),
229+
new Button("1", new Position(0, 2)),
230+
new Button("2", new Position(1, 2)),
231+
new Button("3", new Position(2, 2)),
232+
new Button("0", new Position(1, 3)),
233+
new Button("A", new Position(2, 3))
234+
);
235+
236+
private static final Keypad doorKeypad = new Keypad(doorButtons);
237+
238+
public DoorKeypadNavigator(String startSymbol) {
239+
super(doorKeypad, startSymbol);
240+
}
241+
242+
}
243+
244+
public static class DirectionalKeypadNavigator extends KeypadNavigator {
245+
246+
private static final List<Button> robotButtons = List.of(
247+
new Button("^", new Position(1, 0)),
248+
new Button("A", new Position(2, 0)),
249+
new Button("<", new Position(0, 1)),
250+
new Button("v", new Position(1, 1)),
251+
new Button(">", new Position(2, 1))
252+
);
253+
254+
private static final Keypad robotKeypad = new Keypad(robotButtons);
255+
256+
public DirectionalKeypadNavigator(String startSymbol) {
257+
super(robotKeypad, startSymbol);
258+
}
259+
260+
}
261+
262+
public int getNumericPortion(String str) {
263+
return Integer.parseInt(str.chars()
264+
.boxed()
265+
.map(ch -> String.valueOf((char) ch.intValue()))
266+
.filter(s -> {
267+
try {
268+
Integer.parseInt(s);
269+
} catch(NumberFormatException e) {
270+
return false;
271+
}
272+
return true;
273+
})
274+
.collect(Collectors.joining()));
275+
}
276+
277+
public String solve(Stream<String> data) {
278+
var navigators = List.of(
279+
new DoorKeypadNavigator("A"),
280+
new DirectionalKeypadNavigator("A"),
281+
new DirectionalKeypadNavigator("A")
282+
);
283+
284+
record SeqWithFinalPresses(String seq, String finalPresses) {
285+
286+
}
287+
288+
var total = data
289+
.map(seq -> {
290+
var presses = seq;
291+
for (var navigator : navigators) {
292+
presses = navigator.getPresses(presses);
293+
var aCount = presses.chars().filter(ch -> ((char) ch) == 'A').count();
294+
System.out.println(presses + " num A's=" + aCount);
295+
}
296+
return new SeqWithFinalPresses(seq, presses);
297+
})
298+
.map(s -> s.finalPresses.length() * getNumericPortion(s.seq))
299+
.mapToInt(i -> i)
300+
.sum();
301+
302+
return String.valueOf(total);
303+
}
304+
305+
@Override
306+
public String solve() {
307+
Assert.assertEquals("126384", solve(getSampleInput()));
308+
return solve(getInput());
309+
}
310+
311+
public static void main(String[] args) {
312+
new Part01().run();
313+
}
314+
}

src/main/resources/day21/sample

47 Bytes
Binary file not shown.

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