from misc.helper import verify_answer
= """
example_input 7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
"""
= 2 example_answer_one
Day 2: Red-Nosed Reports
The first place to search for the Chief Historian is the Red-Nosed Reindeer Nuclear Fusion/Fission Plant. While the historians scour the facility for clues, we are tasked with assisting the engineers in analyzing unusual data generated by the reactor.
The data is presented as a matrix of integers, where each row represents a separate report consisting of consecutive levels.
For the full details of the task, you can visit the official Advent of Code 2024 website.
To set the scene, I asked ChatGPT to generate an image of the plant. Here’s the result:
Part 1: Counting Safe Reports
Our first task is to analyze the reports and identify how many are safe. Each report is represented as a list of integers, where:
- A report is safe if all levels are either gradually increasing or gradually decreasing.
- The difference between consecutive levels must be at least 1 and at most 3.
We need to determine the number of safe reports in the data.
Example input:
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
Safety Analysis:
- Report 1 (7 6 4 2 1
): Safe. The levels are consistently decreasing with differences of 1
or 2
.
- Report 2 (1 2 7 8 9
): Unsafe. The jump from 2
to 7
is 5
.
- Report 3 (9 7 6 2 1
): Unsafe. The drop from 6
to 2
is 4
.
- Report 4 (1 3 2 4 5
): Unsafe. The sequence alternates between increasing and decreasing.
- Report 5 (8 6 4 4 1
): Unsafe. The repeated 4
violates the safety rule.
- Report 6 (1 3 6 7 9
): Safe. The levels are consistently increasing with differences within the valid range of 1-3
.
Result: Out of the six reports, 2 are safe.
First, I define the helper function to check the sign of a number.
def sign(x) -> int:
"""Return the sign of x"""
return (x > 0) - (x < 0)
Then, I’ll write a function to check if a report is safe by verifying if the differences between consecutive levels are within the valid range, and the levels are either consistently increasing or decreasing.
def test_sequence(report: list[int]) -> bool:
"""
Check if a sequence is valid based on safety rules.
A sequence is valid if it is strictly increasing or decreasing,
with consecutive differences in the range [1, 3].
"""
for l1, l2 in zip(report[:-1], report[1:]):
if abs(l1 - l2) > 3 or abs(l1 - l2) == 0:
return False
elif sign(l1 - l2) != sign(report[0] - report[1]):
return False
return True
Next, I’ll implement a function to read the reports from the input file, or a string (for easire testing).
from pathlib import Path
def read_input(input_source: str | Path) -> list[int]:
"""Read input file"""
if Path(input_source).is_file():
with open(input_source, "r") as f:
return [list(map(int, line.split())) for line in f]
else:
return [
list(map(int, seq.split()))
for seq in input_source.strip().split("\n")
]
And finally, I’ll write a function to put it all together and count the safe reports.
def part_one(input_source: str | Path):
"""Count the number of valid sequences"""
= read_input(input_source)
reports return sum(test_sequence(report) for report in reports)
verify_answer(
part_one,
example_input,
example_answer_one, )
✔️ That's right! The answer is 2.
Since the test is correct, I’ll run the function on the actual input data.
%time
"../inputs/day_02.txt") part_one(
CPU times: user 1 μs, sys: 0 ns, total: 1 μs
Wall time: 4.05 μs
526
That’s the right answer! You are one gold star ⭐ closer to finding the Chief Historian.
Part 2: Adjusting reports with the Problem Dampener
In this task, we expand the definition of a safe report to include those that can be made safe by removing up to one level from the sequence. This means that even if a report initially fails the safety criteria, it can still be considered safe if removing one level resolves the issue.
Example Input:
7 6 4 2 1
1 2 7 8 9
9 7 6 2 1
1 3 2 4 5
8 6 4 4 1
1 3 6 7 9
Adjusted Safety Analysis:
- Report 1 (7 6 4 2 1
): Safe. No changes needed.
- Report 2 (1 2 7 8 9
): Still unsafe. No single level can be removed to make it safe.
- Report 3 (9 7 6 2 1
): Still unsafe. No single level can be removed to make it safe.
- Report 4 (1 3 2 4 5
): Safe if the level 3
is removed, resulting in 1 2 4 5
.
- Report 5 (8 6 4 4 1
): Safe if the level 4
(third position) is removed, resulting in 8 6 4 1
.
- Report 6 (1 3 6 7 9
): Safe. No changes needed.
Result: With this adjustment, the number of safe reports increases from 2 to 4.
Key Changes:
- A Problem Dampener allows for the removal of a single problematic level to validate an otherwise unsafe report.
- This provides additional flexibility in assessing reactor data, ensuring that slightly flawed reports can still be deemed safe.
In this solution, I will need to modify the approach to test all possible subsequences for unsafe reports and check if removing one level makes them safe.
= 4
example_answer_two
def part_two(input_source: str | Path):
"""Count the number of valid sequences"""
= read_input(input_source)
reports = 0
safe for report in reports:
if test_sequence(report):
+= 1
safe else:
for i in range(len(report)):
if test_sequence(report[:i] + report[i + 1 :]):
+= 1
safe break
return safe
verify_answer(
part_two,
example_input,
example_answer_two, )
✔️ That's right! The answer is 4.
%time
"../inputs/day_02.txt") part_two(
CPU times: user 3 μs, sys: 0 ns, total: 3 μs
Wall time: 5.01 μs
566
That’s the right answer! You are one gold star ⭐ closer to finding the Chief Historian.