"""
|
|
Advent of Code 2021 - Day 08
|
|
|
|
Run with:
|
|
python puzzles/day08.py inputs/day08.txt
|
|
"""
|
|
|
|
import pathlib
|
|
import sys
|
|
from collections import defaultdict
|
|
from typing import List, Tuple
|
|
|
|
|
|
class Display:
|
|
"""
|
|
0 (6) 1 (2*) 2 (5) 3 (5) 4 (4*)
|
|
aaaa .... aaaa aaaa ....
|
|
b c . c . c . c b c
|
|
b c . c . c . c b c
|
|
.... .... dddd dddd dddd
|
|
e f . f e . . f . f
|
|
e f . f e . . f . f
|
|
gggg .... gggg gggg ....
|
|
|
|
5 (5) 6 (6) 7 (3*) 8 (7*) 9 (6)
|
|
aaaa aaaa aaaa aaaa aaaa
|
|
b . b . . c b c b c
|
|
b . b . . c b c b c
|
|
dddd dddd .... dddd dddd
|
|
. f e f . f e f . f
|
|
. f e f . f e f . f
|
|
gggg gggg .... gggg gggg
|
|
"""
|
|
|
|
def __init__(self, entries: List[str]):
|
|
self.entries = self._parse(entries)
|
|
|
|
def decode(self):
|
|
results = []
|
|
|
|
for entry in self.entries:
|
|
cipher = self._generate_cipher(entry["signals"])
|
|
decrypt = self._decrypt(cipher, entry["outputs"])
|
|
results.append(decrypt)
|
|
|
|
return sum(results)
|
|
|
|
def unique_count(self):
|
|
unique = 0
|
|
for entry in self.entries:
|
|
unique += self._get_unique(entry)
|
|
return unique
|
|
|
|
@staticmethod
|
|
def _decrypt(cipher, enc_string):
|
|
output = ""
|
|
for code in enc_string:
|
|
output += str(cipher["".join(sorted(code))])
|
|
return int(output)
|
|
|
|
@staticmethod
|
|
def _generate_cipher(entry):
|
|
codes = {} # Length: Actual Number
|
|
filtered = defaultdict(list)
|
|
for signal in entry:
|
|
filtered[len(signal)].append(set(signal))
|
|
|
|
codes[1] = filtered[2][0]
|
|
codes[4] = filtered[4][0]
|
|
codes[7] = filtered[3][0]
|
|
codes[8] = filtered[7][0]
|
|
|
|
for code in filtered[5]:
|
|
if len(code.union(codes[1])) == 5:
|
|
codes[3] = code
|
|
elif len(code.union(codes[4])) == 7:
|
|
codes[2] = code
|
|
else:
|
|
codes[5] = code
|
|
|
|
for code in filtered[6]:
|
|
if len(code.union(codes[7])) == 7:
|
|
codes[6] = code
|
|
elif len(code.union(codes[5])) == 6:
|
|
codes[9] = code
|
|
else:
|
|
codes[0] = code
|
|
|
|
# Let's reformat the results so we can reference by string
|
|
results = {"".join(sorted(code)): value for value, code in codes.items()}
|
|
|
|
return results
|
|
|
|
@staticmethod
|
|
def _get_unique(entries):
|
|
unique = 0
|
|
for entry in entries["outputs"]:
|
|
length = len(entry)
|
|
unique += length in [2, 4, 3, 7] # 1,4,7,8
|
|
return unique
|
|
|
|
@staticmethod
|
|
def _parse(lines):
|
|
entries = []
|
|
for line in lines:
|
|
split = line.split()
|
|
entries.append({
|
|
"signals": list(split[0:10]),
|
|
"outputs": list(split[11:]),
|
|
})
|
|
|
|
return entries
|
|
|
|
|
|
|
|
|
|
|
|
def part1(inputs: List[int]) -> int:
|
|
d = Display(inputs)
|
|
return d.unique_count()
|
|
|
|
|
|
def part2(inputs: List[int]) -> int:
|
|
d = Display(inputs)
|
|
return d.decode()
|
|
|
|
|
|
def parse(inputs: str) -> List[int]:
|
|
"""Parse the input string"""
|
|
return [line for line in inputs.split("\n")]
|
|
|
|
|
|
def solve(path: str) -> Tuple[int, int]:
|
|
"""Solve the puzzle"""
|
|
puzzle_input = parse(pathlib.Path(path).read_text().strip())
|
|
part1_result = part1(puzzle_input)
|
|
part2_result = part2(puzzle_input)
|
|
|
|
return part1_result, part2_result
|
|
|
|
|
|
def main() -> None:
|
|
for path in sys.argv[1:]:
|
|
print(f"Input File: {path}")
|
|
|
|
part1_result, part2_result = solve(path)
|
|
|
|
print(f"Part 1 Result: {part1_result}")
|
|
print(f"Part 2 Result: {part2_result}")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|