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

```commit 5cdf049c8cef9b85f2fe770d010fd681632297f8
parent 319d828636c9f02a9c265fa471de4d3695e701ee
Date:   Fri,  3 Dec 2021 15:48:17 -0500

Solution to day 3, part 2

Diffstat:
```
```1 file changed, 67 insertions(+), 0 deletions(-)
diff --git a/day03_part2.py b/day03_part2.py
@@ -0,0 +1,67 @@
+#!/usr/bin/env python
+"""Get the 'life support rating' by finding the 'oxygen generator rating' and
+'CO2 scrubber tating' from the same stream of binary numbers used for part
+1."""
+
+import functools
+from utils import get_puzzle_input
+from day03_part1 import (convert_input_to_df,
+                         split_report_bits,
+                         bit_series_to_int)
+
+def filter_by_bit_commonality_at(bits_df, column_label, least_common=False):
+    """Helper function: return the rows for which the given `column_label`
+    has the most common bit (or least common). Ties go to the `1`s for most
+    common (`0` for least common)"""
+    # If we select all rows for which the value of the given column equals
+    # the integer value of the boolean corresponding to the test of whether
+    # twice the sum of the column is greater than or equal to the column
+    # length, we'll follow the rules for selecting the most common bit, and
+    # if we invert that, the least common
+    if len(bits_df) == 1:
+        return bits_df
+    rows_to_get = bits_df.loc[:, column_label] == \
+        int(2 * bits_df.loc[:, column_label].sum() >= bits_df.loc[:, 1].size)
+    if least_common:
+        rows_to_get = ~rows_to_get
+    return bits_df.loc[rows_to_get]
+
+def filter_by_bit_commonality(bits_df, least_common=False):
+    """Filter the df from left to right, selecting only rows that have the most
+    common bit (or least common, if `least_common` == `True`), until only a
+    single row remains, which is returned as a Series"""
+    # Using `reduce` instead of writing a loop prevents us from stopping as
+    # early as possible (with a break) and from checking that we have a unique
+    # solution, but it's just so CUTE I'm sorry
+    return functools.reduce(
+        lambda x, y: filter_by_bit_commonality_at(x, y, least_common),
+        bits_df.columns,
+        bits_df
+    ).iloc
+
+def calculate_oxygen_co2(bits_df):
+    """Given a df find the "oxygen generator rating" and "CO2 scrubber rating"
+    by successively filtering the df by the most (least) common bit from left
+    to right, and return the values as a tuple"""
+    return (bit_series_to_int(filter_by_bit_commonality(bits_df, False)),
+            bit_series_to_int(filter_by_bit_commonality(bits_df, True)))
+
+def calculate_life_support_rating(oxygen_generator_rating, co2_scrubber_rating):
+    """Return the product of the oxygen generator rating and co2 scrubber
+    rating"""
+    return oxygen_generator_rating * co2_scrubber_rating
+
+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_life_support_rating(
+        *calculate_oxygen_co2(
+            split_report_bits(
+                convert_input_to_df(input_string)
+            )
+        )
+    )
+
+if __name__ == "__main__":
+    print("Life support rating:",
+          solve_puzzle(get_puzzle_input(3)))
```