Skip to content

Commit 366736c

Browse files
committed
day 16
1 parent 3207f46 commit 366736c

File tree

13 files changed

+735
-0
lines changed

13 files changed

+735
-0
lines changed

README.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,17 @@ and avoid clutter at the class level. Pretty cool!
8484
### 12/15/2024
8585

8686
Day 14 Part 2 had me stumped, at least for now. Moving on to Day 15.
87+
88+
### 12/18/2024
89+
90+
This is the part of AoC when I start to fall behind. =P
91+
92+
Day 16 took me several days to do, when I could find time to work on it. I did actually intuit
93+
what the best solution for part 1 probably was, but I was lazy and wasted time banging out a
94+
naive solution, hoping it would be good enough. It wasn't; it worked for the sample data, but it
95+
would run out of memory on the real input. So in the long run, it cost me more time than if I
96+
went with the right solution from the start.
97+
98+
The biggest stumbling block was adapting the well-known algorithm (I won't give it away here,
99+
it's in the code) to handle the direction of the reindeer in the maze, and accounting for that
100+
in the cost of the weighted edges of the graph.
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package com.codefork.aoc2024.day14;
2+
3+
import com.codefork.aoc2024.Problem;
4+
import com.codefork.aoc2024.util.Assert;
5+
6+
import java.util.Map;
7+
import java.util.stream.Collectors;
8+
import java.util.stream.Stream;
9+
import java.util.Scanner;
10+
11+
public class Part02 extends Problem {
12+
13+
public String solve(int width, int height, Stream<String> data) {
14+
var swarm = Swarm.create(data, width, height);
15+
var keepGoing = true;
16+
var i = 0;
17+
var scanner = new Scanner(System.in);
18+
while(i < 1000000) {
19+
var newSwarm = swarm.doMoves(1);
20+
21+
// an xmas tree is going to be bottom heavy, so only show the trees
22+
// where bottom sections have more robots than top ones
23+
24+
var quadrantCounts = newSwarm.getQuadrantCounts();
25+
26+
var bottomHeavy = quadrantCounts.get(2).floatValue() > quadrantCounts.get(0).floatValue() * 1.25 &&
27+
quadrantCounts.get(3).floatValue() > quadrantCounts.get(1).floatValue() * 1.25;
28+
29+
// var topHeavy = quadrantCounts.get(0).floatValue() > quadrantCounts.get(2).floatValue() * 1.4 &&
30+
// quadrantCounts.get(1).floatValue() > quadrantCounts.get(3).floatValue() * 1.4;
31+
32+
if (bottomHeavy) {
33+
System.out.println("--------------------------");
34+
System.out.println("i=" + i);
35+
newSwarm.print();
36+
}
37+
38+
// -------------------------------------------------------------
39+
40+
// var sectionCounts = newSwarm.getCustomSectionCounts();
41+
// if (sectionCounts.get(4) > sectionCounts.get(2) &&
42+
// sectionCounts.get(2) > sectionCounts.get(0) &&
43+
// sectionCounts.get(5) > sectionCounts.get(3) &&
44+
// sectionCounts.get(3) > sectionCounts.get(1)) {
45+
// System.out.println("--------------------------");
46+
// System.out.println("i=" + i);
47+
// newSwarm.print();
48+
// }
49+
50+
// --------------------------------------------------
51+
52+
// record Pos(int x, int y) {
53+
// }
54+
//
55+
// var counts = newSwarm.robots().stream()
56+
// .collect(Collectors.toMap(
57+
// robot -> new Pos(robot.x(), robot.y()),
58+
// robot -> 1,
59+
// Integer::sum));
60+
//
61+
// if(counts.values().stream().anyMatch(c -> c > 5)) {
62+
// System.out.println("--------------------------");
63+
// System.out.println("i=" + i);
64+
// newSwarm.print();
65+
// }
66+
67+
//scanner.nextLine();
68+
swarm = newSwarm;
69+
i++;
70+
}
71+
72+
return String.valueOf(i);
73+
}
74+
75+
@Override
76+
public String solve() {
77+
return solve(101, 103, getInput());
78+
}
79+
80+
public static void main(String[] args) {
81+
new Part02().run();
82+
}
83+
}
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
package com.codefork.aoc2024.day16;
2+
3+
import com.codefork.aoc2024.util.Assert;
4+
5+
import java.util.ArrayList;
6+
import java.util.Comparator;
7+
import java.util.HashMap;
8+
import java.util.HashSet;
9+
import java.util.LinkedList;
10+
import java.util.List;
11+
import java.util.Map;
12+
import java.util.Set;
13+
import java.util.stream.Collectors;
14+
import java.util.stream.Stream;
15+
16+
public record Dijkstra(Maze maze, Map<Reindeer, Integer> dist, Map<Reindeer, Set<Reindeer>> prev) {
17+
18+
private static int INFINITY = Integer.MAX_VALUE;
19+
20+
public static Dijkstra create(Maze maze, Set<Edge> edges) {
21+
// intermediate record
22+
record ReindeerDist(Reindeer r, int d) {
23+
24+
}
25+
26+
// all the reindeer found in "from" part of edges
27+
var allReindeer = edges.stream()
28+
.map(Edge::from)
29+
.collect(Collectors.toSet());
30+
31+
// distances of all reindeer from start
32+
Map<Reindeer, Integer> dist = new HashMap<>();
33+
for (Reindeer r : allReindeer) {
34+
dist.put(r, INFINITY);
35+
}
36+
dist.put(new Reindeer(maze.start(), Direction.East), 0);
37+
38+
// The prev array contains pointers to previous-hop nodes on
39+
// the shortest path from source to the given vertex
40+
Map<Reindeer, Set<Reindeer>> prev = new HashMap<>();
41+
42+
List<Reindeer> q = new ArrayList<>(allReindeer);
43+
while (!q.isEmpty()) {
44+
// find a position that has minimum value in dist
45+
var u = q.stream()
46+
.map(r -> new ReindeerDist(r, dist.get(r)))
47+
.min(Comparator.comparingInt(ReindeerDist::d))
48+
.orElseThrow()
49+
.r();
50+
q.remove(u);
51+
52+
//System.out.println("doing " + u);
53+
54+
var edgesFromU = edges.stream()
55+
.filter(e -> e.from().equals(u))
56+
.toList();
57+
58+
// each neighbor of u that's still in Q
59+
var neighbors = edgesFromU.stream()
60+
.map(Edge::to)
61+
.filter(q::contains)
62+
.toList();
63+
64+
//System.out.println("neighbors for " + u + " = " + neighbors);
65+
66+
for (var v : neighbors) {
67+
var joiningEdges = edgesFromU.stream()
68+
.filter(e -> e.to().equals(v))
69+
.toList();
70+
Assert.assertEquals(1, joiningEdges.size());
71+
var joiningEdge = joiningEdges.getFirst();
72+
73+
// sanity check
74+
// if(dist.get(u).equals(INFINITY)) {
75+
// throw new RuntimeException("dist[u] should never be infinity here, but it is");
76+
// }
77+
78+
var alt = dist.get(u) + joiningEdge.score();
79+
// modification of original algorithm: instead of checking if alt < dist[v],
80+
// we check if it's <= to build all shortest paths, not just one. accordingly,
81+
// prev is a Map<Reindeer, Set<Reindeer>> instead of a Map<Reindeer, Reindeer>
82+
if (alt <= dist.get(v)) {
83+
dist.put(v, alt);
84+
//prev.put(v, u);
85+
if(!prev.containsKey(v)) {
86+
prev.put(v, new HashSet<>());
87+
}
88+
prev.get(v).add(u);
89+
}
90+
}
91+
}
92+
return new Dijkstra(maze, dist, prev);
93+
}
94+
95+
/**
96+
* This is slightly complicated: technically, there is more than one possible "end" point
97+
* to the maze, depending on the direction of the approach. we try all possible approaches,
98+
* find the best score out of all of them, and return all the paths matching that best score.
99+
*/
100+
public List<List<Reindeer>> findBestPaths() {
101+
// there can be different ways to reach the end (approaching from diff directions)
102+
var reindeerEnds = maze
103+
.getPossibleApproaches(maze.end())
104+
.stream()
105+
.map(d -> new Reindeer(maze.end(), d))
106+
.toList();
107+
108+
record PathAndScore(List<Reindeer> path, int score) {
109+
110+
}
111+
112+
// find all the paths for different Reindeer ends
113+
var pathsAndScores = reindeerEnds.stream()
114+
.flatMap(u -> {
115+
// construct a path by working backwards from maze end
116+
List<Reindeer> path = new LinkedList<>();
117+
if (prev.containsKey(u) || u.pos().equals(maze.start())) {
118+
while (u != null) {
119+
path.addFirst(u);
120+
if(prev.containsKey(u)) {
121+
// arbitarily pick the first item, doesn't matter
122+
// since we just want any shortest path
123+
u = prev.get(u).stream().findFirst().orElseThrow();
124+
} else {
125+
u = null;
126+
}
127+
}
128+
}
129+
if (!path.isEmpty()) {
130+
return Stream.of(new PathAndScore(path, Reindeer.getPathScore(path)));
131+
}
132+
return Stream.empty();
133+
}).toList();
134+
135+
// find the lowest score, and just return the path(s) with that score
136+
137+
var minScore = pathsAndScores.stream()
138+
.min(Comparator.comparingInt(PathAndScore::score))
139+
.orElseThrow()
140+
.score();
141+
142+
return pathsAndScores.stream()
143+
.filter(pas -> pas.score() == minScore)
144+
.map(PathAndScore::path)
145+
.toList();
146+
}
147+
148+
/**
149+
* count all the tiles on every shortest path to end
150+
*/
151+
public int countTilesOnEveryShortestPathToEnd(Reindeer reindeerEnd) {
152+
record Pair(Reindeer start, Reindeer end) {
153+
154+
}
155+
156+
// walk back from end to the start of the maze,
157+
// grouping reindeer into pairs so we can calculate
158+
// the tile positions we've walked
159+
160+
var visited = new HashSet<Reindeer>();
161+
162+
var tiles = new HashSet<Position>();
163+
var toProcess = new LinkedList<Pair>();
164+
toProcess.add(new Pair(reindeerEnd, reindeerEnd));
165+
while (!toProcess.isEmpty()) {
166+
var pair = toProcess.removeFirst();
167+
var prevReindeer = pair.end();
168+
tiles.addAll(pair.start().tiles(prevReindeer));
169+
// if we've seen this Reindeer already, we don't need to walk it further
170+
if(visited.contains(prevReindeer)) {
171+
continue;
172+
}
173+
if(prev.containsKey(prevReindeer)) {
174+
// create new Pairs for ALL the previous reindeer, to the queue.
175+
// this ensures this walk reaches the tiles on any shortest path.
176+
for (var prevItem : prev.get(prevReindeer)) {
177+
toProcess.add(new Pair(prevReindeer, prevItem));
178+
}
179+
}
180+
visited.add(prevReindeer);
181+
}
182+
return tiles.size();
183+
}
184+
185+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package com.codefork.aoc2024.day16;
2+
3+
public enum Direction {
4+
North, East, South, West
5+
}
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
package com.codefork.aoc2024.day16;
2+
3+
import java.util.List;
4+
import java.util.stream.IntStream;
5+
6+
import static com.codefork.aoc2024.util.FoldLeft.foldLeft;
7+
8+
public record Edge(Reindeer from, Reindeer to) {
9+
10+
public static int calculateScore(List<Edge> path) {
11+
return IntStream.range(0, path.size()).boxed().collect(foldLeft(
12+
() -> 0,
13+
(acc, i) -> acc + path.get(i).score()
14+
));
15+
}
16+
17+
@Override
18+
public String toString() {
19+
return String.format("%s,%s %s -> %s,%s %s (score: %s)",
20+
from.pos().x(),
21+
from.pos().y(),
22+
from.dir(),
23+
to.pos().x(),
24+
to.pos().y(),
25+
to.dir(),
26+
score());
27+
}
28+
29+
public int score() {
30+
return from.score(to);
31+
}
32+
}

0 commit comments

Comments
 (0)