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): def try_add(n, val, primary, secondary): if val == 0: return 0 if is_pair(primary(n)): val = try_add(primary(n), val, primary, secondary) else: primary(n, primary(n) + val) return 0 if val != 0: if is_pair(secondary(n)): val = try_add(secondary(n), val, primary, secondary) else: secondary(n, secondary(n) + val) return 0 return val def descend(n, depth=0): if not is_pair(n): return None if depth == 4: return n car_val = descend(n.car, depth + 1) if car_val is not None: if depth == 3: n.car = 0 if not is_pair(n.cdr): n.cdr += car_val.cdr car_val.cdr = 0 return Pair(car_val.car, try_add(n.cdr, car_val.cdr, car, cdr)) else: cdr_val = descend(n.cdr, depth + 1) if cdr_val is not None: if depth == 3: n.cdr = 0 if not is_pair(n.car): n.car += cdr_val.car cdr_val.car = 0 return Pair(try_add(n.car, cdr_val.car, cdr, car), cdr_val.cdr) return descend(n) tests = [ ('[[[[[9,8],1],2],3],4]', '[[[[0,9],2],3],4]'), ('[7,[6,[5,[4,[3,2]]]]]', '[7,[6,[5,[7,0]]]]'), ('[[6,[5,[4,[3,2]]]],1]', '[[6,[5,[7,0]]],3]'), ('[[3,[2,[1,[7,3]]]],[6,[5,[4,[3,2]]]]]', '[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]'), ('[[3,[2,[8,0]]],[9,[5,[4,[3,2]]]]]', '[[3,[2,[8,0]]],[9,[5,[7,0]]]]'), ] for test, expect in tests: test = parse(test) try_explode(test) assert(test == parse(expect)) 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)))