145 lines
4.6 KiB
Python
145 lines
4.6 KiB
Python
![]() |
# SPDX-License-Identifier: MIT
|
||
|
# Copyright (c) 2021 Akumatic
|
||
|
#
|
||
|
# https://adventofcode.com/2021/day/18
|
||
|
|
||
|
import math
|
||
|
|
||
|
class Node:
|
||
|
def __init__(self, left: "Node" = None, right: "Node" = None,
|
||
|
value: int = None, parent: "Node" = None, depth: int = 1):
|
||
|
self.left = left
|
||
|
self.right = right
|
||
|
self.value = value
|
||
|
self.parent = parent
|
||
|
self.depth = depth
|
||
|
|
||
|
def __str__(self) -> str:
|
||
|
return str(self.value) if self.value is not None else f"[{self.left},{self.right}]"
|
||
|
|
||
|
def explode(self) -> bool:
|
||
|
if self.depth < 5:
|
||
|
tmp = False
|
||
|
if self.left.value is None:
|
||
|
tmp = self.left.explode()
|
||
|
if tmp:
|
||
|
return True
|
||
|
if self.right.value is None:
|
||
|
tmp = self.right.explode()
|
||
|
return tmp
|
||
|
|
||
|
elif self.depth == 5:
|
||
|
assert self.left.value is not None and self.right.value is not None
|
||
|
left = self._find_next_node_with_value(left = True)
|
||
|
right = self._find_next_node_with_value(left = False)
|
||
|
if left:
|
||
|
left.value += self.left.value
|
||
|
if right:
|
||
|
right.value += self.right.value
|
||
|
self.left, self.right, self.value = None, None, 0
|
||
|
return True
|
||
|
|
||
|
def split(self) -> bool:
|
||
|
if self.value is None:
|
||
|
tmp = False
|
||
|
tmp = self.left.split()
|
||
|
if tmp:
|
||
|
return True
|
||
|
tmp = self.right.split()
|
||
|
return tmp
|
||
|
|
||
|
if self.value < 10:
|
||
|
return False
|
||
|
|
||
|
val = self.value / 2
|
||
|
self.value = None
|
||
|
self.left = Node(value=math.floor(val), parent=self, depth=self.depth + 1)
|
||
|
self.right = Node(value=math.ceil(val), parent=self, depth=self.depth + 1)
|
||
|
return True
|
||
|
|
||
|
def set_parent(self, parent: "Node") -> None:
|
||
|
self.parent = parent
|
||
|
self.depth = parent.depth + 1
|
||
|
if self.value is None:
|
||
|
self.left.set_parent(self)
|
||
|
self.right.set_parent(self)
|
||
|
|
||
|
def _find_next_node_with_value(self, left: bool) -> "Node":
|
||
|
no_parent = True
|
||
|
node = self
|
||
|
while node.parent is not None:
|
||
|
if left and node.parent.right == node or not left and node.parent.left == node:
|
||
|
node = node.parent.left if left else node.parent.right
|
||
|
no_parent = False
|
||
|
break
|
||
|
else:
|
||
|
node = node.parent
|
||
|
|
||
|
if no_parent:
|
||
|
return None
|
||
|
else:
|
||
|
while left and node.right is not None or not left and node.left is not None:
|
||
|
node = node.right if left else node.left
|
||
|
return node
|
||
|
|
||
|
def read_file(filename: str = "input.txt") -> list:
|
||
|
with open(f"{__file__.rstrip('code.py')}{filename}", "r") as f:
|
||
|
return [eval(line) for line in f.read().strip().split("\n")]
|
||
|
|
||
|
def parse_node(node: list, depth: int = 1) -> Node:
|
||
|
a = Node(value=node[0], depth=depth) if isinstance(node[0], int) else parse_node(node[0], depth = depth + 1)
|
||
|
b = Node(value=node[1], depth=depth) if isinstance(node[1], int) else parse_node(node[1], depth = depth + 1)
|
||
|
node = Node(left=a, right=b, depth=depth)
|
||
|
a.set_parent(node)
|
||
|
b.set_parent(node)
|
||
|
return node
|
||
|
|
||
|
def reduce(node: Node) -> None:
|
||
|
repeat = True
|
||
|
while repeat:
|
||
|
repeat = False
|
||
|
explosion_result = node.explode()
|
||
|
if explosion_result:
|
||
|
repeat = True
|
||
|
continue
|
||
|
repeat = node.split()
|
||
|
|
||
|
def add(a: Node, b: Node) -> Node:
|
||
|
node = Node(left=a, right=b)
|
||
|
a.set_parent(node)
|
||
|
b.set_parent(node)
|
||
|
reduce(node)
|
||
|
return node
|
||
|
|
||
|
def sum_nodes(nodes: list) -> list:
|
||
|
node = parse_node(nodes[0])
|
||
|
for i in range(1, len(nodes)):
|
||
|
node = add(node, parse_node(nodes[i]))
|
||
|
return node
|
||
|
|
||
|
def get_magnitude(node: Node) -> int:
|
||
|
if node.value is not None:
|
||
|
return node.value
|
||
|
return 3 * get_magnitude(node.left) + 2 * get_magnitude(node.right)
|
||
|
|
||
|
def part1(vals: list) -> int:
|
||
|
tree = sum_nodes(vals)
|
||
|
return get_magnitude(tree)
|
||
|
|
||
|
def part2(vals: list) -> int:
|
||
|
max_mag = 0
|
||
|
nodes = [parse_node(node) for node in vals]
|
||
|
for i in range(len(vals)):
|
||
|
for j in range(len(vals)):
|
||
|
if i == j:
|
||
|
continue
|
||
|
a, b = parse_node(vals[i]), parse_node(vals[j])
|
||
|
c, d = parse_node(vals[i]), parse_node(vals[j])
|
||
|
max_mag = max(max_mag, get_magnitude(add(a, b)), get_magnitude(add(c, d)))
|
||
|
return max_mag
|
||
|
|
||
|
if __name__ == "__main__":
|
||
|
vals = read_file()
|
||
|
print(f"Part 1: {part1(vals)}")
|
||
|
print(f"Part 2: {part2(vals)}")
|