From 003a8bcf115f9428f554d2eacff3564ab0009134 Mon Sep 17 00:00:00 2001 From: Juris Date: Fri, 22 Dec 2023 20:53:34 +0200 Subject: [PATCH] 2023-21 - WIP --- .../jurisk/adventofcode/y2023/Advent21.scala | 221 ++---------------- .../adventofcode/y2023/Advent21Spec.scala | 83 ++++--- 2 files changed, 78 insertions(+), 226 deletions(-) diff --git a/scala2/src/main/scala/jurisk/adventofcode/y2023/Advent21.scala b/scala2/src/main/scala/jurisk/adventofcode/y2023/Advent21.scala index c0a9292f..220a8c38 100644 --- a/scala2/src/main/scala/jurisk/adventofcode/y2023/Advent21.scala +++ b/scala2/src/main/scala/jurisk/adventofcode/y2023/Advent21.scala @@ -171,13 +171,28 @@ object Advent21 { ) def calculateFieldCounts(time: Long, size: Long): FieldCounts = { - val p = time / size - val q = time % size + assert(size.parity == 1) + val halfRoundedDown = size / 2 + val halfRoundedUp = halfRoundedDown + 1 + + val completedEdgeCenter = (((time + 1) / size) - 1) max 0 + val leftOverEdgeCenter = + (time + 1) - (completedEdgeCenter * size) - halfRoundedUp + val edgeCenterMap = + if (leftOverEdgeCenter <= 0) { + Map.empty[Long, Long] + } else if (leftOverEdgeCenter > size) { + Map( + leftOverEdgeCenter -> 1L, + (leftOverEdgeCenter % size) -> 1L, + ) + } else { + Map(leftOverEdgeCenter -> 1L) + } - // TODO: implement FieldCounts( - edgeCenter = InnerCounts(Map.empty, 123), - corner = InnerCounts(Map.empty, 123), + edgeCenter = InnerCounts(edgeCenterMap, completedEdgeCenter), + corner = InnerCounts(Map.empty, 0), // TODO: implement corner ) } @@ -247,8 +262,8 @@ object Advent21 { }.sum }.sum - // TODO: actually also use oddSquareCount - val cornerSquaresFinalised = fieldCounts.corner.finalised * evenSquareCount + // TODO: actually also use evenSquareCount + val cornerSquaresFinalised = fieldCounts.corner.finalised * oddSquareCount val cornerSquaresInProgress = fieldCounts.corner.inProgress.map { case (time, count) => @@ -262,198 +277,6 @@ object Advent21 { centerSquares + edgeSquaresFinalised + edgeSquaresInProgress + cornerSquaresFinalised + cornerSquaresInProgress } - def part2DistRec(data: Input, steps: Int): Long = { - val field = data.field - - // Narrow cross should be empty - assert(field.column(field.width / 2).forall(b => !b)) - assert(field.row(field.height / 2).forall(b => !b)) - - // Edges should be empty - assert(field.allEdgeCoords.forall(c => field.at(c).contains(false))) - - val start = data.start - val distances = distancesFrom(data.field, data.start) - - val manhattanDiffs = distances.mapByCoordsWithValues { case (c, v) => - v map { v => - v - c.manhattanDistance(start) - } - } - - assert( - manhattanDiffs.allEdgeCoords.forall(c => - manhattanDiffs.at(c).flatten == 0.some - ) - ) - - val printableManhattanDiffs = manhattanDiffs.map { - case None => "R" - case Some(0) => "" - case Some(v) => v.toString - } - - Field2D.printStringField(printableManhattanDiffs, 3) - - def distance(c: Coords2D): Long = { - val brutal = distanceBrutal(c) - val smart = distanceSmart(c) - brutal shouldEqual smart - brutal - } - - def distanceBrutal(c: Coords2D): Long = - Dijkstra - .dijkstraWithIdenticalCosts[Coords2D, Long]( - c, - c => - c.adjacent4.filter { n: Coords2D => - val w = wrapCoords(field, n) - field.at(w).contains(false) - }, - _ == start, - ) - .get - ._2 - - def distanceSmart(c: Coords2D): Long = { - // TODO: - // Is it in original field? Then we have the distance? - // Is it further out? Find the offset, take naive MD, adjust somehow? - // Or do we have to somehow check distances from corners / edge midpoints? - // It could be we have to treat the "on the narrow cross" fields separately (calc from edge midpoint) and the - // others separately (more naively) - - val md = c.manhattanDistance(start) - val wrapped = wrapCoords(field, c) - - // TODO: Not just the diff from center, diff from either the center of the edge (for narrow cross) or from the - // corner closest to the start - val diff = manhattanDiffs.at(wrapped).flatten.get - md + diff -// -// if (distances.isValidCoordinate(c)) { -// distances.at(c).flatten.get -// } else { -// val fieldOffset = Coords2D( -// c.x / field.width, -// c.y / field.height, -// ) -// -// println(s"field offset = $fieldOffset for coords $c") -// ??? -// } - } - - // TODO: Can we just iterate the width x height, and for each pixel, based on `steps`, decide in how many - // fields it is "on"? Binary search as a last resort? - - def isOn(c: Coords2D): Boolean = { - val wrapped = wrapCoords(field, c) - if (field.at(wrapped).contains(false)) { - val d = distance(c) - d <= steps && d.parity == steps.parity - } else { - false - } - } - - // TODO: we iterate too much, we should just iterate the diamond shape, not a square - val result = - (-steps to +steps) - .flatMap { x => - (-steps to +steps) - .map { y => - Coords2D(x, y) + data.start - } - } - .count { c => - isOn(c) - } - - result - } - - def part2Interpolated(data: Input, steps: Int): Long = - // Do a flood-fill and for each pixel (in main field), calculate number of fields that have - // Considering checkerboard, number of fields that have it on should be calculable - - // Try to interpolate to algebraic formula that is f(pxCoords, time) -> fieldsReached - // Then can iterate through pixels - - ??? - - def part2Impulses(data: Input, steps: Int): Long = - // We define a concept of "impulse" which is a set of incoming squares that get flipped on - // within a field, and a set of outgoing squares that get flipped outside of square - - // These will likely be repeating in patterns. - - // These patterns determine the concept of "FieldType". - - // These "FieldType"-s will repeat with some regularity. - - // Thus we have one mapping which for each field determines "FieldType" and another which determines - // end-state. - - // But how to avoid having to iterate through all fields? - - ??? - - def part2Stacked(data: Input, steps: Int): Long = { - // Let us build info about each pixel, at what time N it gets flipped on for each field, basically a sum of such pixels. - // Can such N may be a linear equation from which field it is? - // Then we can iterate through pixels (only 113x113) and solve this linear equation. - - final case class Knowledge(firstOn: Long) { - def guess(n: Long): Boolean = - if (n < firstOn) false else firstOn.parity == n.parity - } - - val field = data.field - val positions: Map[Coords2D, Knowledge] = Map(data.start -> Knowledge(0L)) - val results = Simulation.runNIterations(positions, steps) { - case (current, counter) => -// val dimensions = Coords2D(field.width, field.height) -// debugPrint(Area2D(dimensions * -1, dimensions * 2), current) - - val options = current.toList.flatMap { case (c, knowledge) => - if (knowledge.guess(counter)) { - val validNeighbours = c.adjacent4.filter { neighbour => - val adjusted = wrapCoords(field, neighbour) - field.at(adjusted).contains(false) - } - - validNeighbours map { _ -> (counter + 1L) } - } else { - Nil - } - } - - var results = current - options.foreach { case (k, v) => - current.get(k) match { - case Some(value) => assert(value.guess(counter + 1)) - case None => results = results + (k -> Knowledge(v)) - } - } - - results - } - - val grouped: Map[Coords2D, List[Long]] = - results.groupBy { case (k, v) => wrapCoords(data.field, k) }.map { - case (k, v) => - k -> v.values.map(_.firstOn).toList.sorted - } - -// println(grouped) - - results.count { case (_, v) => - v.guess(steps) - } - } - def parseFile(fileName: String): Input = parse(readFileText(fileName)) diff --git a/scala2/src/test/scala/jurisk/adventofcode/y2023/Advent21Spec.scala b/scala2/src/test/scala/jurisk/adventofcode/y2023/Advent21Spec.scala index e5648085..3c9868a9 100644 --- a/scala2/src/test/scala/jurisk/adventofcode/y2023/Advent21Spec.scala +++ b/scala2/src/test/scala/jurisk/adventofcode/y2023/Advent21Spec.scala @@ -52,37 +52,13 @@ class Advent21Spec extends AnyFreeSpec { // part1(realData, 258) shouldEqual 7757 // } // -// } - -// "nextCounts" - { -// val test = parse(s""".S -// |## -// |""".stripMargin) -// -// "simple 1" ignore { -// part2(test, 1) shouldEqual 2 -// } -// -// "simple 2" ignore { -// part2(test, 2) shouldEqual 3 -// } -// -// "simple zoom" ignore { -// (1 to 10) foreach { n => -// val a = part2(test, n) -// val b = part2Old(test, n) -// -// println(n) -// a shouldEqual b -// } -// } -// // } "FieldCounts" - { + val emptyCounts = InnerCounts(Map.empty, 0) + "3" - { - val size = 3 - val emptyCounts = InnerCounts(Map.empty, 0) + val size = 3 "0" in { calculateFieldCounts(0, size) shouldEqual FieldCounts( @@ -132,6 +108,59 @@ class Advent21Spec extends AnyFreeSpec { corner = InnerCounts(Map(3L -> 1L), 0), ) } + + "7" in { + calculateFieldCounts(7, size) shouldEqual FieldCounts( + edgeCenter = InnerCounts(Map(3L -> 1L), 1), + corner = InnerCounts(Map(1L -> 2L, 4L -> 1L), 0), + ) + } + } + + "5" - { + val size = 5 + + "3" in { + calculateFieldCounts(3, size) shouldEqual FieldCounts( + edgeCenter = InnerCounts(Map(1L -> 1L), 0), + corner = emptyCounts, + ) + } + + "4" in { + calculateFieldCounts(4, size) shouldEqual FieldCounts( + edgeCenter = InnerCounts(Map(2L -> 1L), 0), + corner = emptyCounts, + ) + } + + "5" in { + calculateFieldCounts(5, size) shouldEqual FieldCounts( + edgeCenter = InnerCounts(Map(3L -> 1L), 0), + corner = emptyCounts, + ) + } + + "8" in { + calculateFieldCounts(8, size) shouldEqual FieldCounts( + edgeCenter = InnerCounts(Map(6L -> 1L, 1L -> 1L), 0), + corner = InnerCounts(Map(3L -> 1L), 0), + ) + } + + "26" in { + calculateFieldCounts(26, size) shouldEqual FieldCounts( + edgeCenter = InnerCounts(Map(4L -> 1L), 4), + corner = InnerCounts(Map(6L -> 4L), 6), + ) + } + + "28" in { + calculateFieldCounts(28, size) shouldEqual FieldCounts( + edgeCenter = InnerCounts(Map(6L -> 1L, 1L -> 1L), 4), + corner = InnerCounts(Map(3L -> 5L, 8L -> 4L), 6), + ) + } } }