Skip to content

Commit ea33062

Browse files
committed
Add all paths Dijkstra solution to 2024 day 16 part 2
1 parent 1580750 commit ea33062

File tree

4 files changed

+132
-34
lines changed

4 files changed

+132
-34
lines changed

src/main/scala/eu/sim642/adventofcode2024/Day16.scala

Lines changed: 42 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,36 +34,58 @@ object Day16 {
3434
Dijkstra.search(graphSearch).target.get._2
3535
}
3636

37-
def bestPathTiles(grid: Grid[Char]): Int = {
38-
val forwardSearch = forwardGraphSearch(grid)
39-
val forwardResult = Dijkstra.search(forwardSearch)
40-
41-
val backwardTraversal = new GraphTraversal[Reindeer] with UnitNeighbors[Reindeer] {
42-
override val startNode: Reindeer = forwardResult.target.get._1 // TODO: other orientations
43-
44-
override def unitNeighbors(reindeer: Reindeer): IterableOnce[Reindeer] = {
45-
val distance = forwardResult.distances(reindeer)
46-
for {
47-
(oldReindeer, step) <- Seq(
48-
reindeer.copy(pos = reindeer.pos - reindeer.direction) -> 1, // backward steo
49-
reindeer.copy(direction = reindeer.direction.left) -> 1000,
50-
reindeer.copy(direction = reindeer.direction.right) -> 1000,
51-
)
52-
if grid(oldReindeer.pos) != '#'
53-
oldDistance <- forwardResult.distances.get(oldReindeer)
54-
if oldDistance + step == distance // if step on shortest path
55-
} yield oldReindeer
37+
trait Part2Solution {
38+
def bestPathTiles(grid: Grid[Char]): Int
39+
}
40+
41+
object BackwardNeighborsPart2Solution extends Part2Solution {
42+
override def bestPathTiles(grid: Grid[Char]): Int = {
43+
val forwardSearch = forwardGraphSearch(grid)
44+
val forwardResult = Dijkstra.search(forwardSearch)
45+
46+
val backwardTraversal = new GraphTraversal[Reindeer] with UnitNeighbors[Reindeer] {
47+
override val startNode: Reindeer = forwardResult.target.get._1 // TODO: other orientations
48+
49+
override def unitNeighbors(reindeer: Reindeer): IterableOnce[Reindeer] = {
50+
val distance = forwardResult.distances(reindeer)
51+
for {
52+
(oldReindeer, step) <- Seq(
53+
reindeer.copy(pos = reindeer.pos - reindeer.direction) -> 1, // backward step
54+
reindeer.copy(direction = reindeer.direction.left) -> 1000,
55+
reindeer.copy(direction = reindeer.direction.right) -> 1000,
56+
)
57+
if grid(oldReindeer.pos) != '#'
58+
oldDistance <- forwardResult.distances.get(oldReindeer)
59+
if oldDistance + step == distance // if step on shortest path
60+
} yield oldReindeer
61+
}
5662
}
63+
64+
BFS.traverse(backwardTraversal).nodes.map(_.pos).size
5765
}
66+
}
5867

59-
BFS.traverse(backwardTraversal).nodes.map(_.pos).size
68+
object AllPathsPart2Solution extends Part2Solution {
69+
override def bestPathTiles(grid: Grid[Char]): Int = {
70+
val forwardSearch = forwardGraphSearch(grid)
71+
val forwardResult = Dijkstra.searchAllPaths(forwardSearch)
72+
73+
val backwardTraversal = new GraphTraversal[Reindeer] with UnitNeighbors[Reindeer] {
74+
override val startNode: Reindeer = forwardResult.target.get._1 // TODO: other orientations
75+
76+
override def unitNeighbors(reindeer: Reindeer): IterableOnce[Reindeer] = forwardResult.allPrevNodes.getOrElse(reindeer, Set.empty)
77+
}
78+
79+
BFS.traverse(backwardTraversal).nodes.map(_.pos).size
80+
}
6081
}
6182

6283
def parseGrid(input: String): Grid[Char] = input.linesIterator.map(_.toVector).toVector
6384

6485
lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day16.txt")).mkString.trim
6586

6687
def main(args: Array[String]): Unit = {
88+
import AllPathsPart2Solution._
6789
println(lowestScore(parseGrid(input)))
6890
println(bestPathTiles(parseGrid(input)))
6991
}

src/main/scala/eu/sim642/adventofcodelib/graph/Dijkstra.scala

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,58 @@ object Dijkstra {
7979
override def target: Option[(A, Int)] = None
8080
}
8181
}
82+
83+
// copied from search, modified like BFS.searchPaths
84+
def searchAllPaths[A](graphSearch: GraphSearch[A]): Distances[A] & AllPaths[A] & Target[A] = {
85+
val visitedDistance: mutable.Map[A, Int] = mutable.Map.empty
86+
val prevNode: mutable.Map[A, mutable.Set[A]] = mutable.Map.empty
87+
val toVisit: mutable.PriorityQueue[(Int, Option[A], A)] = mutable.PriorityQueue.empty(Ordering.by(-_._1))
88+
89+
def enqueue(oldNode: Option[A], node: A, dist: Int): Unit = {
90+
toVisit.enqueue((dist, oldNode, node))
91+
}
92+
93+
enqueue(None, graphSearch.startNode, 0)
94+
95+
while (toVisit.nonEmpty) {
96+
val (dist, oldNode, node) = toVisit.dequeue()
97+
if (!visitedDistance.contains(node)) {
98+
visitedDistance(node) = dist
99+
for (oldNode <- oldNode)
100+
prevNode(node) = mutable.Set(oldNode)
101+
102+
if (graphSearch.isTargetNode(node, dist)) {
103+
return new Distances[A] with AllPaths[A] with Target[A] {
104+
override def distances: collection.Map[A, Int] = visitedDistance
105+
106+
override def allPrevNodes: collection.Map[A, collection.Set[A]] = prevNode
107+
108+
override def target: Option[(A, Int)] = Some(node -> dist)
109+
}
110+
}
111+
112+
113+
def goNeighbor(newNode: A, distDelta: Int): Unit = {
114+
if (!visitedDistance.contains(newNode)) { // avoids some unnecessary queue duplication but not all
115+
val newDist = dist + distDelta
116+
enqueue(Some(node), newNode, newDist)
117+
}
118+
}
119+
120+
graphSearch.neighbors(node).iterator.foreach(goNeighbor.tupled)
121+
}
122+
else { // visitedDistance.contains(node)
123+
for (oldNode <- oldNode if visitedDistance(node) == dist)
124+
prevNode(node) += oldNode
125+
}
126+
}
127+
128+
new Distances[A] with AllPaths[A] with Target[A] {
129+
override def distances: collection.Map[A, Int] = visitedDistance
130+
131+
override def allPrevNodes: collection.Map[A, collection.Set[A]] = prevNode
132+
133+
override def target: Option[(A, Int)] = None
134+
}
135+
}
82136
}

src/main/scala/eu/sim642/adventofcodelib/graph/GraphTraversal.scala

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,10 @@ trait Paths[A] {
2929
)
3030
}
3131

