Skip to content

Commit 1157b36

Browse files
authored
Solution article for 2024 day 02 (#566)
* Solution article for 2024 day 02 * fix some typos
1 parent 758e94c commit 1157b36

File tree

1 file changed

+79
-0
lines changed

1 file changed

+79
-0
lines changed

docs/2024/puzzles/day02.md

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,85 @@ import Solver from "../../../../../website/src/components/Solver.js"
66

77
https://adventofcode.com/2024/day/2
88

9+
## Solution summary
10+
11+
- First we parse each line of the input into a `Report` case class: `case class Report(levels: Seq[Long])`
12+
- In each `Report`, we construct the sequence of consecutive pairs.
13+
- For part 1, we check if the pairs are all increasing or all decreasing, and if the difference is within the given limits (such a report is "safe").
14+
- For part 2, we construct new `Report`s obtained by dropping one entry in the original report, and check if there exists a safe `Report` among these.
15+
- In both parts, we simply count the number of `Report`s that are considered safe.
16+
17+
### Parsing
18+
19+
Each line of input is a string of numbers separated by a single space.
20+
Therefore parsing looks like this:
21+
22+
```scala
23+
case class Report(levels: Seq[Long])
24+
25+
def parseLine(line: String): Report = Report(line.split(" ").map(_.toLong).toSeq)
26+
27+
def parse(input: String): Seq[Report] = input
28+
.linesIterator
29+
.map(parseLine)
30+
.toSeq
31+
```
32+
33+
### Part 1: methods of the `Report` case class
34+
35+
We need to check consecutive pairs of numbers in each report in 3 ways:
36+
37+
- to see if they are all increasing,
38+
- to see if they are all decreasing,
39+
- to see if their differences are within given bounds.
40+
41+
So let's construct them only once, save it as a `val`, then reuse this value 3 times.
42+
It's not the most efficient way (like traversing only once and keeping track of everything),
43+
but it's very clean and simple:
44+
45+
```scala
46+
case class Report(levels: Seq[Long]):
47+
val pairs = levels.init.zip(levels.tail) // consecutive pairs
48+
def allIncr: Boolean = pairs.forall(_ < _)
49+
def allDecr: Boolean = pairs.forall(_ > _)
50+
def within(lower: Long, upper: Long): Boolean = pairs.forall: pair =>
51+
val diff = math.abs(pair._1 - pair._2)
52+
lower <= diff && diff <= upper
53+
def isSafe: Boolean = (allIncr || allDecr) && within(1L, 3L)
54+
```
55+
56+
Part 1 solver simply counts safe reports, so it looks like this:
57+
58+
```scala
59+
def part1(input: String): Int = parse(input).count(_.isSafe)
60+
```
61+
62+
### Part 2
63+
64+
Now we add new methods to `Report`.
65+
We check if there exists a `Report` obtained by dropping one number, such that it's safe.
66+
We do this by iterating over the index of each `Report`.
67+
Then, a `Report` is safe, if it's safe as in Part 1, or one of the dampened reports is safe:
68+
69+
```scala
70+
case class Report(levels: Seq[Long]):
71+
// ... as before
72+
def checkDampenedReports: Boolean = (0 until levels.size).exists: index =>
73+
val newLevels = levels.take(index) ++ levels.drop(index + 1)
74+
Report(newLevels).isSafe
75+
def isDampenedSafe: Boolean = isSafe || checkDampenedReports
76+
```
77+
78+
Again this is not the most efficient way (we are creating many new `Report` instances),
79+
but our puzzle inputs are fairly short (there are at most 8 levels in each `Report`),
80+
so it's a simple approach that reuses the `isSafe` method from Part 1.
81+
82+
Part 2 solver now counts the dampened safe reports:
83+
84+
```scala
85+
def part2(input: String): Int = parse(input).count(_.isDampenedSafe)
86+
```
87+
988
## Solutions from the community
1089

1190
- [Solution](https://github.com/rmarbeck/advent2024/tree/main/day2) by [Raphaël Marbeck](https://github.com/rmarbeck)

0 commit comments

Comments
 (0)