Skip to content

Commit c46dc46

Browse files
committed
Solve puzzle 2 of day 3
1 parent c8e37a2 commit c46dc46

File tree

2 files changed

+119
-6
lines changed

2 files changed

+119
-6
lines changed

03/binary_diagnostic.py

Lines changed: 72 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,19 @@
11
#! /usr/bin/env python3
22
from pathlib import Path
3-
import math
3+
from typing import Callable
4+
45

56
def gamma_epsilon_product(report: "list[str]") -> int:
67
bin_digit_count = sum_bin_digits(report)
78
gamma = gamma_rate(bin_digit_count, len(report))
89
# epsilon rate is the unsigned complement, but because Python has two's
910
# complement representation, this isn't the simple unary bitwise negation
1011
# (~)
11-
epsilon = ~gamma & (2**(len(bin(gamma)) - 2) - 1)
12+
epsilon = invert_bin_num(gamma)
1213
return gamma * epsilon
1314

1415

15-
def gamma_rate(bit_counts: "list[int]", report_length : int) -> int:
16+
def gamma_rate(bit_counts: "list[int]", report_length: int) -> int:
1617
threshold = report_length // 2
1718
most_common_bit = ['1' if x > threshold else '0' for x in bit_counts]
1819
bin_string = '0b' + ''.join(most_common_bit)
@@ -27,6 +28,69 @@ def sum_bin_digits(bin_numbers: "list[str]") -> "list[int]":
2728
return bin_digit_count
2829

2930

31+
def invert_bin_num(bin_num: int) -> int:
32+
"""Invert a positive binary number (i.e. no consideration of two's complement implementation)
33+
34+
Args:
35+
bin_num (int): the binary number
36+
37+
Returns:
38+
int: the complement/inverse of bin_num
39+
"""
40+
return ~bin_num & (2**(len(bin(bin_num)) - 2) - 1)
41+
42+
43+
def bin_list_to_int(bin_list: "list[int]") -> int:
44+
bin_string = '0b' + ''.join([str(x) for x in bin_list])
45+
return int(bin_string, base=2)
46+
47+
48+
def filter_readings(report_section: "list[list[int]]", column: int,
49+
digit: int) -> "list[list[int]]":
50+
return list(filter(valid_reading_functor(column, digit), report_section))
51+
52+
53+
def valid_reading_functor(column: int, digit: int) -> Callable:
54+
def valid_reading(reading: "list[int]") -> bool:
55+
return reading[column] == digit
56+
57+
return valid_reading
58+
59+
60+
def most_common_digit(report_section: "list[list[int]]", column: int) -> int:
61+
sum = 0
62+
threshold = len(report_section) // 2
63+
even_length = len(report_section) % 2 == 0
64+
for reading in report_section:
65+
sum += reading[column]
66+
if sum == threshold and even_length:
67+
most_common_digit = 1
68+
elif sum > threshold:
69+
most_common_digit = 1
70+
else:
71+
most_common_digit = 0
72+
return most_common_digit
73+
74+
75+
def least_common_digit(report_section: "list[list[int]]", column: int) -> int:
76+
return invert_bin_num(most_common_digit(report_section, column))
77+
78+
79+
def find_unique_rating(report: "list[list[int]]", column: int, digit_selector: Callable) -> int:
80+
digit = digit_selector(report_section=report, column=column)
81+
report_subsection = filter_readings(report_section=report, column=column, digit=digit)
82+
if len(report_subsection) == 1:
83+
return report_subsection[0]
84+
else:
85+
return find_unique_rating(report=report_subsection, column=column + 1, digit_selector=digit_selector)
86+
87+
88+
def life_support_rating(report: "list[list[int]]") -> int:
89+
oxygen_generator_rating = find_unique_rating(report=report, column=0, digit_selector=most_common_digit)
90+
co2_scrubber_rating = find_unique_rating(report=report, column=0, digit_selector=least_common_digit)
91+
return bin_list_to_int(oxygen_generator_rating) * bin_list_to_int(co2_scrubber_rating)
92+
93+
3094
def puzzle_1(inputfile: Path) -> int:
3195
report = open(inputfile, 'r')
3296
with open(inputfile, 'r') as file:
@@ -35,13 +99,16 @@ def puzzle_1(inputfile: Path) -> int:
3599

