from __future__ import annotations from copy import deepcopy from dataclasses import dataclass from functools import reduce from itertools import combinations from math import floor, ceil from utils import open_day @dataclass class Pair: car: int | Pair cdr: int | Pair def __repr__(self) -> str: return f'[{self.car},{self.cdr}]' def car(p, assign=None): if assign is not None: p.car = assign return p.car def cdr(p, assign=None): if assign is not None: p.cdr = assign return p.cdr def is_pair(p): return isinstance(p, Pair) def parse(s): return eval(s.replace('[', 'Pair(').replace(']', ')')) with open_day(18) as f: homework = [parse(l) for l in f] def try_explode(n, depth=0): def try_add(n, val, access, primary, secondary): if val == 0: return 0 content = access(n) if not is_pair(content): access(n, content + val) return 0 val = try_add(content, val, primary, primary, secondary) val = try_add(content, val, secondary, primary, secondary) return val if not is_pair(n): return None if depth == 4: return n for access in (car, cdr): val = try_explode(access(n), depth + 1) if val is None: continue if depth == 3: access(n, 0) if access == car: return Pair(val.car, try_add(n, val.cdr, cdr, car, cdr)) else: return Pair(try_add(n, val.car, car, cdr, car), val.cdr) def try_split(n, access=lambda n: n): content = access(n) if is_pair(content): return try_split(content, car) or try_split(content, cdr) if content < 10: return False content /= 2 access(n, Pair(floor(content), ceil(content))) return True def reduce_num(n): n = deepcopy(n) while True: if try_explode(n): continue if try_split(n): continue break return n def magnitude(n): if is_pair(n): return 3 * magnitude(n.car) + 2 * magnitude(n.cdr) return n print(magnitude(reduce(lambda a, b: reduce_num(Pair(a, b)), homework))) print(max(max(magnitude(reduce_num(Pair(a, b))), magnitude(reduce_num(Pair(b, a)))) for a, b in combinations(homework, 2)))