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