36100

37101
def puzzle_2(inputfile: Path) -> int:
38-
pass
102+
report = open(inputfile, 'r')
103+
with open(inputfile, 'r') as file:
104+
report = [[int(digit) for digit in line.rstrip('\n')] for line in file]
105+
return life_support_rating(report)
39106

40107

41108
def main():
42109
inputfile = Path("puzzle_1_input.txt")
43110
print(f"Puzzle 1 Answer = {puzzle_1(inputfile)}")
44-
#print(f"Puzzle 2 Answer = {puzzle_2(inputfile)}")
111+
print(f"Puzzle 2 Answer = {puzzle_2(inputfile)}")
45112

46113

47114
if __name__ == "__main__":

03/test_binary_diagnostic.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
from binary_diagnostic import gamma_epsilon_product, gamma_rate, sum_bin_digits
2-
from binary_diagnostic import puzzle_1, Path
2+
from binary_diagnostic import puzzle_1, puzzle_2, Path
3+
from binary_diagnostic import valid_reading_functor, filter_readings
4+
from binary_diagnostic import most_common_digit, least_common_digit
5+
from binary_diagnostic import find_unique_rating, life_support_rating
36
import pytest
47

58

@@ -13,6 +16,13 @@ def bin_report():
1316
return ['00100', '11110', '10110', '10111', '10101', '01111', '00111',
1417
'11100', '10000', '11001', '00010', '01010']
1518

19+
@pytest.fixture
20+
def int_report(bin_report):
21+
readings = []
22+
for reading in bin_report:
23+
readings.append([int(x) for x in reading])
24+
return readings
25+
1626

1727
def test_gamma_rate(bin_digit_count):
1828
assert gamma_rate(bin_digit_count, 12) == 22
@@ -21,9 +31,45 @@ def test_gamma_rate(bin_digit_count):
2131
def test_sum_bin_digits(bin_report, bin_digit_count):
2232
assert sum_bin_digits(bin_report) == bin_digit_count
2333

34+
2435
def test_gamma_epsilon_product(bin_report):
2536
assert gamma_epsilon_product(bin_report) == 198
2637

2738

2839
def test_puzzle_1():
2940
assert puzzle_1(Path(__file__).parent / "puzzle_1_test_input.txt") == 198
41+
42+
43+
@pytest.mark.parametrize(("column", "digit"), [
44+
[0, 1], [1, 0], [2, 1], [3, 1], [4, 1]
45+
])
46+
def test_valid_reading_functor(column, digit):
47+
assert valid_reading_functor(column, digit)([1, 0, 1, 1, 1])
48+
49+
50+
def test_filter_readings(int_report):
51+
filtered_readings = [[1, 1, 1, 1, 0], [1, 0, 1, 1, 0], [1, 0, 1, 1, 1],
52+
[1, 0, 1, 0, 1], [1, 1, 1, 0, 0], [1, 0, 0, 0, 0],
53+
[1, 1, 0, 0, 1]]
54+
assert filter_readings(int_report, 0, 1) == filtered_readings
55+
56+
57+
def test_common_digit_selectors(int_report):
58+
assert most_common_digit(report_section=int_report, column=0) == 1
59+
assert least_common_digit(report_section=int_report, column=0) == 0
60+
split_report = [[0], [0], [0], [1], [1], [1]]
61+
assert most_common_digit(report_section=split_report, column=0) == 1
62+
assert least_common_digit(report_section=split_report, column=0) == 0
63+
64+
65+
def test_find_unique_rating(int_report):
66+
assert find_unique_rating(report=int_report, column=0, digit_selector=most_common_digit) == [1, 0, 1, 1, 1]
67+
assert find_unique_rating(report=int_report, column=0, digit_selector=least_common_digit) == [0, 1, 0, 1, 0]
68+
69+
70+
def test_life_support_rating(int_report):
71+
assert life_support_rating(report=int_report) == 230
72+
73+
74+
def test_puzzle_2():
75+
assert puzzle_2(Path(__file__).parent / "puzzle_1_test_input.txt") == 230

0 commit comments

Comments
 (0)