This could probably be better, but I don't really care because that one was tough. Signed-off-by: Alek Ratzloff <alekratz@gmail.com>
157 lines
4.9 KiB
Python
Executable File
157 lines
4.9 KiB
Python
Executable File
#!/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)
|