advent_of_code_2021

My attempts to work through the 2021 Advent of Code problems.
git clone https://git.eamoncaddigan.net/advent_of_code_2021.git
Log | Files | Refs | README | LICENSE

commit 5cdf049c8cef9b85f2fe770d010fd681632297f8
parent 319d828636c9f02a9c265fa471de4d3695e701ee
Author: Eamon Caddigan <eamon.caddigan@gmail.com>
Date:   Fri,  3 Dec 2021 15:48:17 -0500

Solution to day 3, part 2

Diffstat:
Aday03_part2.py | 67+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 67 insertions(+), 0 deletions(-)

diff --git a/day03_part2.py b/day03_part2.py @@ -0,0 +1,67 @@ +#!/usr/bin/env python +"""Get the 'life support rating' by finding the 'oxygen generator rating' and +'CO2 scrubber tating' from the same stream of binary numbers used for part +1.""" + +import functools +from utils import get_puzzle_input +from day03_part1 import (convert_input_to_df, + split_report_bits, + bit_series_to_int) + +def filter_by_bit_commonality_at(bits_df, column_label, least_common=False): + """Helper function: return the rows for which the given `column_label` + has the most common bit (or least common). Ties go to the `1`s for most + common (`0` for least common)""" + # If we select all rows for which the value of the given column equals + # the integer value of the boolean corresponding to the test of whether + # twice the sum of the column is greater than or equal to the column + # length, we'll follow the rules for selecting the most common bit, and + # if we invert that, the least common + if len(bits_df) == 1: + return bits_df + rows_to_get = bits_df.loc[:, column_label] == \ + int(2 * bits_df.loc[:, column_label].sum() >= bits_df.loc[:, 1].size) + if least_common: + rows_to_get = ~rows_to_get + return bits_df.loc[rows_to_get] + +def filter_by_bit_commonality(bits_df, least_common=False): + """Filter the df from left to right, selecting only rows that have the most + common bit (or least common, if `least_common` == `True`), until only a + single row remains, which is returned as a Series""" + # Using `reduce` instead of writing a loop prevents us from stopping as + # early as possible (with a break) and from checking that we have a unique + # solution, but it's just so CUTE I'm sorry + return functools.reduce( + lambda x, y: filter_by_bit_commonality_at(x, y, least_common), + bits_df.columns, + bits_df + ).iloc[0] + +def calculate_oxygen_co2(bits_df): + """Given a df find the "oxygen generator rating" and "CO2 scrubber rating" + by successively filtering the df by the most (least) common bit from left + to right, and return the values as a tuple""" + return (bit_series_to_int(filter_by_bit_commonality(bits_df, False)), + bit_series_to_int(filter_by_bit_commonality(bits_df, True))) + +def calculate_life_support_rating(oxygen_generator_rating, co2_scrubber_rating): + """Return the product of the oxygen generator rating and co2 scrubber + rating""" + return oxygen_generator_rating * co2_scrubber_rating + +def solve_puzzle(input_string): + """Return the numeric solution to the puzzle""" + # 5.47 ms ± 43.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) + return calculate_life_support_rating( + *calculate_oxygen_co2( + split_report_bits( + convert_input_to_df(input_string) + ) + ) + ) + +if __name__ == "__main__": + print("Life support rating:", + solve_puzzle(get_puzzle_input(3)))