#!/usr/bin/env python3 from pathlib import Path def parse(line): """This function parses an input line to nested tuples of length 2. It works by stripping the outermost bracket-pair from the string and counting the 'depth' of the current brackets. After we got back to the initial level 0 the next comma marks the index where the left and right part are seperated. Those will be parsed by a recursive parse call each. If there is no comma on a line, we are on the deepest level of this branch and return the value as integer. Else the left and right return value will be returned in a tuple. The result will bea binary tree of the snailfish number. Alternatives for parsing would be eval() - which can be unsafe and is slow - or json.parse(). """ if ',' not in line: return int(line) line = line[1:-1] level = 0 split_idx = 0 for i, c in enumerate(line): if '[' == c: level += 1 elif ']' == c: level -= 1 elif 0 == level and ',' == c: split_idx = i break left = parse(line[:split_idx]) right = parse(line[split_idx + 1:]) return (left, right) def explode_to_left(n, l): (left, right) = n res = True if isinstance(right, int): right = right + l else: res, right = explode_to_left(right, l) if not res: if isinstance(left, int): left = left + l res = True else: res, left = explode_to_left(left, l) if not res: res = False return res, (left, right) def explode_to_right(n, r): (left, right) = n res = True if isinstance(left, int): left = left + r else: res, left = explode_to_right(left, r) if not res: if isinstance(right, int): right = right + r res = True else: res, right = explode_to_right(right, r) if not res: res = False return res, (left, right) def explode(n, lvl=0): if isinstance(n, int): return False, n, None, None (left, right) = n if lvl == 4 and isinstance(left, int) and isinstance(right, int): return True, 0, left, right lvl += 1 res, left, l, r = explode(left, lvl) if not res: res, right, l, r = explode(right, lvl) if not res: return False, n, None, None if l: if isinstance(left, int): left = left + l l = None else: res, left = explode_to_left(left, l) if res: l = None return True, (left, right), l, r if r: if isinstance(right, int): right = right + r r = None else: res, right = explode_to_right(right, r) if res: r = None return True, (left, right), l, r def split(n): if isinstance(n, int): if 10 <= n: return True, (n // 2, (n + 1) // 2) return False, n (left, right) = n res, left = split(left) if not res: res, right = split(right) return res, (left, right) def magnitude(n): m = 0 (left, right) = n if isinstance(left, int): m += 3 * left else: m += 3 * magnitude(left) if isinstance(right, int): m += 2 * right else: m += 2 * magnitude(right) return m def part_1(input): result = 0 n = None for line in input: line = line.rstrip() if not n: n = parse(line) continue n = (n, parse(line)) res = True while res: while res: res, n, _, _ = explode(n) res, n = split(n) result = magnitude(n) print("Part 1 result:", result) def part_2(input): result = 0 nbrs = set() for line in input: line = line.rstrip() nbrs.add(parse(line)) for n1 in nbrs: for n2 in nbrs: if n1 == n2: continue n = (n1, n2) res = True while res: while res: res, n, _, _ = explode(n) res, n = split(n) result = max(result, magnitude(n)) print("Part 2 result:", result) input = list() p = Path(__file__).with_name('input.txt') with open(p) as f: input = f.readlines() part_1(input) part_2(input)