diff --git a/scala/hack-by-example/readme.md b/scala/hack-by-example/readme.md index 66bba56e..374c2d33 100644 --- a/scala/hack-by-example/readme.md +++ b/scala/hack-by-example/readme.md @@ -30,12 +30,13 @@ Advent of Code 2024 | ๐ŸŸข[Day 11][AoC2024-11] ([Solution][Sol-AoC2024-11]) | Apply rules to a set of numbers. Two good solutions, one is a nice example of memoization. | | ๐Ÿ”ถ[Day 12][AoC2024-12] ([Solution][Sol-AoC2024-12]) | Finding disjoint sets (using union-find or merge-find) to calculate perimeters or sides of garden plots in a 2D map. | | ๐Ÿ”ถ[Day 13][AoC2024-13] ([Solution][Sol-AoC2024-13]) | Miniature linear algebra, finding integer solutions to two equations. | -| ๐Ÿ”ถ[Day 14][AoC2024-14] ([Solution][Sol-AoC2024-14]) | ๐Ÿ’œRobots moving around a grid given a starting point and vector. Find the pretty picture they make. | +| ๐Ÿ”ถ[Day 14][AoC2024-14] ([Solution][Sol-AoC2024-14]) | ๐Ÿ’œ๏ธ Robots moving around a grid given a starting point and vector. Find the pretty picture they make. | | ๐ŸŸฅ[Day 15][AoC2024-15] ([Solution][Sol-AoC2024-15]) | A Robot pushing movable boxes in a 2D warehouse, where boxes can push other boxes. | | ๐Ÿ”ถ[Day 16][AoC2024-16] ([Solution][Sol-AoC2024-16]) | Find the minimum cost path through a maze, then all minimum cost paths. BFS and directional map. | | ๐ŸŸฅ[Day 17][AoC2024-17] ([Solution][Sol-AoC2024-17]) | Make a simple CPU with opcodes and operands to find the output, then study the input program to make a quine. Lots of bit fiddling. | -| ๐ŸŸข[Day 18][AoC2024-18] ([Solution][Sol-yAoC2024-18]) | ๐Ÿ’œ๏ธSimple BFS pathfinding through a grid (without drawing a plan), then a binary search to find the first blocked path. | -| ๐ŸŸข[Day 19][AoC2024-19] ([Solution][Sol-yAoC2024-19]) | ๐Ÿ’œ๏ธSplitting a design into valid towels, using memoization. | +| ๐ŸŸข[Day 18][AoC2024-18] ([Solution][Sol-AoC2024-18]) | ๐Ÿ’œ๏ธ Simple BFS pathfinding through a grid (without drawing a plan), then a binary search to find the first blocked path. | +| ๐ŸŸข[Day 19][AoC2024-19] ([Solution][Sol-AoC2024-19]) | ๐Ÿ’œ๏ธ Splitting a design into valid towels, using memoization. | +| ๐ŸŸข[Day 20][AoC2024-20] ([Solution][Sol-AoC2024-20]) | Running a maze choosing when some walls can disappear. | [AoC2024-01]: https://adventofcode.com/2024/day/1 [AoC2024-02]: https://adventofcode.com/2024/day/2 diff --git a/scala/hack-by-example/src/test/resources/com/skraba/byexample/scala/hack/advent2024/Day20Input.txt b/scala/hack-by-example/src/test/resources/com/skraba/byexample/scala/hack/advent2024/Day20Input.txt index 25391d34..180f8aaa 100644 --- a/scala/hack-by-example/src/test/resources/com/skraba/byexample/scala/hack/advent2024/Day20Input.txt +++ b/scala/hack-by-example/src/test/resources/com/skraba/byexample/scala/hack/advent2024/Day20Input.txt @@ -1,2 +1,2 @@ !! Set ADVENT_OF_CODE_KEY to decrypt (https://adventofcode.com/about) -g6nexIDgFMyQFVY6fruWbg== \ No newline at end of file +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 \ No newline at end of file diff --git a/scala/hack-by-example/src/test/scala/com/skraba/byexample/scala/hack/advent2024/AdventOfCodeDay20Spec.scala b/scala/hack-by-example/src/test/scala/com/skraba/byexample/scala/hack/advent2024/AdventOfCodeDay20Spec.scala index ba904911..ba3bc30a 100644 --- a/scala/hack-by-example/src/test/scala/com/skraba/byexample/scala/hack/advent2024/AdventOfCodeDay20Spec.scala +++ b/scala/hack-by-example/src/test/scala/com/skraba/byexample/scala/hack/advent2024/AdventOfCodeDay20Spec.scala @@ -4,14 +4,20 @@ import com.skraba.byexample.scala.hack.advent2024.AdventUtils._ import org.scalatest.BeforeAndAfterEach import org.scalatest.funspec.AnyFunSpecLike import org.scalatest.matchers.should.Matchers +import org.scalatest.tagobjects.Slow + +import scala.collection.mutable /** =Advent of Code 2024 Day 20 Solutions in scala= * - * Input: + * Input: A maze with walls defined by '#', and a single non-branching path starting from 'S' and ending at 'E'. Since + * there is only one path, there's a fixed time to finish the maze where one step costs one picosecond. * - * Part 1: + * Part 1: You can turn off the walls for two picoseconds (meaning that you can pass through at most two walls) to take + * a shortcut. A unique shortcut is defined from one point on the original path to another point on the original path. + * How many shortcuts can save you at least 100 picoseconds? * - * Part 2: + * Part 2: You can turn off the walls for twenty picoseconds. How many shortcuts can save you 100 picoseconds now? * * @see * Rephrased from [[https://adventofcode.com/2024/day/20]] @@ -20,41 +26,108 @@ class AdventOfCodeDay20Spec extends AnyFunSpecLike with Matchers with BeforeAndA object Solution { - case class ABC(a: Long) {} + def part1(in: String*): Long = solve(3, 100, in: _*).map(_._2).sum + + def part2(in: String*): Long = solve(21, 100, in: _*).map(_._2).sum + + /** Find the different ways you can cheat to save time in the maze. + * + * @param cheat + * The number of picoseconds the walls are turned off plus ONE. This is the maximum distance you can travel from + * one valid step on the input path to another valid step. + * @param in + * The maze as a sequence of strings. + * @param min + * The minimum time you have to save off the original route to be counted. + * @return + * A list of tuples corresponding to how much time could be saved due to shortcuts, in the form (saved -> count) + * where {{count}} is the number of unique shortcuts that shave {{saved}} picoseconds off the time. + */ + def solve(cheat: Int, min: Int, in: String*): Seq[(Int, Int)] = { + val (dx, dy) = (in.head.length, in.length) + val plan: String = in.mkString + val start = plan.indexOf('S') - def parse(in: String): Option[ABC] = None + // The original path in reverse order. + val path: Seq[Int] = LazyList + .iterate(List(start)) { path => + Seq(1, dx, -1, -dx) + .map(_ + path.head) + .filterNot(path.length > 1 && path(1) == _) // Don't go back + .find(plan(_) != '#') + .head :: path + } + .find(path => plan(path.head) == 'E') + .get - def part1(in: String*): Long = 100 + // Map from a position in plan to the distance from the end on the original path. + val distance = path.zipWithIndex.toMap - def part2(in: String*): Long = 200 + // Given any position, find any cheat to another position that cause the distance to decrease. + def cheatsFrom(pos: Int): Seq[Int] = { + val d0 = distance(pos); + for ( + xTaxi <- -cheat to cheat; + x = pos % dx + xTaxi if x >= 1 && x < (dx - 1); + yTaxi <- -cheat to cheat; + y = pos / dx + yTaxi if y >= 1 && y < (dy - 1); + taxi = xTaxi.abs + yTaxi.abs if taxi < cheat; + newPos = y * dx + x if distance.contains(newPos); + saved = d0 - distance(newPos) - taxi if saved >= min + ) yield saved + } + + // Flatten the results into a count of how many shortcuts can save how much time. + path.flatMap(cheatsFrom).groupMapReduce(identity)(_ => 1)(_ + _).toSeq + } } import Solution._ describe("Example case") { + val input = - """ + """############### + |#...#...#.....# + |#.#.#.#.#.###.# + |#S#...#.#.#...# + |#######.#.#.### + |#######.#.#...# + |#######.#.###.# + |###..E#...#...# + |###.#######.### + |#...###...#...# + |#.#####.#.###.# + |#.#...#.#.#...# + |#.#.#.#.#.#.### + |#...#...#...### + |############### |""".trim.stripMargin.split("\n") it("should match the puzzle description for part 1") { - part1(input: _*) shouldBe 100 + val cheats = solve(3, 1, input: _*) + cheats.sorted should contain theSameElementsAs + Seq((2, 14), (4, 14), (6, 2), (8, 4), (10, 2), (12, 3), (20, 1), (36, 1), (38, 1), (40, 1), (64, 1)) } it("should match the puzzle description for part 2") { - part2(input: _*) shouldBe 200 + val cheats = solve(21, 1, input: _*) + cheats.sorted should contain allOf + ((50, 32), (52, 31), (54, 29), (56, 39), (58, 25), (60, 23), (62, 20), (64, 19), (66, 12), (68, 14), (70, 12), + (72, 22), (74, 4), (76, 3)) } } describe("๐Ÿ”‘ Solution ๐Ÿ”‘") { lazy val input = puzzleInput("Day20Input.txt") - lazy val answer1 = decryptLong("tTNGygZ0+O4PEH+5IiCrBw==") - lazy val answer2 = decryptLong("U9BZNCixKWAgOXNrGyDe5A==") + lazy val answer1 = decryptLong("oyB8B22n0/IeBp9K/tBX3w==") + lazy val answer2 = decryptLong("k5zhUo4QM+6+DZw0yo4ADw==") it("should have answers for part 1") { part1(input: _*) shouldBe answer1 } - it("should have answers for part 2") { + it("should have answers for part 2 (3s)", Slow) { part2(input: _*) shouldBe answer2 } }