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:
A | day03_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)))