|
3 | 3 | import com.codefork.aoc2024.Problem;
|
4 | 4 | import com.codefork.aoc2024.util.Assert;
|
5 | 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.Set; |
14 | | -import java.util.stream.Collectors; |
15 | | -import java.util.stream.IntStream; |
16 | 6 | import java.util.stream.Stream;
|
17 | 7 |
|
18 | | -import static com.codefork.aoc2024.util.FoldLeft.foldLeft; |
19 | | - |
20 | 8 | public class Part01 extends Problem {
|
21 | 9 |
|
22 | | - public record Position(int x, int y) { |
23 | | - |
24 | | - } |
25 | | - |
26 | | - public record Button(String symbol, Position pos) { |
27 | | - |
28 | | - public boolean isAdjacent(Button other) { |
29 | | - var xDiff = Math.abs(pos().x() - other.pos().x()); |
30 | | - var yDiff = Math.abs(pos().y() - other.pos().y()); |
31 | | - return xDiff + yDiff == 1; |
32 | | - } |
33 | | - |
34 | | - /** |
35 | | - * returns the action symbol when navigating from this button to (adjacent) target |
36 | | - */ |
37 | | - public String getAction(Button target) { |
38 | | - var xDiff = pos().x() - target.pos().x(); |
39 | | - var yDiff = pos().y() - target.pos().y(); |
40 | | - if(xDiff > 0) { |
41 | | - return "<"; |
42 | | - } else if(xDiff < 0) { |
43 | | - return ">"; |
44 | | - } |
45 | | - if(yDiff > 0) { |
46 | | - return "^"; |
47 | | - } else if (yDiff < 0) { |
48 | | - return "v"; |
49 | | - } |
50 | | - throw new RuntimeException("buttons aren't adjacent in getAction=" + this + "," + target); |
51 | | - } |
52 | | - |
53 | | - public String toString() { |
54 | | - return symbol + "(" + pos.x() + "," + pos.y() + ")"; |
55 | | - } |
56 | | - } |
57 | | - |
58 | | - public record Move(Button from, Button to) { |
59 | | - |
60 | | - } |
61 | | - |
62 | | - public record ShortestPaths(Button source, Map<Button, Integer> dist, Map<Button, Set<Button>> prev) { |
63 | | - |
64 | | - // Dijkstra, yet again. |
65 | | - // this is probably overkill since there's not weighted edges in the graph |
66 | | - // but I've already implemented it twice so far for AoC, so it's the easiest way |
67 | | - // for me to create shortest paths at this point |
68 | | - private static ShortestPaths create(List<Button> buttons, Map<Button, List<Button>> graph, Button source) { |
69 | | - record ButtonWithDist(Button button, int dist) { |
70 | | - |
71 | | - } |
72 | | - var dist = new HashMap<Button, Integer>(); |
73 | | - var prev = new HashMap<Button, Set<Button>>(); |
74 | | - var q = new HashSet<Button>(); |
75 | | - |
76 | | - for(var button : buttons) { |
77 | | - dist.put(button, Integer.MAX_VALUE); |
78 | | - q.add(button); |
79 | | - } |
80 | | - dist.put(source, 0); |
81 | | - |
82 | | - while(!q.isEmpty()) { |
83 | | - var u = q.stream() |
84 | | - .map(button -> new ButtonWithDist(button, dist.get(button))) |
85 | | - .min(Comparator.comparingInt(ButtonWithDist::dist)) |
86 | | - .orElseThrow() |
87 | | - .button(); |
88 | | - q.remove(u); |
89 | | - |
90 | | - var neighborsInQ = graph.get(u).stream() |
91 | | - .filter(q::contains) |
92 | | - .toList(); |
93 | | - |
94 | | - for(var v : neighborsInQ) { |
95 | | - var alt = dist.get(u) + 1; |
96 | | - if (alt <= dist.get(v)) { |
97 | | - dist.put(v, alt); |
98 | | - if(!prev.containsKey(v)) { |
99 | | - prev.put(v, new HashSet<>()); |
100 | | - } |
101 | | - prev.get(v).add(u); |
102 | | - } |
103 | | - } |
104 | | - } |
105 | | - return new ShortestPaths(source, dist, prev); |
106 | | - } |
107 | | - |
108 | | - /** |
109 | | - * get all the possible paths for navigating from source to target |
110 | | - */ |
111 | | - public List<List<Button>> getPaths(Button target) { |
112 | | - var paths = new ArrayList<List<Button>>(); |
113 | | - paths.add(new LinkedList<>(List.of(target))); |
114 | | - var finalPaths = new ArrayList<List<Button>>(); |
115 | | - while(!paths.isEmpty()) { |
116 | | - var newPaths = new ArrayList<List<Button>>(); |
117 | | - for(var path : paths) { |
118 | | - var u = path.getFirst(); |
119 | | - if(prev.containsKey(u)){ |
120 | | - var allPrev = prev.get(u); |
121 | | - for(var prevItem : allPrev) { |
122 | | - var newPath = new LinkedList<>(path); |
123 | | - newPath.addFirst(prevItem); |
124 | | - newPaths.add(newPath); |
125 | | - } |
126 | | - |
127 | | - } else if (u.equals(source)) { |
128 | | - finalPaths.add(path); |
129 | | - } |
130 | | - } |
131 | | - paths = newPaths; |
132 | | - } |
133 | | - //System.out.println(source + "->" + target + ", returning " + finalPaths.size() + " finalPaths=" + finalPaths); |
134 | | - return finalPaths; |
135 | | - } |
136 | | - |
137 | | - public List<String> getPossiblePressSequences(Button target) { |
138 | | - var paths = getPaths(target); |
139 | | - return paths.stream() |
140 | | - .map(path -> { |
141 | | - return IntStream.range(1, path.size()).boxed() |
142 | | - .collect(foldLeft( |
143 | | - StringBuilder::new, |
144 | | - (acc, i) -> { |
145 | | - var button = path.get(i); |
146 | | - var prevButton = path.get(i - 1); |
147 | | - var action = prevButton.getAction(button); |
148 | | - acc.append(action); |
149 | | - return acc; |
150 | | - }) |
151 | | - ) |
152 | | - .append("A") |
153 | | - .toString(); |
154 | | - }) |
155 | | - .toList(); |
156 | | - } |
157 | | - } |
158 | | - |
159 | | - public static class Keypad { |
160 | | - private final List<Button> buttons; |
161 | | - |
162 | | - private final Map<String, Button> buttonsMap; |
163 | | - |
164 | | - private final Map<Move, List<String>> movesToPaths; |
165 | | - |
166 | | - public Keypad(List<Button> buttons) { |
167 | | - this.buttons = buttons; |
168 | | - this.buttonsMap = buttons.stream() |
169 | | - .collect(Collectors.toMap( |
170 | | - Button::symbol, |
171 | | - b -> b)); |
172 | | - |
173 | | - this.movesToPaths = createShortestPaths(); |
174 | | - } |
175 | | - |
176 | | - private Map<Move, List<String>> createShortestPaths() { |
177 | | - Map<Move, List<String>> result = new HashMap<>(); |
178 | | - |
179 | | - // generate graph of adjacent buttons |
180 | | - var graph = new HashMap<Button, List<Button>>(); |
181 | | - for(var i=0; i < buttons.size(); i++) { |
182 | | - var button1 = buttons.get(i); |
183 | | - for(var j=i; j < buttons.size(); j++) { |
184 | | - var button2 = buttons.get(j); |
185 | | - if(button1.isAdjacent(button2)) { |
186 | | - if(!graph.containsKey(button1)) { |
187 | | - graph.put(button1, new ArrayList<>()); |
188 | | - } |
189 | | - graph.get(button1).add(button2); |
190 | | - if(!graph.containsKey(button2)) { |
191 | | - graph.put(button2, new ArrayList<>()); |
192 | | - } |
193 | | - graph.get(button2).add(button1); |
194 | | - } |
195 | | - } |
196 | | - } |
197 | | - |
198 | | - // generate shortest paths for every button to every other button |
199 | | - for(var i=0; i < buttons.size(); i++) { |
200 | | - var button1 = buttons.get(i); |
201 | | - var shortestPaths = ShortestPaths.create(buttons, graph, button1); |
202 | | - var otherButtons = buttons.stream().filter(b -> !b.equals(button1)).toList(); |
203 | | - for (var button2 : otherButtons) { |
204 | | - var presses = shortestPaths.getPossiblePressSequences(button2); |
205 | | - //System.out.println("move " + button1 + "->" + button2 + " adding presses=" + presses); |
206 | | - result.put(new Move(button1, button2), presses); |
207 | | - } |
208 | | - } |
209 | | - return result; |
210 | | - } |
211 | | - |
212 | | - public Map<String, Button> getButtonsMap() { |
213 | | - return buttonsMap; |
214 | | - } |
215 | | - |
216 | | - public Map<Move, List<String>> getMovesToPaths() { |
217 | | - return movesToPaths; |
218 | | - } |
219 | | - } |
220 | | - |
221 | | - public static class KeypadNavigator { |
222 | | - |
223 | | - private final Keypad keypad; |
224 | | - private final Button current; |
225 | | - |
226 | | - public KeypadNavigator(Keypad keypad, String current) { |
227 | | - this.keypad = keypad; |
228 | | - this.current = this.keypad.getButtonsMap().get(current); |
229 | | - } |
230 | | - |
231 | | - public List<String> getPossiblePressSequences(String seq) { |
232 | | - List<StringBuilder> pressSeqList = new ArrayList<>(); |
233 | | - pressSeqList.add(new StringBuilder()); |
234 | | - var c = current; |
235 | | - while(!seq.isEmpty()) { |
236 | | - var newPressSeqList = new ArrayList<StringBuilder>(); |
237 | | - var symbol = seq.substring(0, 1); |
238 | | - var next = keypad.getButtonsMap().get(symbol); |
239 | | - if(next.equals(c)) { |
240 | | - for(var pressSeq : pressSeqList) { |
241 | | - pressSeq.append("A"); |
242 | | - newPressSeqList.add(pressSeq); |
243 | | - } |
244 | | - } else { |
245 | | - var move = new Move(c, next); |
246 | | - var paths = keypad.getMovesToPaths().get(move); |
247 | | - for(var pressSeq : pressSeqList) { |
248 | | - for(var path : paths) { |
249 | | - var copy = new StringBuilder(pressSeq.toString()); |
250 | | - copy.append(path); |
251 | | - //System.out.println("move " + move + ", appended " + path + ", copy is: " + copy); |
252 | | - newPressSeqList.add(copy); |
253 | | - } |
254 | | - } |
255 | | - } |
256 | | - pressSeqList = newPressSeqList; |
257 | | - c = next; |
258 | | - seq = seq.substring(1); |
259 | | - } |
260 | | - return pressSeqList.stream() |
261 | | - .map(StringBuilder::toString) |
262 | | - .toList(); |
263 | | - } |
264 | | - |
265 | | - } |
266 | | - |
267 | | - public static class DoorKeypadNavigator extends KeypadNavigator { |
268 | | - |
269 | | - private static final List<Button> doorButtons = List.of( |
270 | | - new Button("7", new Position(0, 0)), |
271 | | - new Button("8", new Position(1, 0)), |
272 | | - new Button("9", new Position(2, 0)), |
273 | | - new Button("4", new Position(0, 1)), |
274 | | - new Button("5", new Position(1, 1)), |
275 | | - new Button("6", new Position(2, 1)), |
276 | | - new Button("1", new Position(0, 2)), |
277 | | - new Button("2", new Position(1, 2)), |
278 | | - new Button("3", new Position(2, 2)), |
279 | | - new Button("0", new Position(1, 3)), |
280 | | - new Button("A", new Position(2, 3)) |
281 | | - ); |
282 | | - |
283 | | - private static final Keypad doorKeypad = new Keypad(doorButtons); |
284 | | - |
285 | | - public DoorKeypadNavigator(String startSymbol) { |
286 | | - super(doorKeypad, startSymbol); |
287 | | - } |
288 | | - |
289 | | - } |
290 | | - |
291 | | - public static class DirectionalKeypadNavigator extends KeypadNavigator { |
292 | | - |
293 | | - private static final List<Button> robotButtons = List.of( |
294 | | - new Button("^", new Position(1, 0)), |
295 | | - new Button("A", new Position(2, 0)), |
296 | | - new Button("<", new Position(0, 1)), |
297 | | - new Button("v", new Position(1, 1)), |
298 | | - new Button(">", new Position(2, 1)) |
299 | | - ); |
300 | | - |
301 | | - private static final Keypad robotKeypad = new Keypad(robotButtons); |
302 | | - |
303 | | - public DirectionalKeypadNavigator(String startSymbol) { |
304 | | - super(robotKeypad, startSymbol); |
305 | | - } |
306 | | - |
307 | | - } |
308 | | - |
309 | | - public int getNumericPortion(String str) { |
310 | | - return Integer.parseInt(str.chars() |
311 | | - .boxed() |
312 | | - .map(ch -> String.valueOf((char) ch.intValue())) |
313 | | - .filter(s -> { |
314 | | - try { |
315 | | - Integer.parseInt(s); |
316 | | - } catch(NumberFormatException e) { |
317 | | - return false; |
318 | | - } |
319 | | - return true; |
320 | | - }) |
321 | | - .collect(Collectors.joining())); |
322 | | - } |
323 | | - |
324 | 10 | public String solve(Stream<String> data) {
|
325 | | - var navigators = List.of( |
326 | | - new DoorKeypadNavigator("A"), |
327 | | - new DirectionalKeypadNavigator("A"), |
328 | | - new DirectionalKeypadNavigator("A") |
329 | | - ); |
330 | | - |
331 | | - var complexities = data |
332 | | - .map(seq -> { |
333 | | - System.out.println("doing " + seq); |
334 | | - // run each sequence through a list of navigators, collecting results |
335 | | - var pressesList = List.of(seq); |
336 | | - for (var navigator : navigators) { |
337 | | - var newPressesList = new ArrayList<String>(); |
338 | | - for (var presses : pressesList) { |
339 | | - var possiblePresses = navigator.getPossiblePressSequences(presses); |
340 | | - for (var p : possiblePresses) { |
341 | | - System.out.println(p); |
342 | | - } |
343 | | - newPressesList.addAll(possiblePresses); |
344 | | - } |
345 | | - |
346 | | -// var lengthCounts = newPressesList.stream() |
347 | | -// .collect(Collectors.toMap( |
348 | | -// String::length, |
349 | | -// s -> 1, |
350 | | -// Integer::sum |
351 | | -// )); |
352 | | -// for(var len : lengthCounts.keySet().stream().sorted(Integer::compareTo).toList()) { |
353 | | -// System.out.println("press sequences with len = " + len + |
354 | | -// " occurred " + lengthCounts.get(len) + " times"); |
355 | | -// } |
356 | | - |
357 | | - // group press sequences by their "signature" (where/which elements repeat) |
358 | | - |
359 | | - |
360 | | - System.out.println(navigator + " found " + newPressesList.size() + " possible presses"); |
361 | | - |
362 | | - pressesList = newPressesList; |
363 | | - } |
364 | | - System.out.println("found " + pressesList.size() + " possible presses for last navigator"); |
365 | | - // find the shortest path of the LAST navigator only |
366 | | - var shortestPress = pressesList.stream() |
367 | | - .min(Comparator.comparingInt(String::length)) |
368 | | - .orElseThrow(); |
369 | | - System.out.println("shortest press found is "+ shortestPress.length() + " long"); |
370 | | - var result = shortestPress.length() * getNumericPortion(seq); |
371 | | - return result; |
372 | | - }) |
373 | | - .toList(); |
374 | | - |
375 | | - var total = complexities.stream().mapToInt(i -> i).sum(); |
376 | | - return String.valueOf(total); |
| 11 | + return String.valueOf(ShipLock.calculateSumOfComplexities(data, 2)); |
377 | 12 | }
|
378 | 13 |
|
379 | 14 | @Override
|
380 | 15 | public String solve() {
|
381 | 16 | Assert.assertEquals("126384", solve(getSampleInput()));
|
382 | | - //return solve(getInput()); |
383 | | - return ""; |
| 17 | + return solve(getInput()); |
384 | 18 | }
|
385 | 19 |
|
386 | 20 | public static void main(String[] args) {
|
387 | 21 | new Part01().run();
|
388 | 22 | }
|
| 23 | + |
389 | 24 | }
|
0 commit comments