32+
trait AllPaths[A] { // does not extend Paths, because prevNodes is Map, not function
33+
def allPrevNodes: collection.Map[A, collection.Set[A]]
34+
}
35+
3236
trait Order[A] {
3337
def nodeOrder: collection.Seq[A]
3438
}

src/test/scala/eu/sim642/adventofcode2024/Day16Test.scala

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,17 @@
11
package eu.sim642.adventofcode2024
22

3-
import Day16._
3+
import Day16.*
4+
import Day16Test.*
5+
import org.scalatest.Suites
46
import org.scalatest.funsuite.AnyFunSuite
57

6-
class Day16Test extends AnyFunSuite {
8+
class Day16Test extends Suites(
9+
new Part1Test,
10+
new BackwardNeighborsPart2SolutionTest,
11+
new AllPathsPart2SolutionTest,
12+
)
13+
14+
object Day16Test {
715

816
val exampleInput =
917
"""###############
@@ -41,21 +49,31 @@ class Day16Test extends AnyFunSuite {
4149
|#S#.............#
4250
|#################""".stripMargin
4351

44-
test("Part 1 examples") {
45-
assert(lowestScore(parseGrid(exampleInput)) == 7036)
46-
assert(lowestScore(parseGrid(exampleInput2)) == 11048)
47-
}
52+
class Part1Test extends AnyFunSuite {
53+
test("Part 1 examples") {
54+
assert(lowestScore(parseGrid(exampleInput)) == 7036)
55+
assert(lowestScore(parseGrid(exampleInput2)) == 11048)
56+
}
4857

49-
test("Part 1 input answer") {
50-
assert(lowestScore(parseGrid(input)) == 73404)
58+
test("Part 1 input answer") {
59+
assert(lowestScore(parseGrid(input)) == 73404)
60+
}
5161
}
5262

53-
test("Part 2 examples") {
54-
assert(bestPathTiles(parseGrid(exampleInput)) == 45)
55-
assert(bestPathTiles(parseGrid(exampleInput2)) == 64)
56-
}
63+
class Part2SolutionTest(part2Solution: Part2Solution) extends AnyFunSuite {
64+
import part2Solution._
5765

58-
test("Part 2 input answer") {
59-
assert(bestPathTiles(parseGrid(input)) == 449)
66+
test("Part 2 examples") {
67+
assert(bestPathTiles(parseGrid(exampleInput)) == 45)
68+
assert(bestPathTiles(parseGrid(exampleInput2)) == 64)
69+
}
70+
71+
test("Part 2 input answer") {
72+
assert(bestPathTiles(parseGrid(input)) == 449)
73+
}
6074
}
75+
76+
class BackwardNeighborsPart2SolutionTest extends Part2SolutionTest(BackwardNeighborsPart2Solution)
77+
78+
class AllPathsPart2SolutionTest extends Part2SolutionTest(AllPathsPart2Solution)
6179
}

0 commit comments

Comments
 (0)