Skip to content

Commit

Permalink
day 14 article (#822)
Browse files Browse the repository at this point in the history
* day 14 article

* add browser support

* make size more explicit

* Specify that tree being solid is assumption

* inline -> contiguous
  • Loading branch information
TheDrawingCoder-Gamer authored Jan 2, 2025
1 parent c9682d6 commit 6105295
Show file tree
Hide file tree
Showing 2 changed files with 306 additions and 1 deletion.
305 changes: 304 additions & 1 deletion docs/2024/puzzles/day14.md
Original file line number Diff line number Diff line change
@@ -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

<Solver puzzle="day14-part1" year="2024"/>

### Part 2

<Solver puzzle="day14-part2" year="2024"/>


## 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)
Expand All @@ -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)

Expand Down
2 changes: 2 additions & 0 deletions solver/src/main/scala/adventofcode/Solver.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down

0 comments on commit 6105295

Please sign in to comment.