Skip to content

Commit 250acdf

Browse files
committed
wip for part 2
1 parent 8bfb2a1 commit 250acdf

File tree

3 files changed

+478
-368
lines changed

3 files changed

+478
-368
lines changed

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

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

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;
166
import java.util.stream.Stream;
177

18-
import static com.codefork.aoc2024.util.FoldLeft.foldLeft;
19-
208
public class Part01 extends Problem {
219

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-
32410
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));
37712
}
37813

37914
@Override
38015
public String solve() {
38116
Assert.assertEquals("126384", solve(getSampleInput()));
382-
//return solve(getInput());
383-
return "";
17+
return solve(getInput());
38418
}
38519

38620
public static void main(String[] args) {
38721
new Part01().run();
38822
}
23+
38924
}

0 commit comments

Comments
 (0)