Advent of Code 2024-Day 2: Red-Nosed Reports
from advent import parse, ints, combinations
input = parse(2, ints)
def is_safe(t):
helper = lambda t: all(x < y <= (x + 3) for x, y in zip(t, t[1:]))
return helper(t) or helper(list(reversed(t)))
def is_dampened_safe(t):
return any(is_safe(t) for t in combinations(t, len(t) - 1))
def part1():
return sum([ 1 for t in input if is_safe(t) ])
def part2():
return sum([ 1 for t in input if is_safe(t) or is_dampened_safe(t) ])
# ---------------------------------------------------------------------------------------------
assert part1() == 510
assert part2() == 553
Parsing
The input for Day 2 consists of rows of space delimited integers. Here's the output from parse(2, ints):
----------------------------------------------------------------------------------------------------
day02.txt ➜ 19370 chars, 1000 lines; first 7 lines:
----------------------------------------------------------------------------------------------------
9 12 14 16 17 18 15
86 88 91 94 95 95
15 18 20 21 23 25 28 32
70 72 74 77 78 83
57 60 62 64 63 64 65
44 45 44 47 46
33 35 32 33 36 36
----------------------------------------------------------------------------------------------------
parse(2) ➜ 1000 entries:
----------------------------------------------------------------------------------------------------
((9, 12, 14, 16, 17, 18, 15), (86, 88, 91, 94, 95, 95), (15, 18, 20, 2 ... (66, 64, 63, 62, 59, 58))
----------------------------------------------------------------------------------------------------
So, we already have a tuple for each line to work with.
Part 1
The key element for part 1 is the is_safe() function on lines 5-12 to indicate whether the
tuple of levels is safe. To be safe, the levels need to be monotonically increasing with
intervals between levels no greater than 3 (either from left to right, or right to left).
zip(t, t[1:]) will produce a list of 2-tuples of successive pairs of integers, then we simply
check for safety via the expression x < y <= (x + 3). Rather than have specific logic
for the forward and reverse directions, we simply check the tuple and the reverse of the tuple.
Part 2
The key element for part 2 is we're now permitted to remove one level, and see if that makes
the remaining levels safe. We accomplish the removal of one level by computing the
len - 1 length combinations of the tuple, and then reuse our is_safe() function.
New Python Concepts
all()any()reversed()- chained boolean expressions
x < y < z - conditional in list comprehension
[ ... if pred() ] - generator expressions
(pred(x) for x in ...) - itertools.combinations
lambdalist()- sequence slicing
[:]
Conclusion
Another easy day, and I'm pleased with Python's pragmatism for this sort of thing :)