summaryrefslogtreecommitdiffstats
path: root/18.py
blob: 59c3f8aa49e141d4ba6ce82c89f6ae1a5fbac69f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
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, 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
    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
            return Pair(car_val.car, try_add(n, car_val.cdr, cdr, car, cdr))
        else:
            cdr_val = descend(n.cdr, depth + 1)
            if cdr_val is not None:
                if depth == 3: n.cdr = 0
                return Pair(try_add(n, cdr_val.car, 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)))