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 319d828636c9f02a9c265fa471de4d3695e701ee
parent 80236e07f07f797b9d7f69036a16b193b8edb3ac
Author: Eamon Caddigan <eamon.caddigan@gmail.com>
Date:   Fri,  3 Dec 2021 11:40:01 -0500

Solution to day 3, part 1

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

diff --git a/day03_part1.py b/day03_part1.py @@ -0,0 +1,78 @@ +#!/usr/bin/env python +"""For Day 3, we're parsing binary strings. Given a stream of text +representations of binary numbers, we need to find the most common bit (MCB; +and LCB, but that's a pretty simple next step).""" + +# Python has good bitwise operations, but finding the most common bit isn't a +# common case. I'm going to split the string into one column per bit (because, +# as always, I'm using pandas even if it's a slower than just writing simpler +# code). + +from io import StringIO +import numpy as np +import pandas as pd +from utils import get_puzzle_input + +def convert_input_to_df(input_string): + """Given a string representation of the input data, return a single-column + pandas data frame with the column `report`""" + return pd.read_csv(StringIO(input_string), + names=('report',), + dtype={'report': str}) + + +def split_report_bits(report_df): + """Given a pandas df with the column `report`, returns a new df with a + column for each bit (as integers--and not booleans as perhaps they should + be--in order to skip unnecessary type conversions)""" + return ( + report_df['report'] + .str.split('', expand=True) + .iloc[:, 1:-1] # Drop the first and last (completely empty) columns + .astype(np.int32, copy=False) + ) + +def find_mcb(bits_df): + """Given a df with one column per bit position, return a Series with the + most common bit per position (represented as booleans)""" + # There's no guarantee in the puzzle description that a single value will + # be most/least common, and with an even number of values, that's a problem + if ((bits_means := bits_df.mean()) == 0.5).any(): + raise ValueError('Failed to identify a most common bit') + return bits_means > 0.5 + +def bit_series_to_int(bit_series): + """Given a series of boolean values (representing bits), return an + integer""" + # I could have used python's string parsing for this but doing the math + # seems more straightforawrd herea + return sum(bit_series * 2 ** np.arange(len(bit_series) - 1, -1, -1)) + +def calculate_gamma_epsilon(mcb_bit_series): + """Given a Series representing the Most Common Bits, return a tuple + containing the 'gamma rate' (the integer representation of the series) and + the 'epsilon rate' (which is the integer representation of the least common + bits, which is the dual of the most common bits)""" + return (bit_series_to_int(mcb_bit_series), + bit_series_to_int(~mcb_bit_series)) + +def calculate_power(gamma, epsilon): + """Return the product of the gamma and epsilon rates""" + return gamma * epsilon + +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_power( + *calculate_gamma_epsilon( + find_mcb( + split_report_bits( + convert_input_to_df(input_string) + ) + ) + ) + ) + +if __name__ == "__main__": + print("Power consumption parameter:", + solve_puzzle(get_puzzle_input(3)))