#!/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)