AOC 2024 Day Two: Red-Nosed Reports
Now we’re searching the Red-Nosed Reindeer’s nuclear plant. That sounds legit.
Problem | Solution |
Part One
We’re given a list of reports, one report per line, like this:
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
We have to determine if a report is safe, which is defined as:
- The numbers in the report are either all increasing or all decreasing;
- Adjacent numbers differ by at least one and no more than three.
First, let’s parse the input into a list of lists:
import sys
reports = []
input = sys.stdin.read()
for line in input.splitlines():
reports.append(list(map(int, line.strip().split())))
Now we need to check each of the reports to see if they’re safe. First, we need to figue out if they should all be increasing or all be decreasing. We can check the first two values for that, and then use the operator
module to store the comparison function we need to use. Then it’s a matter of checking each value against the previous one and making sure that the checks pass. The result is a little verbose in Python, but I can’t be bothered to make it more compact.
partone = 0
for report in reports:
# If the first two numbers are the same, we know the report isn't safe.
if report[0] == report[1]:
continue
# Otherwise, figure out if we're increasing or decreasing.
elif report[0] < report[1]:
op = operator.lt
else:
op = operator.gt
safe = True
for i in range(1, len(report)):
# If we're not moving in the right direction, it's not safe.
if not op(report[i - 1], report[i]):
safe = False
break
# If the difference is too big, it's not safe either.
diff = abs(report[i - 1] - report[i])
if diff == 0 or diff > 3:
safe = False
break
if safe:
partone += 1
print(partone)
Part Two
As is AoC’s wont, Part Two throws a wrench in the checks. Now, instead of any problem causing the report to be unsafe, a report is safe if any one value can be removed and the rest of the report is safe. Thankfully this doesn’t require much of a change compared to our original result–we can mostly package it up into a function and re-use it. However, it’s not quite straightforward, because the way we get the operator to compare all of the values with relies on the first two elements indicating the direction, but one of them might be the one to skip!
Since there are only five values in each report, it’s easy enough to generate all five possible reports, and see if any of them are safe.
def is_safe(report, *, skipping=False):
if skipping:
# Brute force all the possible combinations
return any(
is_safe(report[:i] + report[i + 1 :], skipping=False)
for i in range(len(report))
)
# The original implementation, more or less, packaged as a function.
# If the first two numbers are the same, we know the report isn't safe.
if report[0] == report[1]:
return False
# Otherwise, figure out if we're increasing or decreasing.
elif report[0] < report[1]:
op = operator.lt
else:
op = operator.gt
for i in range(1, len(report)):
# If we're not moving in the right direction, it's not safe.
if not op(report[i - 1], report[i]):
return False
# If the difference is too big, it's not safe either.
diff = abs(report[i - 1] - report[i])
if diff == 0 or diff > 3:
return False
return True
parttwo = sum(is_safe(r) for r in reports)
print(parttwo)
This was a fun little puzzle, and the twist was a nice taste of things to come.