Skip to content

Commit 9b57380

Browse files
committed
day 18
1 parent 7d50e41 commit 9b57380

File tree

6 files changed

+226
-0
lines changed

6 files changed

+226
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
package com.codefork.aoc2024.day18;
2+
3+
import com.codefork.aoc2024.util.Maps;
4+
5+
import java.util.Arrays;
6+
import java.util.Collections;
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.Optional;
14+
import java.util.Set;
15+
import java.util.stream.Collectors;
16+
import java.util.stream.IntStream;
17+
import java.util.stream.Stream;
18+
19+
public record MemoryMap(int height, int width, Set<Position> corrupted, Set<Position> empty) {
20+
21+
public static MemoryMap create(int height, int width, Stream<String> data) {
22+
var corrupted = data
23+
.map(line -> {
24+
var parts = Arrays.stream(line.split(","))
25+
.map(Integer::valueOf)
26+
.toList();
27+
return new Position(parts.get(0), parts.get(1));
28+
})
29+
.collect(Collectors.toSet());
30+
31+
var empty = IntStream.range(0, height).boxed()
32+
.flatMap(y ->
33+
IntStream.range(0, width).boxed()
34+
.map(x -> new Position(x, y))
35+
.filter(pos -> !corrupted.contains(pos))
36+
)
37+
.collect(Collectors.toSet());
38+
39+
return new MemoryMap(height, width, corrupted, empty);
40+
}
41+
42+
public Set<Position> getNeighbors(Position pos) {
43+
return Stream.of(pos.decX(), pos.incX(), pos.decY(), pos.incY())
44+
.filter(p ->
45+
!corrupted.contains(p)
46+
&& p.x() >= 0 && p.x() < width && p.y() >= 0 && p.y() < height)
47+
.collect(Collectors.toSet());
48+
}
49+
50+
// Dijkstra's algorithm again
51+
public Optional<Integer> countStepsOnShortestPath() {
52+
record Edge(Position from, Position to) {
53+
54+
}
55+
record PosWithDist(Position p, int d) {
56+
57+
}
58+
var edges = empty().stream()
59+
.flatMap(pos ->
60+
getNeighbors(pos).stream().map(n -> new Edge(pos, n)))
61+
.collect(Collectors.toMap(
62+
Edge::from,
63+
e -> List.of(e.to()),
64+
Maps::listConcat));
65+
66+
var source = new Position(0, 0);
67+
var target = new Position(height - 1, width - 1);
68+
69+
var INFINITY = Integer.MAX_VALUE;
70+
Map<Position, Integer> dist = new HashMap<>();
71+
Map<Position, Position> prev = new HashMap<>();
72+
for (var v : empty()) {
73+
dist.put(v, INFINITY);
74+
}
75+
dist.put(source, 0);
76+
77+
var q = new HashSet<>(empty());
78+
79+
while (!q.isEmpty()) {
80+
var u = q.stream()
81+
.map(pos -> new PosWithDist(pos, dist.get(pos)))
82+
.min(Comparator.comparingInt(PosWithDist::d))
83+
.orElseThrow()
84+
.p();
85+
q.remove(u);
86+
87+
var neighbors = edges
88+
.getOrDefault(u, Collections.emptyList())
89+
.stream()
90+
.filter(q::contains)
91+
.toList();
92+
93+
for (var neighbor : neighbors) {
94+
var alt = dist.get(u) + 1;
95+
if (alt < dist.get(neighbor)) {
96+
dist.put(neighbor, alt);
97+
prev.put(neighbor, u);
98+
}
99+
}
100+
}
101+
102+
// find shortest path
103+
var path = new LinkedList<Position>();
104+
var u = target;
105+
if (prev.containsKey(u) || u.equals(source)) {
106+
while (u != null) {
107+
path.addFirst(u);
108+
u = prev.get(u);
109+
}
110+
}
111+
112+
// did we find a complete path back to the source position?
113+
if (path.getFirst().equals(source)) {
114+
// number of steps = number of nodes - 1
115+
return Optional.of(path.size() - 1);
116+
}
117+
return Optional.empty();
118+
}
119+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package com.codefork.aoc2024.day18;
2+
3+
import com.codefork.aoc2024.Problem;
4+
import com.codefork.aoc2024.util.Assert;
5+
6+
import java.util.stream.Stream;
7+
8+
/**
9+
* This is similar to day 16 but less complicated
10+
*/
11+
public class Part01 extends Problem {
12+
13+
public String solve(int height, int width, Stream<String> data) {
14+
var memoryMap = MemoryMap.create(height, width, data);
15+
var result = memoryMap.countStepsOnShortestPath();
16+
return String.valueOf(result.orElseThrow());
17+
}
18+
19+
@Override
20+
public String solve() {
21+
Assert.assertEquals("22", solve(7, 7, getSampleInput().limit(12)));
22+
return solve(71, 71, getInput().limit(1024));
23+
}
24+
25+
public static void main(String[] args) {
26+
new Part01().run();
27+
}
28+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package com.codefork.aoc2024.day18;
2+
3+
import com.codefork.aoc2024.Problem;
4+
import com.codefork.aoc2024.util.Assert;
5+
6+
import java.util.List;
7+
import java.util.stream.Stream;
8+
9+
public class Part02 extends Problem {
10+
11+
public boolean isUnreachable(int height, int width, List<String> lines) {
12+
return MemoryMap
13+
.create(height, width, lines.stream())
14+
.countStepsOnShortestPath()
15+
.isEmpty();
16+
}
17+
18+
public String solve(int height, int width, Stream<String> data, int start) {
19+
var lines = data.toList();
20+
21+
// there are 2426 positions to try in the real input, which is very doable
22+
// using brute force, but it would take a few minutes. I opted to do a binary
23+
// search instead. this searches for the boundary between reachable -> unreachable
24+
// MemoryMaps, so we can find the first position that made it unreachable.
25+
var lower = start;
26+
var upper = lines.size();
27+
28+
var lastUnreachableN = -1;
29+
int solutionN = -1;
30+
while (solutionN == -1) {
31+
var mid = (lower + upper) / 2;
32+
var unreachable = isUnreachable(height, width, lines.subList(0, mid + 1));
33+
if (unreachable) {
34+
// remember it, and update upper
35+
lastUnreachableN = mid;
36+
upper = mid;
37+
} else {
38+
// we found a solution: are we at the boundary where the next
39+
// entry would be unreachable? if so, we found the solution!
40+
// if not, increase lower and keep going
41+
if (mid + 1 == lastUnreachableN) {
42+
solutionN = lastUnreachableN;
43+
} else {
44+
lower = mid + (mid == lower ? 1 : 0);
45+
}
46+
}
47+
}
48+
return lines.get(solutionN);
49+
}
50+
51+
@Override
52+
public String solve() {
53+
Assert.assertEquals("6,1", solve(7, 7, getSampleInput(), 12));
54+
return solve(71, 71, getInput(), 1024);
55+
}
56+
57+
public static void main(String[] args) {
58+
new Part02().run();
59+
}
60+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.codefork.aoc2024.day18;
2+
3+
public record Position(int x, int y) {
4+
public Position decX() {
5+
return new Position(x - 1, y);
6+
}
7+
8+
public Position incX() {
9+
return new Position(x + 1, y);
10+
}
11+
12+
public Position decY() {
13+
return new Position(x, y - 1);
14+
}
15+
16+
public Position incY() {
17+
return new Position(x, y + 1);
18+
}
19+
}

src/main/resources/day18/input

19.3 KB
Binary file not shown.

src/main/resources/day18/sample

122 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)