My attempts to work through the 2021 Advent of Code problems.

```commit 319d828636c9f02a9c265fa471de4d3695e701ee
parent 80236e07f07f797b9d7f69036a16b193b8edb3ac
Date:   Fri,  3 Dec 2021 11:40:01 -0500

Solution to day 3, part 1

Diffstat:
```
```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`"""
+                       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)))
```