Skip to content

Commit 5ed90a9

Browse files
committed
Add linear on path solution to 2024 day 18 part 2
1 parent 8e9fa4a commit 5ed90a9

File tree

3 files changed

+79
-20
lines changed

3 files changed

+79
-20
lines changed

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

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@ import eu.sim642.adventofcodelib.OrderedSearch
44
import eu.sim642.adventofcodelib.graph.{BFS, GraphSearch, TargetNode, UnitNeighbors}
55
import eu.sim642.adventofcodelib.pos.Pos
66

7+
import scala.annotation.tailrec
8+
79
object Day18 {
810

911
def bytesGraphSearch(bytes: Seq[Pos], max: Pos = Pos(70, 70), after: Int = 1024): GraphSearch[Pos] & UnitNeighbors[Pos] & TargetNode[Pos] = {
@@ -30,16 +32,51 @@ object Day18 {
3032
BFS.search(graphSearch).target.get._2
3133
}
3234

33-
def exitReachable(bytes: Seq[Pos], max: Pos, after: Int): Boolean = {
34-
val graphSearch = bytesGraphSearch(bytes, max, after)
35-
BFS.search(graphSearch).target.isDefined
35+
trait Part2Solution {
36+
def findBlockingByte(bytes: Seq[Pos], max: Pos = Pos(70, 70)): Pos
37+
38+
def findBlockingByteString(bytes: Seq[Pos], max: Pos = Pos(70, 70)): String = {
39+
val blockingByte = findBlockingByte(bytes, max)
40+
s"${blockingByte.x},${blockingByte.y}"
41+
}
3642
}
3743

38-
def findBlockingByte(bytes: Seq[Pos], max: Pos = Pos(70, 70)): String = {
39-
def f(after: Int): Boolean = !exitReachable(bytes, max, after)
40-
val blockingAfter = OrderedSearch.binaryLower(f, 0, bytes.size + 1)(true)
41-
val blockingByte = bytes(blockingAfter - 1)
42-
s"${blockingByte.x},${blockingByte.y}"
44+
object BinarySearchPart2Solution extends Part2Solution {
45+
def exitReachable(bytes: Seq[Pos], max: Pos, after: Int): Boolean = {
46+
val graphSearch = bytesGraphSearch(bytes, max, after)
47+
BFS.search(graphSearch).target.isDefined
48+
}
49+
50+
override def findBlockingByte(bytes: Seq[Pos], max: Pos = Pos(70, 70)): Pos = {
51+
def f(after: Int): Boolean = !exitReachable(bytes, max, after)
52+
val blockingAfter = OrderedSearch.binaryLower(f, 0, bytes.size + 1)(true)
53+
bytes(blockingAfter - 1)
54+
}
55+
}
56+
57+
object LinearOnPathPart2Solution extends Part2Solution {
58+
def exitPath(bytes: Seq[Pos], max: Pos, after: Int): Option[Seq[Pos]] = {
59+
val graphSearch = bytesGraphSearch(bytes, max, after + 1)
60+
BFS.searchPaths(graphSearch).paths.get(graphSearch.targetNode) // TODO: optimize paths to not compute everything
61+
}
62+
63+
override def findBlockingByte(bytes: Seq[Pos], max: Pos = Pos(70, 70)): Pos = {
64+
65+
@tailrec
66+
def helper(after: Int, path: Set[Pos]): Int = {
67+
if (path(bytes(after))) {
68+
exitPath(bytes, max, after + 1) match {
69+
case Some(newPath) => helper(after + 1, newPath.toSet)
70+
case None => after + 1
71+
}
72+
}
73+
else
74+
helper(after + 1, path)
75+
}
76+
77+
val blockingAfter = helper(0, exitPath(bytes, max, 0).get.toSet)
78+
bytes(blockingAfter - 1)
79+
}
4380
}
4481

4582
def parseByte(s: String): Pos = s match {
@@ -51,7 +88,8 @@ object Day18 {
5188
lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day18.txt")).mkString.trim
5289

5390
def main(args: Array[String]): Unit = {
91+
import BinarySearchPart2Solution._
5492
println(exitSteps(parseBytes(input)))
55-
println(findBlockingByte(parseBytes(input)))
93+
println(findBlockingByteString(parseBytes(input)))
5694
}
5795
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,9 @@ trait Paths[A] {
2828
node -> (node +: LazyList.unfold0(node)(prevNodes.get)).reverse
2929
)
3030
}
31+
32+
/*def paths(node: A): Seq[A] =
33+
(node +: LazyList.unfold0(node)(prevNodes.get)).reverse*/
3134
}
3235

3336
trait Order[A] {

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

Lines changed: 29 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,18 @@
11
package eu.sim642.adventofcode2024
22

33
import Day18.*
4+
import Day18Test.*
45
import eu.sim642.adventofcodelib.pos.Pos
6+
import org.scalatest.Suites
57
import org.scalatest.funsuite.AnyFunSuite
68

7-
class Day18Test extends AnyFunSuite {
9+
class Day18Test extends Suites(
10+
new Part1Test,
11+
new BinarySearchPart2SolutionTest,
12+
new LinearOnPathPart2SolutionTest,
13+
)
14+
15+
object Day18Test {
816

917
val exampleInput =
1018
"""5,4
@@ -33,19 +41,29 @@ class Day18Test extends AnyFunSuite {
3341
|1,6
3442
|2,0""".stripMargin
3543

36-
test("Part 1 examples") {
37-
assert(exitSteps(parseBytes(exampleInput), Pos(6, 6), 12) == 22)
38-
}
44+
class Part1Test extends AnyFunSuite {
45+
test("Part 1 examples") {
46+
assert(exitSteps(parseBytes(exampleInput), Pos(6, 6), 12) == 22)
47+
}
3948

40-
test("Part 1 input answer") {
41-
assert(exitSteps(parseBytes(input)) == 304)
49+
test("Part 1 input answer") {
50+
assert(exitSteps(parseBytes(input)) == 304)
51+
}
4252
}
4353

44-
test("Part 2 examples") {
45-
assert(findBlockingByte(parseBytes(exampleInput), Pos(6, 6)) == "6,1")
46-
}
54+
abstract class Part2SolutionTest(part2Solution: Part2Solution) extends AnyFunSuite {
55+
import part2Solution._
4756

48-
test("Part 2 input answer") {
49-
assert(findBlockingByte(parseBytes(input)) == "50,28")
57+
test("Part 2 examples") {
58+
assert(findBlockingByteString(parseBytes(exampleInput), Pos(6, 6)) == "6,1")
59+
}
60+
61+
test("Part 2 input answer") {
62+
assert(findBlockingByteString(parseBytes(input)) == "50,28")
63+
}
5064
}
65+
66+
class BinarySearchPart2SolutionTest extends Part2SolutionTest(BinarySearchPart2Solution)
67+
68+
class LinearOnPathPart2SolutionTest extends Part2SolutionTest(LinearOnPathPart2Solution)
5169
}

0 commit comments

Comments
 (0)