All checks were successful
continuous-integration/drone/push Build is passing
174 lines
4.6 KiB
Python
174 lines
4.6 KiB
Python
#!/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 type(right) == int:
|
|
right = right + l
|
|
else:
|
|
res, right = explode_to_left(right, l)
|
|
if not res:
|
|
if type(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 type(left) == int:
|
|
left = left + r
|
|
else:
|
|
res, left = explode_to_right(left, r)
|
|
if not res:
|
|
if type(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 type(n) is int:
|
|
return False, n, None, None
|
|
(left, right) = n
|
|
if lvl == 4 and type(left) is int and type(right) is int:
|
|
return True, 0, left, right
|
|
else:
|
|
res, left, l, r = explode(left, lvl+1)
|
|
if not res:
|
|
res, right, l, r = explode(right, lvl+1)
|
|
if not res:
|
|
return False, n, None, None
|
|
else:
|
|
if l:
|
|
if type(left) is int:
|
|
left = left + l
|
|
l = None
|
|
else:
|
|
res, left = explode_to_left(left, l)
|
|
if res:
|
|
l = None
|
|
return True, (left, right), l, r
|
|
else:
|
|
if r:
|
|
if type(right) is 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 type(n) is int:
|
|
if 10 <= n:
|
|
return True, (n // 2, (n + 1) // 2)
|
|
else:
|
|
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 type(left) is int:
|
|
m += 3 * left
|
|
else:
|
|
m += 3 * magnitude(left)
|
|
if type(right) is 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)
|