adventOfCode/challenges/2021/03-binaryDiagnostic/py/__init__.py
2021-12-03 14:30:28 +00:00

100 lines
2.9 KiB
Python

from typing import List, Tuple
from aocpy import BaseChallenge
def parse(instr: str) -> Tuple[List[int], int]:
# parse returns a list of integers and the original length of the first of
# those integers in the binary representation in the input.
y = instr.strip().splitlines()
return [int(x, base=2) for x in y], len(y[0])
def get_bit(number: int, n: int) -> int:
# get_bit returns a given bit from the binary representation of `number`.
#
# In the number 111001011110, n=4 would refer to this bit:
# ^
return (number >> n) & 0b1
def analyse_bits(numbers: List[int], n: int) -> Tuple[int, int]:
# analyse_bits returns a tuple containing the most common bit (either 1 or
# 0) and the least common bit (either 1 or 0) in position `n` of each
# number in `numbers`.
#
# If the most common bit and least common bit occur the same amount of
# times, both return values are -1.
#
# Returns: most common bit, least common bit
zeros = 0
ones = 0
for number in numbers:
sel = get_bit(number, n)
if sel == 0:
zeros += 1
elif sel == 1:
ones += 1
if zeros > ones:
return 0, 1
elif ones > zeros:
return 1, 0
return -1, -1
class Challenge(BaseChallenge):
@staticmethod
def one(instr: str) -> int:
numbers, bit_length = parse(instr)
gamma = ""
epsilon = ""
for bit_number in reversed(range(bit_length)):
most_common, least_common = analyse_bits(numbers, bit_number)
gamma += str(most_common)
epsilon += str(least_common)
return int(gamma, 2) * int(epsilon, 2)
@staticmethod
def two(instr: str) -> int:
numbers, bit_length = parse(instr)
def find(inp: List[int], use_most_common: bool, n: int = bit_length - 1) -> int:
# find implements the bit criteria-based filtering as defined in
# AoC 2021 day 3 part 2. If `use_most_common` is True, the bit
# criteria for the oxygen generator rating is used, else, the bit
# criteria for the CO2 scrubber rating is used.
if len(inp) == 1:
return inp[0]
most_common, least_common = analyse_bits(inp, n)
target = None
if most_common == -1 or least_common == -1:
target = 1 if use_most_common else 0
else:
target = most_common if use_most_common else least_common
# oooo, accidental tail recursion!
return find(
list(
filter(
lambda x: get_bit(x, n) == target,
inp,
)
),
use_most_common,
n - 1,
)
o2_rating = find(numbers, True)
co2_rating = find(numbers, False)
return o2_rating * co2_rating