Skip to content

Commit 126e532

Browse files
committed
aoc2023 day 16 + 17, added dijkstra util
1 parent 156cc0b commit 126e532

File tree

4 files changed

+270
-1
lines changed

4 files changed

+270
-1
lines changed
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
package com.matsemann.adventofcode2023
2+
3+
import com.matsemann.adventofcode2023.utils.*
4+
import com.matsemann.adventofcode2023.utils.Direction.*
5+
6+
fun day16_1(lines: List<String>): Any {
7+
val grid = lines.map { it.toList() }
8+
val bounds = grid.bounds()
9+
val res = generateSequence { }.scan(
10+
listOf(
11+
IntVec(
12+
0,
13+
0
14+
) to RIGHT
15+
) to setOf<Pair<IntVec, Direction>>()
16+
) { (beams, seenStates), _ ->
17+
val newBeams = beams.flatMap { (pos, dir) ->
18+
if (!pos.withinBounds(bounds) || (pos to dir) in seenStates) {
19+
emptyList()
20+
} else {
21+
val value = grid[pos]
22+
when {
23+
value == '|' && dir in listOf(LEFT, RIGHT) -> listOf((pos + DOWN) to DOWN, (pos + UP) to UP)
24+
value == '-' && dir in listOf(DOWN, UP) -> listOf((pos + LEFT) to LEFT, (pos + RIGHT) to RIGHT)
25+
value == '/' && dir == RIGHT -> listOf((pos + UP) to UP)
26+
value == '/' && dir == DOWN -> listOf((pos + LEFT) to LEFT)
27+
value == '/' && dir == LEFT -> listOf((pos + DOWN) to DOWN)
28+
value == '/' && dir == UP -> listOf((pos + RIGHT) to RIGHT)
29+
value == '\\' && dir == RIGHT -> listOf((pos + DOWN) to DOWN)
30+
value == '\\' && dir == DOWN -> listOf((pos + RIGHT) to RIGHT)
31+
value == '\\' && dir == LEFT -> listOf((pos + UP) to UP)
32+
value == '\\' && dir == UP -> listOf((pos + LEFT) to LEFT)
33+
else -> listOf((pos + dir) to dir)
34+
}
35+
}
36+
}
37+
38+
newBeams to (seenStates + beams)
39+
}.dropWhile { (beams, seenStates) -> beams.isNotEmpty() }.first()
40+
41+
val seenPos = res.second.map { it.first }.filter { it.withinBounds(bounds) }
42+
// println(seenPos.showAsGrid())
43+
return seenPos.toSet().size
44+
}
45+
46+
47+
fun day16_2(lines: List<String>): Any {
48+
val grid = lines.map { it.toList() }
49+
val bounds = grid.bounds()
50+
51+
val vertical = (0..bounds.y).flatMap { y -> listOf(IntVec(0, y) to RIGHT, IntVec(bounds.x, y) to LEFT) }
52+
val horizontal = (0..bounds.x).flatMap { x -> listOf(IntVec(x, 0) to DOWN, IntVec(x, bounds.y) to UP) }
53+
54+
return (vertical + horizontal).maxOf { startPos ->
55+
generateSequence { }.scan(listOf(startPos) to setOf<Pair<IntVec, Direction>>()) { (beams, seenStates), _ ->
56+
val newBeams = beams.flatMap { (pos, dir) ->
57+
if (!pos.withinBounds(bounds) || (pos to dir) in seenStates) {
58+
emptyList()
59+
} else {
60+
val value = grid[pos]
61+
when {
62+
value == '|' && dir in listOf(LEFT, RIGHT) -> listOf((pos + DOWN) to DOWN, (pos + UP) to UP)
63+
value == '-' && dir in listOf(DOWN, UP) -> listOf(
64+
(pos + LEFT) to LEFT,
65+
(pos + RIGHT) to RIGHT
66+
)
67+
68+
value == '/' && dir == RIGHT -> listOf((pos + UP) to UP)
69+
value == '/' && dir == DOWN -> listOf((pos + LEFT) to LEFT)
70+
value == '/' && dir == LEFT -> listOf((pos + DOWN) to DOWN)
71+
value == '/' && dir == UP -> listOf((pos + RIGHT) to RIGHT)
72+
value == '\\' && dir == RIGHT -> listOf((pos + DOWN) to DOWN)
73+
value == '\\' && dir == DOWN -> listOf((pos + RIGHT) to RIGHT)
74+
value == '\\' && dir == LEFT -> listOf((pos + UP) to UP)
75+
value == '\\' && dir == UP -> listOf((pos + LEFT) to LEFT)
76+
else -> listOf((pos + dir) to dir)
77+
}
78+
}
79+
}
80+
81+
newBeams to (seenStates + beams)
82+
}.dropWhile { (beams, seenStates) -> beams.isNotEmpty() }
83+
.first().second
84+
.map { it.first }.filter { it.withinBounds(bounds) }.toSet()
85+
.size
86+
}
87+
}
88+
89+
fun main() {
90+
Direction.setYDown()
91+
92+
val a = generateSequence { }.runningFold(0) { acc, _ ->
93+
acc + 1
94+
}.dropWhile { it < 8 }.take(5).toList()
95+
println(a)
96+
97+
// run("1", fileName = "day16_ex.txt", func = ::day16_1)
98+
// run("2", fileName = "day16_ex.txt", func = ::day16_2)
99+
100+
101+
// run("1", fileName = "day16.txt", func = ::day16_1)
102+
// run("2", fileName = "day16.txt", func = ::day16_2)
103+
}
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package com.matsemann.adventofcode2023
2+
3+
import com.matsemann.adventofcode2023.utils.*
4+
import com.matsemann.adventofcode2023.utils.IntVec.Companion.showAsGrid
5+
6+
fun day17_1(lines: List<String>): Any {
7+
val grid = lines.map { it.toList().map { it.digitToInt().toLong() } }
8+
val bounds = grid.bounds()
9+
10+
data class State(val pos: IntVec, val dir: Direction, val straights: Int)
11+
12+
val dijkstra = Dijkstra<State> {(pos, dir, straights) ->
13+
val straight = if (straights < 3 && (pos+dir).withinBounds(bounds)) {
14+
State(pos + dir, dir, straights + 1) to grid[pos+dir]
15+
} else {
16+
null
17+
}
18+
val leftDir = dir.turnCcw()
19+
val left = if ((pos+leftDir).withinBounds(bounds)) {
20+
State(pos + leftDir, leftDir, 1) to grid[pos+leftDir]
21+
} else {
22+
null
23+
}
24+
val rightDir = dir.turnCw()
25+
val right = if ((pos+rightDir).withinBounds(bounds)) {
26+
State(pos + rightDir, rightDir, 1) to grid[pos+rightDir]
27+
} else {
28+
null
29+
}
30+
31+
listOfNotNull(straight, left, right)
32+
}
33+
val res = dijkstra.solve(State(IntVec(0,0), Direction.RIGHT, 0)) { (pos, dir, straights) ->
34+
pos == bounds
35+
}
36+
37+
val path = dijkstra.path(res!!.first)
38+
val cost = path.map { grid[it.pos] }.sum() - grid[IntVec(0,0)]
39+
40+
return cost
41+
}
42+
43+
44+
fun day17_2(lines: List<String>): Any {
45+
val grid = lines.map { it.toList().map { it.digitToInt().toLong() } }
46+
val bounds = grid.bounds()
47+
48+
data class State(val pos: IntVec, val dir: Direction, val straights: Int)
49+
50+
val dijkstra = Dijkstra<State> {(pos, dir, straights) ->
51+
val straight = if (straights < 10 && (pos+dir).withinBounds(bounds)) {
52+
State(pos + dir, dir, straights + 1) to grid[pos+dir]
53+
} else {
54+
null
55+
}
56+
val leftDir = dir.turnCcw()
57+
val left = if (straights > 3 && (pos+leftDir).withinBounds(bounds)) {
58+
State(pos + leftDir, leftDir, 1) to grid[pos+leftDir]
59+
} else {
60+
null
61+
}
62+
val rightDir = dir.turnCw()
63+
val right = if (straights > 3 && (pos+rightDir).withinBounds(bounds)) {
64+
State(pos + rightDir, rightDir, 1) to grid[pos+rightDir]
65+
} else {
66+
null
67+
}
68+
69+
listOfNotNull(straight, left, right)
70+
}
71+
// Now start direction matters, but simpler to just try UP and RIGHT
72+
// and see which one is shorter, than adding support for two start nodes in my
73+
// Dijkstra
74+
val res = dijkstra.solve(State(IntVec(0,0), Direction.UP, 0)) { (pos, dir, straights) ->
75+
pos == bounds && straights > 3
76+
}
77+
78+
val path = dijkstra.path(res!!.first)
79+
80+
println(path.map { it.pos }.showAsGrid())
81+
return path.map { grid[it.pos] }.sum() - grid[IntVec(0,0)]
82+
}
83+
84+
fun main() {
85+
86+
// run("1", fileName = "day17_ex.txt", func = ::day17_1)
87+
run("2", fileName = "day17_ex.txt", func = ::day17_2)
88+
run("2", fileName = "day17_ex2.txt", func = ::day17_2)
89+
90+
91+
// run("1", fileName = "day17.txt", func = ::day17_1)
92+
// 1304 too high
93+
run("2", fileName = "day17.txt", func = ::day17_2)
94+
}

