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)))