Advent of Code 2024 - Day 2: Red-Nosed Reports

:: programming, python, puzzle

 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 :)