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
"""
example_answer_one = 2Day 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 TrueNext, 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"""
reports = read_input(input_source)
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
part_one("../inputs/day_02.txt")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.
example_answer_two = 4
def part_two(input_source: str | Path):
"""Count the number of valid sequences"""
reports = read_input(input_source)
safe = 0
for report in reports:
if test_sequence(report):
safe += 1
else:
for i in range(len(report)):
if test_sequence(report[:i] + report[i + 1 :]):
safe += 1
break
return safe
verify_answer(
part_two,
example_input,
example_answer_two,
)✔️ That's right! The answer is 4.
%time
part_two("../inputs/day_02.txt")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.