|
1 | 1 | import de.ronny_h.extensions.* |
2 | | -import de.ronny_h.extensions.Direction.* |
3 | 2 |
|
4 | 3 | fun main() { |
5 | 4 | val day = "Day20" |
6 | 5 |
|
7 | | - fun part1(input: List<String>, minPicosecondsSaved: Int): Int { |
| 6 | + fun part1(input: List<String>, minPicosecondsSaved: Int, shortcutMaxLength: Int): Int { |
8 | 7 | val track = RaceTrack(input) |
9 | 8 | track.printGrid() |
10 | | - val baseline = track.shortestPath().distance |
11 | | - return track.countAllShortcutsShorterThan(baseline - minPicosecondsSaved) |
| 9 | + return track.countAllShortcutsSavingAtLeast(minPicosecondsSaved, shortcutMaxLength) |
12 | 10 | } |
13 | 11 |
|
14 | | - fun part1Small(input: List<String>) = part1(input, 10) |
15 | | - fun part1Large(input: List<String>) = part1(input, 100) |
| 12 | + fun part1Small(input: List<String>) = part1(input, 10, 2) |
| 13 | + fun part1Large(input: List<String>) = part1(input, 100, 2) |
16 | 14 |
|
17 | | - fun part2(input: List<String>): Int { |
18 | | - return input.size |
19 | | - } |
| 15 | + fun part2Small(input: List<String>) = part1(input, 76, 20) |
| 16 | + fun part2Large(input: List<String>) = part1(input, 100, 20) |
20 | 17 |
|
21 | 18 | println("$day part 1") |
22 | 19 |
|
@@ -45,13 +42,12 @@ fun main() { |
45 | 42 |
|
46 | 43 | println("$day part 2") |
47 | 44 |
|
48 | | - printAndCheck(testInput, ::part2, 31) |
49 | | - printAndCheck(input, ::part2, 18805872) |
| 45 | + printAndCheck(testInput, ::part2Small, 3) |
| 46 | + printAndCheck(input, ::part2Large, 1026446) |
50 | 47 | } |
51 | 48 |
|
52 | 49 | private class RaceTrack(input: List<String>) : Grid<Char>(input, '#') { |
53 | 50 | private val wall = nullElement |
54 | | - private val free = '.' |
55 | 51 | override fun Char.toElementType(): Char = this |
56 | 52 |
|
57 | 53 | private val start = find('S') |
@@ -82,32 +78,23 @@ private class RaceTrack(input: List<String>) : Grid<Char>(input, '#') { |
82 | 78 | return aStar(start, goal, neighbours, d, h) |
83 | 79 | } |
84 | 80 |
|
85 | | - fun countAllShortcutsShorterThan(picoseconds: Int): Int = |
86 | | - forEachCoordinates { pos, elem -> |
87 | | - when { |
88 | | - elem != wall -> null |
89 | | - pos.isOnTheBorder() -> null |
90 | | - pos.canNotBeCrossed() -> null |
91 | | - else -> pos |
92 | | - } |
93 | | - } |
94 | | - .filterNotNull() |
95 | | - .map { coord -> |
96 | | - setAt(coord, free) |
97 | | - val pathWithShortcut = shortestPath() |
98 | | - setAt(coord, wall) |
99 | | - pathWithShortcut.distance |
100 | | - } |
101 | | - .filter { it <= picoseconds } |
102 | | - .count() |
103 | | - |
104 | | - private fun Coordinates.isOnTheBorder(): Boolean { |
105 | | - return row == 0 || row == (height - 1) || col == 0 || col == width - 1 |
106 | | - } |
| 81 | + fun countAllShortcutsSavingAtLeast(minToSave: Int, shortcutMaxLength: Int): Int { |
| 82 | + val shortestPath = shortestPath() |
| 83 | + val distances = shortestPath.path.reversed().mapIndexed { distanceToGoal, position -> |
| 84 | + position to distanceToGoal |
| 85 | + }.toMap() |
107 | 86 |
|
108 | | - private fun Coordinates.canNotBeCrossed(): Boolean { |
109 | | - return (getAt(this + NORTH) == wall || getAt(this + SOUTH) == wall) && |
110 | | - (getAt(this + EAST) == wall || getAt(this + WEST) == wall) |
111 | | - } |
| 87 | + fun Coordinates.isInShortcutRangeFrom(position: Coordinates): Boolean = |
| 88 | + this taxiDistanceTo position <= shortcutMaxLength |
| 89 | + |
| 90 | + fun Coordinates.isShorterToGoalThan(position: Coordinates): Boolean = |
| 91 | + distances.getValue(this) + (this taxiDistanceTo position) <= distances.getValue(position) - minToSave |
112 | 92 |
|
| 93 | + return shortestPath.path.flatMap { position -> |
| 94 | + shortestPath |
| 95 | + .path |
| 96 | + .filter { it.isInShortcutRangeFrom(position) } |
| 97 | + .filter { it.isShorterToGoalThan(position) } |
| 98 | + }.size |
| 99 | + } |
113 | 100 | } |
0 commit comments