diff --git a/src/main/scala/eu/sim642/adventofcode2023/Day21.scala b/src/main/scala/eu/sim642/adventofcode2023/Day21.scala index 1afb34e7..6b886029 100644 --- a/src/main/scala/eu/sim642/adventofcode2023/Day21.scala +++ b/src/main/scala/eu/sim642/adventofcode2023/Day21.scala @@ -29,76 +29,70 @@ object Day21 { SimultaneousBFS.search(graphSearch).distances.count(_._2 % 2 == steps % 2) } - // copied from 2021 day 25 - extension (pos: Pos) { - def %+(other: Pos): Pos = Pos(pos.x %+ other.x, pos.y %+ other.y) + trait Part2Solution { + def countReachableExactlyInfinite(grid: Grid[Char], steps: Int = 26501365): Long } - def countReachableExactlyInfinite(grid: Grid[Char], steps: Int = 26501365): Int = { - val gridSize = Pos(grid(0).size, grid.size) + object NaivePart2Solution extends Part2Solution { - val graphSearch = new GraphSearch[Pos] with UnitNeighbors[Pos] { - override val startNode: Pos = grid.posOf('S') + // copied from 2021 day 25 + extension (pos: Pos) { + def %+(other: Pos): Pos = Pos(pos.x %+ other.x, pos.y %+ other.y) + } - override def unitNeighbors(pos: Pos): IterableOnce[Pos] = { - for { - offset <- Pos.axisOffsets - newPos = pos + offset - if grid(newPos %+ gridSize) != '#' - } yield newPos + override def countReachableExactlyInfinite(grid: Grid[Char], steps: Int = 26501365): Long = { + val gridSize = Pos(grid(0).size, grid.size) + + val graphSearch = new GraphSearch[Pos] with UnitNeighbors[Pos] { + override val startNode: Pos = grid.posOf('S') + + override def unitNeighbors(pos: Pos): IterableOnce[Pos] = { + for { + offset <- Pos.axisOffsets + newPos = pos + offset + if grid(newPos %+ gridSize) != '#' + } yield newPos + } + + override def isTargetNode(pos: Pos, dist: Int): Boolean = dist == steps } - override def isTargetNode(pos: Pos, dist: Int): Boolean = dist == steps + SimultaneousBFS.search(graphSearch).distances.count(_._2 % 2 == steps % 2) } - - SimultaneousBFS.search(graphSearch).distances.count(_._2 % 2 == steps % 2) } - def countReachableExactlyInfinite2(grid: Grid[Char], steps: Int = 26501365): Long = { - val (q, r) = steps /% 131 - //println((q, r)) - - val x1 = 0 - val y1 = countReachableExactlyInfinite(grid, r) - val x2 = 1 - val y2 = countReachableExactlyInfinite(grid, r + 131) - val x3 = 2 - val y3 = countReachableExactlyInfinite(grid, r + 2 * 131) - - def f(x: Long): Long = { - y1 * (x - x2) * (x - x3) / (x1 - x2) / (x1 - x3) + - y2 * (x - x1) * (x - x3) / (x2 - x1) / (x2 - x3) + - y3 * (x - x1) * (x - x2) / (x3 - x1) / (x3 - x2) - } + object QuadraticPart2Solution extends Part2Solution { + + override def countReachableExactlyInfinite(grid: Grid[Char], steps: Int = 26501365): Long = { + val (q, r) = steps /% 131 + //println((q, r)) + + val x1 = 0 + val y1 = NaivePart2Solution.countReachableExactlyInfinite(grid, r) + val x2 = 1 + val y2 = NaivePart2Solution.countReachableExactlyInfinite(grid, r + 131) + val x3 = 2 + val y3 = NaivePart2Solution.countReachableExactlyInfinite(grid, r + 2 * 131) - f(q) - - //val gridSize = Pos(grid(0).size, grid.size) - //print(gridSize) - // - //val graphSearch = new GraphSearch[Pos] with UnitNeighbors[Pos] { - // override val startNode: Pos = grid.posOf('S') - // - // override def unitNeighbors(pos: Pos): IterableOnce[Pos] = { - // for { - // offset <- Pos.axisOffsets - // newPos = pos + offset - // if grid(newPos %+ gridSize) != '#' - // } yield newPos - // } - // - // override def isTargetNode(pos: Pos, dist: Int): Boolean = dist == steps - //} - // - //SimultaneousBFS.search(graphSearch).distances.count(_._2 % 2 == steps % 2) + def f(x: Long): Long = { + y1 * (x - x2) * (x - x3) / (x1 - x2) / (x1 - x3) + + y2 * (x - x1) * (x - x3) / (x2 - x1) / (x2 - x3) + + y3 * (x - x1) * (x - x2) / (x3 - x1) / (x3 - x2) + } + + f(q) + } } + def parseGrid(input: String): Grid[Char] = input.linesIterator.map(_.toVector).toVector lazy val input: String = scala.io.Source.fromInputStream(getClass.getResourceAsStream("day21.txt")).mkString.trim def main(args: Array[String]): Unit = { + import QuadraticPart2Solution._ + println(countReachableExactly(parseGrid(input))) - println(countReachableExactlyInfinite2(parseGrid(input))) + println(countReachableExactlyInfinite(parseGrid(input))) } } diff --git a/src/test/scala/eu/sim642/adventofcode2023/Day21Test.scala b/src/test/scala/eu/sim642/adventofcode2023/Day21Test.scala index c15754e9..deb5f6eb 100644 --- a/src/test/scala/eu/sim642/adventofcode2023/Day21Test.scala +++ b/src/test/scala/eu/sim642/adventofcode2023/Day21Test.scala @@ -1,9 +1,18 @@ package eu.sim642.adventofcode2023 -import Day21._ +import Day21.* +import Day21Test.* +import org.scalatest.Suites import org.scalatest.funsuite.AnyFunSuite +import org.scalatestplus.scalacheck.ScalaCheckPropertyChecks -class Day21Test extends AnyFunSuite { +class Day21Test extends Suites( + new Part1Test, + new NaivePart2SolutionTest, + new QuadraticPart2SolutionTest, +) + +object Day21Test { private val exampleInput = """........... @@ -18,45 +27,65 @@ class Day21Test extends AnyFunSuite { |.##..##.##. |...........""".stripMargin - test("Part 1 examples") { - assert(countReachableExactly(parseGrid(exampleInput), 6) == 16) - } + class Part1Test extends AnyFunSuite { + + test("Part 1 examples") { + assert(countReachableExactly(parseGrid(exampleInput), 6) == 16) + } - test("Part 1 input answer") { - assert(countReachableExactly(parseGrid(input)) == 3687) + test("Part 1 input answer") { + assert(countReachableExactly(parseGrid(input)) == 3687) + } } - test("Part 2 examples") { - //assert(countReachableExactlyInfinite(parseGrid(exampleInput), 6) == 16) - //assert(countReachableExactlyInfinite(parseGrid(exampleInput), 10) == 50) - //assert(countReachableExactlyInfinite(parseGrid(exampleInput), 50) == 1594) - //assert(countReachableExactlyInfinite(parseGrid(exampleInput), 100) == 6536) - //assert(countReachableExactlyInfinite(parseGrid(exampleInput), 500) == 167004) - //assert(countReachableExactlyInfinite(parseGrid(exampleInput), 1000) == 668697) - //assert(countReachableExactlyInfinite(parseGrid(exampleInput), 5000) == 16733044) + class NaivePart2SolutionTest extends AnyFunSuite with ScalaCheckPropertyChecks { + + test("Part 2 examples") { + val stepsExpectedReachable = Table( + ("steps", "expectedReachable"), + (6, 16), + (10, 50), + (50, 1594), + (100, 6536), + // TODO: optimize + //(500, 167004), + //(1000, 668697), + //(5000, 16733044), + ) + + forAll(stepsExpectedReachable) { (steps, expectedReachable) => + assert(NaivePart2Solution.countReachableExactlyInfinite(parseGrid(exampleInput), steps) == expectedReachable) + } + } } - test("Part 2 input answer") { - //assert(countReachableExactlyInfinite(parseGrid(input), 64) == 3687) - //assert(countReachableExactlyInfinite(parseGrid(input), 128) == 14452) - //assert(countReachableExactlyInfinite(parseGrid(input), 256) == 57375) - //assert(countReachableExactlyInfinite(parseGrid(input), 512) == 228690) - //assert(countReachableExactlyInfinite(parseGrid(input), 1024) == 912913) - //println(Day9.Part1.extrapolate(Seq(15130, 60085, 134866, 239473))) + abstract class Part2SolutionTest(part2Solution: Part2Solution) extends AnyFunSuite with ScalaCheckPropertyChecks { - //assert(countReachableExactlyInfinite(parseGrid(input), 131) == 15130) - //assert(countReachableExactlyInfinite(parseGrid(input), 2 * 131) == 60085) - //assert(countReachableExactlyInfinite(parseGrid(input), 3 * 131) == 134866) - //assert(countReachableExactlyInfinite(parseGrid(input), 4 * 131) == 239473) - //assert(countReachableExactlyInfinite(parseGrid(input), 5 * 131) == 373906) + test("Part 2 input answer") { + // computed by NaivePart2Solution + val stepsExpectedReachable = Table( + ("steps", "expectedReachable"), + // TODO: optimize + //(64, 3687), + //(128, 14452), + //(256, 57375), + //(512, 228690), + //(1024, 912913), + (131, 15130), + //(2 * 131, 60085), + //(3 * 131, 134866), + //(4 * 131, 239473), + //(5 * 131, 373906), + ) - //assert(countReachableExactlyInfinite2(parseGrid(input), 64) == 3687) - //assert(countReachableExactlyInfinite2(parseGrid(input), 128) == 14452) - //assert(countReachableExactlyInfinite2(parseGrid(input), 256) == 57375) - //assert(countReachableExactlyInfinite2(parseGrid(input), 512) == 228690) - //assert(countReachableExactlyInfinite2(parseGrid(input), 1024) == 912913) + forAll(stepsExpectedReachable) { (steps, expectedReachable) => + assert(part2Solution.countReachableExactlyInfinite(parseGrid(input), steps) == expectedReachable) + } - assert(countReachableExactlyInfinite2(parseGrid(input)) == 610321885082978L) + assert(part2Solution.countReachableExactlyInfinite(parseGrid(input)) == 610321885082978L) + } } + + class QuadraticPart2SolutionTest extends Part2SolutionTest(QuadraticPart2Solution) }