This repository has been archived by the owner on Dec 31, 2021. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
__init__.py
105 lines (96 loc) · 4.61 KB
/
__init__.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
"""--- Day 3: Binary Diagnostic ---"""
from dataclasses import dataclass
from pathlib import Path
from aoc import open_utf8
@dataclass
class DiagnosticRates:
"""Object to hold the computed rates for a dataset."""
diagnostic_data: list # The integer data to include in the rates calculations.
num_bits: int # The number of bits used in the input diagnostic data
gamma: int = None
epsilon: int = None
o2: int = None
co2: int = None
def __init__(self, dataset_path: Path):
with open_utf8(dataset_path) as file:
self.num_bits = len(file.readline().strip())
file.seek(0) # Just peek at the data for computing bytes
self.diagnostic_data = [int(line.rstrip(), 2) for line in file]
self.__process_diagnostics()
self.__compute_epsilon_rate()
def __process_diagnostics(self):
"""Iterates the diagnostic data to compute summary rates."""
gamma_bits = "" # tracking the final bit result for gamma rate
o2_inclusions = list(range(len(self.diagnostic_data)))
co2_inclusions = list(range(len(self.diagnostic_data)))
for bit_index in range(
self.num_bits
): # Iterate bits, note bitmasks are from MSB
gamma_frequency = 0 # track the number of high bits for gamma calculation
o2_frequency = 0
co2_frequency = 0
for data_index, line in enumerate(self.diagnostic_data):
bit_value = line >> (self.num_bits - bit_index - 1) & 0b1
if len(o2_inclusions) > 1 and data_index in o2_inclusions:
o2_frequency += bit_value
if len(co2_inclusions) > 1 and data_index in co2_inclusions:
co2_frequency += bit_value
gamma_frequency += bit_value
gamma_bits += (
"1" if gamma_frequency >= len(self.diagnostic_data) / 2 else "0"
)
o2_inclusions = self.__evaluate_bit_criteria(
o2_inclusions, o2_frequency, bit_index, True
)
co2_inclusions = self.__evaluate_bit_criteria(
co2_inclusions, co2_frequency, bit_index, False
)
if 1 < len(o2_inclusions) < 1 or 1 < len(co2_inclusions) < 1:
raise RuntimeError("Couldn't resolve o2 or co2 rate to a valid value.")
self.o2 = self.diagnostic_data[o2_inclusions[0]]
self.co2 = self.diagnostic_data[co2_inclusions[0]]
self.gamma = int(gamma_bits, 2) # convert the gamma bits to int
def __evaluate_bit_criteria(
self, inclusion_indices, frequency, bit_index, most
) -> []:
"""Helper function to identify drop indexes that no longer meet bit
criteria.
:param inclusion_indices: The prior inclusion indices.
:param frequency: The high bit frequency for the bit position.
:param bit_index: The column of bits being evaluated.
:param most: True if we are evaluating for most popularity.
:return: The posterior inclusion indices.
"""
if len(inclusion_indices) > 1: # Then we need to keep evaluating
high_comm = frequency >= len(inclusion_indices) / 2 # Is 1 more common
bitmask = 2 ** (self.num_bits - bit_index - 1)
inclusion_indices = [ # Reduce inclusion indices
data_index
for data_index in inclusion_indices
if ( # Include on if match on current bit popularity
(
most # o2 rate
and (
high_comm # should be hi
and self.diagnostic_data[data_index] & bitmask > 0
or not high_comm # should be lo
and self.diagnostic_data[data_index] & bitmask == 0
)
)
or (
not most # co2 rate
and (
high_comm # should be lo
and self.diagnostic_data[data_index] & bitmask == 0
or not high_comm # should be hi
and self.diagnostic_data[data_index] & bitmask > 0
)
)
)
]
return inclusion_indices
# Pylint not understanding the type of gamma is already checked.
# pylint: disable=E1130
def __compute_epsilon_rate(self):
"""Computes the epsilon rate as the complement of the gamma rate."""
self.epsilon = (~self.gamma) & (2 ** self.num_bits - 1) if self.gamma else None