148 lines
3.3 KiB
Python
148 lines
3.3 KiB
Python
|
|
#!/usr/bin/env python3
|
||
|
|
import sys
|
||
|
|
from typing import Sequence
|
||
|
|
|
||
|
|
|
||
|
|
class ParseError(Exception):
|
||
|
|
def __init__(self, got: str, expected: str):
|
||
|
|
self.got = got
|
||
|
|
self.expected = expected
|
||
|
|
super().__init__(f"expected {repr(expected)} but got {repr(got)} instead")
|
||
|
|
|
||
|
|
|
||
|
|
CLOSING = ")]}>"
|
||
|
|
|
||
|
|
|
||
|
|
class Parser:
|
||
|
|
def __init__(self, text: str):
|
||
|
|
self.text = text
|
||
|
|
self.ptr = 0
|
||
|
|
|
||
|
|
def reset(self):
|
||
|
|
self.ptr = 0
|
||
|
|
|
||
|
|
@property
|
||
|
|
def is_eof(self) -> bool:
|
||
|
|
return self.ptr >= len(self.text)
|
||
|
|
|
||
|
|
@property
|
||
|
|
def c(self) -> str:
|
||
|
|
if self.is_eof:
|
||
|
|
return ""
|
||
|
|
else:
|
||
|
|
return self.text[self.ptr]
|
||
|
|
|
||
|
|
def adv(self):
|
||
|
|
if self.ptr >= len(self.text):
|
||
|
|
self.ptr = len(self.text)
|
||
|
|
else:
|
||
|
|
self.ptr += 1
|
||
|
|
|
||
|
|
def expect(self, c: str):
|
||
|
|
if c == self.c:
|
||
|
|
self.adv()
|
||
|
|
else:
|
||
|
|
raise ParseError(self.c, c)
|
||
|
|
|
||
|
|
def parse(self):
|
||
|
|
if self.c == "(":
|
||
|
|
self.parse_paren()
|
||
|
|
elif self.c == "[":
|
||
|
|
self.parse_bracket()
|
||
|
|
elif self.c == "{":
|
||
|
|
self.parse_brace()
|
||
|
|
elif self.c == "<":
|
||
|
|
self.parse_angle()
|
||
|
|
|
||
|
|
def parse_paren(self):
|
||
|
|
self.expect("(")
|
||
|
|
while self.c not in CLOSING:
|
||
|
|
self.parse()
|
||
|
|
self.expect(")")
|
||
|
|
|
||
|
|
def parse_bracket(self):
|
||
|
|
self.expect("[")
|
||
|
|
while self.c not in CLOSING:
|
||
|
|
self.parse()
|
||
|
|
self.expect("]")
|
||
|
|
|
||
|
|
def parse_brace(self):
|
||
|
|
self.expect("{")
|
||
|
|
while self.c not in CLOSING:
|
||
|
|
self.parse()
|
||
|
|
self.expect("}")
|
||
|
|
|
||
|
|
def parse_angle(self):
|
||
|
|
self.expect("<")
|
||
|
|
while self.c not in CLOSING:
|
||
|
|
self.parse()
|
||
|
|
self.expect(">")
|
||
|
|
|
||
|
|
|
||
|
|
def part1(lines: Sequence[str]):
|
||
|
|
scores = {
|
||
|
|
")": 3,
|
||
|
|
"]": 57,
|
||
|
|
"}": 1197,
|
||
|
|
">": 25137,
|
||
|
|
"": 0,
|
||
|
|
}
|
||
|
|
points = 0
|
||
|
|
for line in lines:
|
||
|
|
try:
|
||
|
|
parser = Parser(line)
|
||
|
|
while not parser.is_eof:
|
||
|
|
parser.parse()
|
||
|
|
except ParseError as e:
|
||
|
|
points += scores[e.got]
|
||
|
|
print(points)
|
||
|
|
|
||
|
|
|
||
|
|
def part2(lines: Sequence[str]):
|
||
|
|
scores = {
|
||
|
|
")": 1,
|
||
|
|
"]": 2,
|
||
|
|
"}": 3,
|
||
|
|
">": 4,
|
||
|
|
}
|
||
|
|
all_points = []
|
||
|
|
for line in lines:
|
||
|
|
completion = ""
|
||
|
|
try:
|
||
|
|
parser = Parser(line)
|
||
|
|
while not parser.is_eof:
|
||
|
|
parser.parse()
|
||
|
|
except ParseError as e:
|
||
|
|
if e.got != "":
|
||
|
|
continue
|
||
|
|
# Try to complete the parse using exceptions :^)
|
||
|
|
parser.text += e.expected
|
||
|
|
completion += e.expected
|
||
|
|
while True:
|
||
|
|
parser.reset()
|
||
|
|
try:
|
||
|
|
while not parser.is_eof:
|
||
|
|
parser.parse()
|
||
|
|
break
|
||
|
|
except ParseError as e:
|
||
|
|
assert e.expected != ""
|
||
|
|
parser.text += e.expected
|
||
|
|
completion += e.expected
|
||
|
|
# Calculate points using the completion
|
||
|
|
points = 0
|
||
|
|
for c in completion:
|
||
|
|
points *= 5
|
||
|
|
points += scores[c]
|
||
|
|
|
||
|
|
all_points += [points]
|
||
|
|
all_points = sorted(all_points)
|
||
|
|
print(all_points[len(all_points) // 2])
|
||
|
|
|
||
|
|
|
||
|
|
lines = list(map(str.strip, sys.stdin))
|
||
|
|
|
||
|
|
print("Part 1")
|
||
|
|
part1(lines)
|
||
|
|
print("Part 2")
|
||
|
|
part2(lines)
|