Binary Diagnostic
Part 1
… is just a simple idiom/expression exercise, count the number of '1'
per
column in a series of rows (lines) of text.
This task inspired the rant Python Is Slow.
aoc = __import__('aoc-common').aoc()
inp = aoc.input_text(3)
from collections import Counter
lines = inp.split()
ncolumns = len(lines[0])
one_counts = [r.count('1') for r in zip(*lines)]
gam = int(''.join(['01'[x >= len(lines)//2] for x in one_counts]), 2)
eps = int(''.join(['01'[x <= len(lines)//2] for x in one_counts]), 2)
print(f'part 1 answer: {gam*eps}')
Part 2
… is verging on being algorithmic. Winnow down the lines iteratively based on some criteria of this column count until one remains.
Standard operating procedure, the tricky part is figuring out the best way
to generalize the two operations (selecting min/max by column) into a single
action. This kind of stuff is often a pain point in imperative-like languages:
abstracting over some kind of “direction.” Often you end with ugly hacks like
step = -1 or +1
or even explicitly with enums like FORWARD
, NORTH
, and so
on. Or, in the case below, this ugly which
argument.
Note also there’s no guarantee in the input that this process will end before we run out of columns, but I’ll pass that off as a problem error.
from enum import Enum
class Dir(Enum):
BIGGER = 0
SMALLER = 1
nums = [int(x, 2) for x in inp.split()]
def recursive_divide(lst, which, idx=ncolumns):
assert lst and idx >= 0
if len(lst) <= 1:
return lst[0]
sep = [[], []]
for x in lst:
sep[1 & x >> idx - 1].append(x)
return recursive_divide(
sep[which.value] if len(sep[0]) <= len(sep[1]) else sep[1^which.value],
which,
idx - 1)
oxy = recursive_divide(nums, Dir.BIGGER)
co2 = recursive_divide(nums, Dir.SMALLER)
print(f'part 2 answer: {oxy*co2}')