Advent of Code 2024 - Day 2: Red-Nosed Reports
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
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
lambda
list()
- sequence slicing
[:]
Conclusion
Another easy day, and I’m pleased with Python’s pragmatism for this sort of thing :)