Add day 08 (phew)
This could probably be better, but I don't really care because that one was tough. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
This commit is contained in:
156
day08/day08.py
Executable file
156
day08/day08.py
Executable file
@@ -0,0 +1,156 @@
|
||||
#!/usr/bin/env python3
|
||||
import sys
|
||||
from typing import DefaultDict, FrozenSet, List, Sequence, Tuple
|
||||
from collections import defaultdict
|
||||
import pprint
|
||||
|
||||
|
||||
def setstr(s: FrozenSet[str]) -> str:
|
||||
return "".join(sorted(s))
|
||||
|
||||
|
||||
def part1(lines: Sequence[Tuple[Sequence[str], Sequence[str]]]):
|
||||
# Lazy solution
|
||||
lengths: DefaultDict[int, int] = defaultdict(lambda: 0)
|
||||
for _, digits in lines:
|
||||
for digit in digits:
|
||||
lengths[len(digit)] += 1
|
||||
# 1, 4, 7, and 8 use a unique number of digits.
|
||||
print(f"1: {lengths[2]}")
|
||||
print(f"4: {lengths[4]}")
|
||||
print(f"7: {lengths[3]}")
|
||||
print(f"8: {lengths[7]}")
|
||||
print(f"Sum: {sum(map(lengths.get, (2, 4, 3, 7)))}")
|
||||
|
||||
|
||||
def part2(lines: Sequence[Tuple[Sequence[str], Sequence[str]]]):
|
||||
# This is a table of 0-9 on the 7-seg display based on which lines are used:
|
||||
#
|
||||
# We can use this to figure out digits:
|
||||
# 1: EASY - Has 2 segments
|
||||
# 4: EASY - Has 4 segments
|
||||
# 7: EASY - Has 3 segments
|
||||
# 8: EASY - Has 7 segments
|
||||
|
||||
# 2: Uniquely shares the C segment with 1
|
||||
# 9: Shares all segments with 4, and also has two more
|
||||
# 3: Subtract 2 from 8, and subtract that result from 9
|
||||
# 6: Subtract C segment from 8
|
||||
# 5: Subtract C and E segments from 8
|
||||
# 0: Last remaining that we haven't figured out yet, no tricks here
|
||||
|
||||
nums = []
|
||||
|
||||
for patterns_, digits in lines:
|
||||
# using the name patterns_ so re-assignment isn't an issue thanks to mypy :|
|
||||
patterns = [frozenset(pat) for pat in patterns_]
|
||||
mappings = {}
|
||||
rmappings = {}
|
||||
for pat in patterns:
|
||||
if len(pat) == 2:
|
||||
mappings[pat] = 1
|
||||
rmappings[1] = pat
|
||||
elif len(pat) == 4:
|
||||
mappings[pat] = 4
|
||||
rmappings[4] = pat
|
||||
elif len(pat) == 3:
|
||||
mappings[pat] = 7
|
||||
rmappings[7] = pat
|
||||
elif len(pat) == 7:
|
||||
mappings[pat] = 8
|
||||
rmappings[8] = pat
|
||||
# Figure out 2:
|
||||
# There is one segment that is shared between 1 and 2, and it's the *only*
|
||||
# segment where that is the case.
|
||||
shared: DefaultDict[str, List[FrozenSet[str]]] = defaultdict(list)
|
||||
for pat in patterns:
|
||||
matches = rmappings[1] & pat
|
||||
shared[setstr(matches)] += [pat]
|
||||
# Whichever segment in shared has a length of 1 is seg value 2
|
||||
for match, pats in shared.items():
|
||||
if len(pats) == 1:
|
||||
pat = pats[0]
|
||||
mappings[pat] = 2
|
||||
rmappings[2] = pat
|
||||
break
|
||||
# pprint.pprint(shared)
|
||||
# Figure out 9:
|
||||
for pat in patterns:
|
||||
matches = rmappings[4] & pat
|
||||
# If the matching segments are shared with 4's segments, and there
|
||||
# are 2 more segments, then it's 9
|
||||
if matches == rmappings[4] and len(pat) == len(rmappings[4]) + 2:
|
||||
mappings[pat] = 9
|
||||
rmappings[9] = pat
|
||||
break
|
||||
|
||||
# Figure out 3:
|
||||
# Subtract the segments of 2 and 1 from the 8 seg, and then subtract
|
||||
# *that* from the 9 seg
|
||||
b_seg = (rmappings[8] - rmappings[2]) - rmappings[1]
|
||||
three = rmappings[9] - b_seg
|
||||
assert three in patterns
|
||||
assert three not in mappings
|
||||
mappings[three] = 3
|
||||
rmappings[3] = three
|
||||
|
||||
# Few more segments
|
||||
a_seg = rmappings[7] - rmappings[1]
|
||||
assert len(a_seg) == 1
|
||||
c_seg = rmappings[2] & rmappings[1]
|
||||
assert len(c_seg) == 1
|
||||
e_seg = rmappings[8] - rmappings[9]
|
||||
assert len(e_seg) == 1
|
||||
f_seg = rmappings[1] - c_seg
|
||||
assert len(f_seg) == 1
|
||||
|
||||
# Figure out 6:
|
||||
# Subtract c segment from 8
|
||||
six = rmappings[8] - c_seg
|
||||
assert six in patterns
|
||||
assert six not in mappings
|
||||
mappings[six] = 6
|
||||
rmappings[6] = six
|
||||
|
||||
# Figure out 5:
|
||||
# Subtract e and c segments from 8
|
||||
five = (rmappings[8] - e_seg) - c_seg
|
||||
assert five in patterns
|
||||
assert five not in mappings
|
||||
mappings[five] = 5
|
||||
rmappings[5] = five
|
||||
|
||||
# There's only one number we haven't mapped yet and that's 0, so that's
|
||||
# the last one.
|
||||
remaining = frozenset(patterns) - frozenset(mappings.keys())
|
||||
assert len(remaining) == 1
|
||||
zero = list(remaining)[0]
|
||||
|
||||
mappings[zero] = 0
|
||||
rmappings[0] = zero
|
||||
|
||||
assert len(mappings) == 10
|
||||
assert len(rmappings) == 10
|
||||
|
||||
# Now, figure out each number using our mappings
|
||||
num = 0
|
||||
for digit in digits:
|
||||
num *= 10
|
||||
num += mappings[frozenset(digit)]
|
||||
nums += [num]
|
||||
print(sum(nums))
|
||||
|
||||
|
||||
lines = [
|
||||
# yeah the line.split[0] and [1] is annoying but it makes it work for a
|
||||
# comprehension. also it doesn't matter
|
||||
(line.split(" | ")[0].split(), line.split(" | ")[1].split())
|
||||
for line in sys.stdin
|
||||
]
|
||||
|
||||
# pprint.pprint(lines)
|
||||
|
||||
print("Part 1")
|
||||
part1(lines)
|
||||
print("Part 2")
|
||||
part2(lines)
|
||||
Reference in New Issue
Block a user