diff --git a/docs/2024/puzzles/day14.md b/docs/2024/puzzles/day14.md index 2fac9c10e..caf439def 100644 --- a/docs/2024/puzzles/day14.md +++ b/docs/2024/puzzles/day14.md @@ -1,11 +1,315 @@ import Solver from "../../../../../website/src/components/Solver.js" # Day 14: Restroom Redoubt +by [Bulby](https://github.com/TheDrawingCoder-Gamer) + ## Puzzle description https://adventofcode.com/2024/day/14 +## Solution Summary + +1. Parse input into a `List[Robot]` +2. Make function to advance state by `n` steps +3. Solve + * For `part1`, this is advancing the state by 100 then calculating the safety score + * For `part2`, this is finding the first state where a christmas tree is visible + +## Part 1 + +Part 1 shouldn't be too bad. Let's get started with our `Robot` class (and a `Vec2i` class): + +```scala +case class Vec2i(x: Int, y: Int) + +case class Robot(pos: Vec2i, velocity: Vec2i) +``` + + +Now we can parse our input: + +```scala +def parse(str: String): List[Robot] = + str.linesIterator.map: + case s"p=$px,$py v=$vx,$vy" => + Robot(Vec2i(px.toInt, py.toInt), Vec2i(vx.toInt, vy.toInt)) + .toList +``` + +Let's define our grid size in a `val`, as it can depend on if we are doing a test input or not: + +```scala +val size = Vec2i(101, 103) +``` + +The problem text states that when a robot goes off the edge it comes back on the other side, which sounds a lot like modulo. +Unfortunately, modulo is incorrect in our case for negative numbers. Let's define a remainder instead: + +```scala +extension (self: Int) + infix def rem(that: Int): Int = + val m = math.abs(self) % that + if self < 0 then + that - m + else + m +``` + +Now we can add a function to `Robot` that advances the state by `n`: + +```scala +case class Robot(pos: Vec2i, velocity: Vec2i): + def stepN(n: Int = 1): Robot = + copy(pos = pos.copy(x = (pos.x + n * velocity.x) rem size.x, y = (pos.y + n * velocity.y) rem size.y)) +``` + +Now for the full `List[Robot]`, we can add `stepN` to that too, and also define our safety function: + +```scala +extension (robots: List[Robot]) + def stepN(n: Int = 1): List[Robot] = robots.map(_.stepN(n)) + + def safety: Int = + val middleX = size.x / 2 + val middleY = size.y / 2 + + robots.groupBy: robot => + (robot.pos.x.compareTo(middleX), robot.pos.y.compareTo(middleY)) match + case (0, _) | (_, 0) => -1 + case ( 1, -1) => 0 + case (-1, -1) => 1 + case (-1, 1) => 2 + case ( 1, 1) => 3 + .removed(-1).values.map(_.length).product +``` + +Let's explain this a little. There are 4 quadrants, and as specified there are also lines that aren't in any quadrant. +We can use `groupBy` to group the robots into quadrants. + +First, we get the midpoints by dividing the size by 2. We then compare the robot to the midpoint, returning `-1` if it's on the line +(either comparison is equal), and otherwise sort the remaining 4 results into quadrants. We then remove the robots on the line, +get the length of each of the lists, and multiply them together. + +With this `part1` is easy to implement: + +```scala +def part1(input: String): Int = parse(input).stepN(100).safety +``` + +## Part 2 + +Part 2 wants us to find an image which is really hard. Thankfully, there is one thing I know about Christmas trees: They have +a lot of lines in a row and are an organized shape. + +We are assuming here that the tree is solid. This assumption is fine in this case, the space to search is finite +so the worst we can get is a "not found" answer, but in other problems we may want to be more sure about our input. + +The christmas trees I know are really tall, but only in a few columns. So let's test the vertical columns for a few lines that are really long. +They also have a lot of shorter horizontal lines, so let's also check the rows for a lot of shorter lines. + +Let's also inspect the input more: the grid size is 101 wide and 103 tall. If we've moved vertically 103 times, then we've moved a multiple +of 103 and are thus back at the start. The same logic applies for horizontal movement, but with 101 instead. This means a robot can, at most, be in +101 * 103 unique positions, or 10,403, and because all robots are moved at the same time there will only ever be 10,403 unique states. This lets us +fail fast while writing our code in case we messed something up. + + +This is arbitrary and only really possible by print debugging your code, but here's my final code: + +```scala +extension (robots: List[Robot]) + def findEasterEgg: Int = + (0 to (size.x * size.y)).find: i => + val newRobots = robots.stepN(i) + newRobots.groupBy(_.pos.y).count(_._2.length >= 10) > 15 && newRobots.groupBy(_.pos.x).count(_._2.length >= 15) >= 3 + .getOrElse(-1) +``` + +We don't even need to check if the lines are contiguous - our test is strict enough with counting lines that it works regardless. Results may vary +on your input. Here I test if there are more than 15 horizontal lines with a length of 10 or more, and that there are 3 or more vertical lines +with length 15 or greater. + +Then let's hook it up to `part2`: + +```scala +def part2(input: String): Int = parse(input).findEasterEgg +``` + +My Christmas tree looks like this: + +``` +...................................................................................................... +.................................................#.................................................... +..............................................#....................................................... +................#.#............................#...................................................... +.................................................................................#.................... +................................................................................................#..... +..#................................................................................................... +.....#........................................................................................#....... +.#.................................................................................................... +...................................................................................................... +...........................................................#...........#........#..................... +.........#..........................................#.........#....................................... +..............................................#........#.............................................. +.......................................#..........#................................................... +...................................................................................#.............#.... +............................................................#......................................... +.................#.......................#.............................#.............................. +.............................#........................................................................ +.................................#...............................#.........................#.......... +.........#........................#..........................................#........................ +...................................................................................................... +.............................#........................................................................ +.......#.............................#....#........................................................... +...#..............................................................#......................#............ +..........................#............................................#.............................. +.......................................................................................#.............. +..............................................#....................................................... +.............#.......................................................................#................ +............#.................................#.....#.......................................#......... +...................................................................................................... +................###############################....................................................... +................#.............................#....................................................... +................#.............................#.....#........................#........................ +................#.............................#.................#..................................... +................#.............................#....................................................... +................#..............#..............#....................................................... +................#.............###.............#....#.........................................#........ +................#............#####............#....................................................... +................#...........#######...........#....................................................... +................#..........#########..........#....................................................... +......#.........#............#####............#......................................................# +................#...........#######...........#....................................................... +................#..........#########..........#....................#...............................#.. +................#.........###########.........#........................................#.............. +...#......#.....#........#############........#..#.................................................... +................#..........#########..........#..............#.....................................#.. +................#.........###########.........#....................................................... +................#........#############........#............................................#.......... +................#.......###############.......#....................................................... +..........#.....#......#################......#....................................................... +................#........#############........#..........................#.........#.................. +................#.......###############.......#....................................................#.. +........#.......#......#################......#......................................................# +................#.....###################.....#....................................................... +................#....#####################....#..#.................................................... +................#.............###.............#..........#............................................ +................#.............###.............#......................#................................ +................#.............###.............#..........................#............................ +................#.............................#.......................#............................... +................#.............................#....................................#.................. +................#.............................#......................................#................ +......#.........#.............................#....................................................... +................###############################............................#.......................... +...................................................................................................... +...............#...........................................#.......................................... +.........................................#............................................................ +...................#.........................................................#........................ +.....................................#.............................................................#.. +...........................#....................#..................................................... +..........................................................#...........................#............... +...................................................................................................... +..#............#................#............................................#........................ +........................................#..#.......................................................... +...........#......................................................................#................... +............#..........#...............................................................#.#............ +............................................................#......................................... +...#......................................................................#........................... +.....#................................................................................................ +...................................................................................................... +...................................................................................................... +............................................#......................................#.................. +...............#............................................................#......................... +........................................#............................................................. +............#..#...................................................................................... +...........#.............#.................#.......................................................... +................................#..................................................................... +.........#.................................#.......................................................... +.......................#............................................................................#. +...............#...................................................................................... +..............................#.#..................................................................... +...................................................................................................... +...................................................................................................... +...........#.......................................................................................... +.........................................................................................#............ +..........................#........................................................................... +...............................#.......................#.............................................. +...............#...................................................................................... +...........#...............................#.......................................................... +.................#.................#............................................#...#................. +...................................................................................................#.. +...............................................................................#...................... +....#...................................................................#............................. +........#...................#.....................#................................................... +................................................................................#..................... +``` + +With this information of how this looks we could make a smarter `findEasterEgg` with the knowledge of the border. The border +makes it much easier as we only have to check for 2 contiguous lines in each dimension. + +## Final Code + +```scala +case class Vec2i(x: Int, y: Int) + +val size = Vec2i(101, 103) + +extension (self: Int) + infix def rem(that: Int): Int = + val m = math.abs(self) % that + if self < 0 then + that - m + else + m + +case class Robot(pos: Vec2i, velocity: Vec2i) + def stepN(n: Int = 1): Robot = + copy(pos = pos.copy(x = (pos.x + n * velocity.x) rem size.x, y = (pos.y + n * velocity.y) rem size.y)) + +def parse(str: String): List[Robot] = + str.linesIterator.map: + case s"p=$px,$py v=$vx,$vy" => + Robot(Vec2i(px.toInt, py.toInt), Vec2i(vx.toInt, vy.toInt)) + .toList + +extension (robots: List[Robot]) + def stepN(n: Int = 1): List[Robot] = robots.map(_.stepN(n)) + + def safety: Int = + val middleX = size.x / 2 + val middleY = size.y / 2 + + robots.groupBy: robot => + (robot.pos.x.compareTo(middleX), robot.pos.y.compareTo(middleY)) match + case (0, _) | (_, 0) => -1 + case ( 1, -1) => 0 + case (-1, -1) => 1 + case (-1, 1) => 2 + case ( 1, 1) => 3 + .removed(-1).values.map(_.length).product + + def findEasterEgg: Int = + (0 to 10403).find: i => + val newRobots = robots.stepN(i) + newRobots.groupBy(_.pos.y).count(_._2.length >= 10) > 15 && newRobots.groupBy(_.pos.x).count(_._2.length >= 15) >= 3 + .getOrElse(-1) + +def part1(input: String): Int = parse(input).stepN(100).safety + +def part2(input: String): Int = parse(input).findEasterEgg +``` + +## Run it in the browser + +### Part 1 + + + +### Part 2 + + + + ## Solutions from the community - [Solution](https://github.com/nikiforo/aoc24/blob/main/src/main/scala/io/github/nikiforo/aoc24/D14T2.scala) by [Artem Nikiforov](https://github.com/nikiforo) @@ -16,7 +320,6 @@ https://adventofcode.com/2024/day/14 - [Solution](https://github.com/spamegg1/aoc/blob/master/2024/14/14.scala#L165) by [Spamegg](https://github.com/spamegg1) - [Solution](https://github.com/jnclt/adventofcode2024/blob/main/day14/restroom-redoubt.sc) by [jnclt](https://github.com/jnclt) - [Solution](https://github.com/Philippus/adventofcode/blob/main/src/main/scala/adventofcode2024/Day14.scala) by [Philippus Baalman](https://github.com/philippus) -- [Writeup](https://thedrawingcoder-gamer.github.io/aoc-writeups/2024/day14.html) by [Bulby](https://github.com/TheDrawingCoder-Gamer) - [Solution](https://github.com/jportway/advent2024/blob/master/src/main/scala/Day14.scala) by [Joshua Portway](https://github.com/jportway) - [Solution](https://github.com/AvaPL/Advent-of-Code-2024/tree/main/src/main/scala/day14) by [Paweł Cembaluk](https://github.com/AvaPL) diff --git a/solver/src/main/scala/adventofcode/Solver.scala b/solver/src/main/scala/adventofcode/Solver.scala index 2705c7a0e..2cdba0801 100644 --- a/solver/src/main/scala/adventofcode/Solver.scala +++ b/solver/src/main/scala/adventofcode/Solver.scala @@ -11,6 +11,8 @@ object Solver: Map( "day13-part1" -> day13.part1, "day13-part2" -> day13.part2, + "day14-part1" -> day14.part1, + "day14-part2" -> day14.part2, "day21-part1" -> day21.part1, "day21-part2" -> day21.part2, "day22-part1" -> day22.part1,