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

day03_part1.py (3143B)


      1 #!/usr/bin/env python
      2 """For Day 3, we're parsing binary strings. Given a stream of text
      3 representations of binary numbers, we need to find the most common bit (MCB;
      4 and LCB, but that's a pretty simple next step)."""
      5 
      6 # Python has good bitwise operations, but finding the most common bit isn't a
      7 # common case. I'm going to split the string into one column per bit (because,
      8 # as always, I'm using pandas even if it's a slower than just writing simpler
      9 # code).
     10 
     11 from io import StringIO
     12 import numpy as np
     13 import pandas as pd
     14 from utils import get_puzzle_input
     15 
     16 def convert_input_to_df(input_string):
     17     """Given a string representation of the input data, return a single-column
     18     pandas data frame with the column `report`"""
     19     return pd.read_csv(StringIO(input_string),
     20                        names=('report',),
     21                        dtype={'report': str})
     22 
     23 
     24 def split_report_bits(report_df):
     25     """Given a pandas df with the column `report`, returns a new df with a
     26     column for each bit (as integers--and not booleans as perhaps they should
     27     be--in order to skip unnecessary type conversions)"""
     28     return (
     29         report_df['report']
     30         .str.split('', expand=True)
     31         .iloc[:, 1:-1]  # Drop the first and last (completely empty) columns
     32         .astype(np.int32, copy=False)
     33     )
     34 
     35 def find_mcb(bits_df):
     36     """Given a df with one column per bit position, return a Series with the
     37     most common bit per position (represented as booleans)"""
     38     # There's no guarantee in the puzzle description that a single value will
     39     # be most/least common, and with an even number of values, that's a problem
     40     if ((bits_means := bits_df.mean()) == 0.5).any():
     41         raise ValueError('Failed to identify a most common bit')
     42     return bits_means > 0.5
     43 
     44 def bit_series_to_int(bit_series):
     45     """Given a series of boolean values (representing bits), return an
     46     integer"""
     47     # I could have used python's string parsing for this but doing the math
     48     # seems more straightforawrd herea
     49     return sum(bit_series * 2 ** np.arange(len(bit_series) - 1, -1, -1))
     50 
     51 def calculate_gamma_epsilon(mcb_bit_series):
     52     """Given a Series representing the Most Common Bits, return a tuple
     53     containing the 'gamma rate' (the integer representation of the series) and
     54     the 'epsilon rate' (which is the integer representation of the least common
     55     bits, which is the dual of the most common bits)"""
     56     return (bit_series_to_int(mcb_bit_series),
     57             bit_series_to_int(~mcb_bit_series))
     58 
     59 def calculate_power(gamma, epsilon):
     60     """Return the product of the gamma and epsilon rates"""
     61     return gamma * epsilon
     62 
     63 def solve_puzzle(input_string):
     64     """Return the numeric solution to the puzzle"""
     65     # 5.47 ms ± 43.6 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
     66     return calculate_power(
     67         *calculate_gamma_epsilon(
     68             find_mcb(
     69                 split_report_bits(
     70                     convert_input_to_df(input_string)
     71                 )
     72             )
     73         )
     74     )
     75 
     76 if __name__ == "__main__":
     77     print("Power consumption parameter:",
     78           solve_puzzle(get_puzzle_input(3)))