adventofcode2023/src/main/kotlin/com/matsemann/adventofcode2023/utils/GraphUtils.kt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@ class DirectedGraph() : Graph {
2929
}
3030
}
3131

32-
class Dijkstra() {}
3332
class AStar() {}
3433
class FloydWarshall() {}
3534

adventofcode2023/src/main/kotlin/com/matsemann/adventofcode2023/utils/SearchUtils.kt

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.matsemann.adventofcode2023.utils
22

3+
import java.util.PriorityQueue
4+
35
/**
46
* A general breadth first search, where you
57
* can pass in the neighbor generating function,
@@ -70,9 +72,80 @@ class BFS<E>(val neighborFunc: BFS<E>.(E) -> Iterable<E>) {
7072

7173
}
7274

75+
class Dijkstra<E>(val neighborFunc: Dijkstra<E>.(E) -> Iterable<Pair<E, Long>>) {
76+
77+
val Q = PriorityQueue<Pair<E, Long>>(compareBy { it.second })
78+
val visited = mutableSetOf<E>()
79+
val seen = mutableSetOf<E>()
80+
var endFound: Pair<E, Long>? = null
81+
var parent = mutableMapOf<E, E>()
82+
83+
fun clear() {
84+
Q.clear()
85+
seen.clear()
86+
visited.clear()
87+
parent.clear()
88+
endFound = null
89+
}
90+
91+
/**
92+
* Runs it, either until it's not possible to go any further,
93+
* or if provided until the goal function returns true for a node
94+
*/
95+
fun solve(start: E, goalFunction: (Dijkstra<E>.(E) -> Boolean)? = null): Pair<E, Long>? {
96+
clear()
97+
Q.add(start to 0)
98+
seen.add(start)
99+
100+
while (Q.isNotEmpty()) {
101+
val (currentNode, cost) = Q.remove()
102+
if (currentNode in visited) {
103+
continue
104+
}
105+
visited += currentNode
106+
107+
if (goalFunction != null && this.goalFunction(currentNode)) {
108+
endFound = currentNode to cost
109+
return endFound
110+
}
111+
112+
val neighbors = this.neighborFunc(currentNode)
113+
neighbors.forEach { (neighbor, neighborCost) ->
114+
if (neighbor !in visited) {
115+
val totalCost = cost + neighborCost
116+
val currentCost = Q.find { it.first == neighbor }?.second ?: Long.MAX_VALUE
117+
if (totalCost < currentCost) {
118+
parent[neighbor] = currentNode
119+
Q.offer(neighbor to totalCost)
120+
}
121+
}
122+
}
123+
}
124+
125+
return null
126+
}
127+
128+
/**
129+
* After a solve, returns the path from start to the node
130+
*/
131+
fun path(node: E): List<E> {
132+
val path = mutableListOf<E>()
133+
var current: E? = node
134+
while (current != null) {
135+
path.addFirst(current)
136+
current = parent[current]
137+
}
138+
return path
139+
}
140+
141+
142+
}
143+
144+
73145
fun main() {
74146

75147
data class Node(val name: String, val neighbors: List<String>)
148+
76149
val nodes = listOf(
77150
Node("A", listOf("B", "C")),
78151
Node("B", listOf("A", "C")),

0 commit comments

Comments
 (0)