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)
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