Advent of Code 2021: Day 3

Advent of Code 2021 - Day 3

Day 3 puzzle description

Day 3 puzzle description can be found on my Github.

My solution in Python

from collections import Counter
from functools import partial
from typing import List

def binary_str_to_int(s: str) -> int:
return int(s, 2)

def get_data() -> List[str]:
with open("aoc-day-3-input.txt", "rt") as f:
data = [x.replace("\n", "") for x in f.readlines()]
return data

def solution(input_data: List[str]) -> int:
# sanity check for input data - all numbers must be of same length
assert len(set([len(line) for line in input_data])) == 1  # noqa

num_length = len(input_data)

gamma_rate_str = ""
epsilon_rate_str = ""

for i in range(num_length):
most_common = Counter([line[i] for line in input_data]).most_common()
gamma_rate_str += most_common
epsilon_rate_str += most_common

gamma_rate = binary_str_to_int(gamma_rate_str)
epsilon_rate_str = binary_str_to_int(epsilon_rate_str)

return gamma_rate * epsilon_rate_str

def get_life_support_rating(nums: List[str]) -> int:
# trying to use more functional programming style
def generic_bit_criteria(mc_index: int, default: str, c: Counter) -> str:
mc = c.most_common()
if mc == mc:
return default
return mc[mc_index]

oxygen_generator_bit_criteria = partial(generic_bit_criteria, 0, "1")
co2_scrubber_bit_criteria = partial(generic_bit_criteria, -1, "0")

def calculate_rate_by_bit_criteria(
numbers: List[str], bit_criteria, ix: int = 0
) -> str:
if len(numbers) == 1:
return numbers
dominating_bit = bit_criteria(Counter([n[ix] for n in numbers]))
filtered_numbers = list(filter(lambda x: x[ix] == dominating_bit, numbers))
return calculate_rate_by_bit_criteria(filtered_numbers, bit_criteria, ix + 1)

oxygen_rate = binary_str_to_int(
calculate_rate_by_bit_criteria(nums, oxygen_generator_bit_criteria)
)
co2_scrubber_rate = binary_str_to_int(
calculate_rate_by_bit_criteria(nums, co2_scrubber_bit_criteria)
)

return oxygen_rate * co2_scrubber_rate

if __name__ == "__main__":

input_data = get_data()
print(solution(input_data))
print(get_life_support_rating(test_data))
print(get_life_support_rating(input_data))

Quick recap

I used collections.Counter to counter frequency of 0s and 1s. Its API exposes most_common() method which made calculating most/least common bit very easy.

Part 2 solution was inspired a bit by a book that I'm currently reading: Functional Programming Simplified by Alvin Alexander - I used partial from functools module - it allowed me to take common logic of oxygen and CO2 scrubber rate to one generic function and then create their actual implementions with partial.

Other functional approach I took was to use recursion (in calculate_rate_by_bit_criteria). It was very satisfying since switching traditional for loops for recursive algorithms feels still unintuitive to me.

And that's it for day 3 of Advent of Code 2021.

Take care :-),
Kuba

Thanks for reading the article, I really appreciate it! Have you heard about Braintrust - the first decentralized talent network? Whether you're a freelancer looking for a job, an employer looking for hiring talents, or you just have a wide network of connections - there's something for you there!

Go check it out and register with below link (yeah - it's my referral link and it's free - no hidden costs):