You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
153 lines
3.9 KiB
153 lines
3.9 KiB
3 years ago
|
from __future__ import annotations
|
||
|
import copy
|
||
|
import json
|
||
|
from typing import Union
|
||
|
from itertools import combinations
|
||
|
|
||
|
|
||
|
class Regular:
|
||
|
|
||
|
def __init__(self, value):
|
||
|
self.value = value
|
||
|
self.parent: Pair = None
|
||
|
|
||
|
def split(self):
|
||
|
newLeft = Regular(self.value // 2)
|
||
|
newRight = Regular(self.value // 2 + self.value % 2)
|
||
|
newPair = Pair(newLeft, newRight)
|
||
|
newPair.parent = self.parent
|
||
|
if self == self.parent.left:
|
||
|
self.parent.left = newPair
|
||
|
if self == self.parent.right:
|
||
|
self.parent.right = newPair
|
||
|
|
||
|
def __repr__(self):
|
||
|
return f"{self.value}"
|
||
|
|
||
|
|
||
|
class Pair:
|
||
|
|
||
|
def __init__(self, left: Union[Pair, Regular], right: Union[Pair, Regular]):
|
||
|
self.left = left
|
||
|
self.right = right
|
||
|
left.parent = self
|
||
|
right.parent = self
|
||
|
self.parent: Pair = None
|
||
|
|
||
|
@staticmethod
|
||
|
def neutral() -> Pair:
|
||
|
pair = Pair.parse([0, 0])
|
||
|
pair.left = None
|
||
|
return pair
|
||
|
|
||
|
@staticmethod
|
||
|
def parse(content) -> Union[Pair, int]:
|
||
|
if isinstance(content, int):
|
||
|
return Regular(content)
|
||
|
left, right = content
|
||
|
return Pair(Pair.parse(left), Pair.parse(right))
|
||
|
|
||
|
def __add__(self, other) -> Pair:
|
||
|
if self.left is None:
|
||
|
return other
|
||
|
pair = Pair(copy.deepcopy(self), copy.deepcopy(other))
|
||
|
pair.reduce()
|
||
|
return pair
|
||
|
|
||
|
def getExplodePair(self, dim=0) -> Pair:
|
||
|
if dim == 4:
|
||
|
return self
|
||
|
|
||
|
def checkSide(side):
|
||
|
if isinstance(side, Pair):
|
||
|
pair = side.getExplodePair(dim + 1)
|
||
|
if isinstance(pair, Pair):
|
||
|
return pair
|
||
|
if explode := checkSide(self.left):
|
||
|
return explode
|
||
|
if explode := checkSide(self.right):
|
||
|
return explode
|
||
|
|
||
|
def getSplitRegular(self) -> Regular:
|
||
|
leftIsRegular = isinstance(self.left, Regular)
|
||
|
rightIsRegular = isinstance(self.right, Regular)
|
||
|
if leftIsRegular:
|
||
|
if self.left.value >= 10:
|
||
|
return self.left
|
||
|
elif left := self.left.getSplitRegular():
|
||
|
return left
|
||
|
if rightIsRegular:
|
||
|
if self.right.value >= 10:
|
||
|
return self.right
|
||
|
elif right := self.right.getSplitRegular():
|
||
|
return right
|
||
|
return None
|
||
|
|
||
|
def findRegularLeft(self) -> Regular:
|
||
|
if self.parent is None:
|
||
|
return None
|
||
|
if self == self.parent.right:
|
||
|
if isinstance(self.parent.left, Regular):
|
||
|
return self.parent.left
|
||
|
return self.parent.left.findLastRegular()
|
||
|
return self.parent.findRegularLeft()
|
||
|
|
||
|
def findRegularRight(self) -> Regular:
|
||
|
if self.parent is None:
|
||
|
return None
|
||
|
if self == self.parent.left:
|
||
|
if isinstance(self.parent.right, Regular):
|
||
|
return self.parent.right
|
||
|
return self.parent.right.findFirstRegular()
|
||
|
return self.parent.findRegularRight()
|
||
|
|
||
|
def findFirstRegular(self) -> Regular:
|
||
|
if isinstance(self.left, Regular):
|
||
|
return self.left
|
||
|
return self.left.findFirstRegular()
|
||
|
|
||
|
def findLastRegular(self) -> Regular:
|
||
|
if isinstance(self.right, Regular):
|
||
|
return self.right
|
||
|
return self.right.findLastRegular()
|
||
|
|
||
|
def explode(self):
|
||
|
regularLeft = self.findRegularLeft()
|
||
|
regularRight = self.findRegularRight()
|
||
|
if regularLeft:
|
||
|
regularLeft.value += self.left.value
|
||
|
if regularRight:
|
||
|
regularRight.value += self.right.value
|
||
|
zero = Regular(0)
|
||
|
zero.parent = self.parent
|
||
|
if self == self.parent.left:
|
||
|
self.parent.left = zero
|
||
|
if self == self.parent.right:
|
||
|
self.parent.right = zero
|
||
|
|
||
|
def reduce(self):
|
||
|
while (toExplode := self.getExplodePair()) or (toSplit := self.getSplitRegular()):
|
||
|
if toExplode:
|
||
|
toExplode.explode()
|
||
|
continue
|
||
|
toSplit.split()
|
||
|
|
||
|
def magnitude(self):
|
||
|
left = self.left.value if isinstance(self.left, Regular) else self.left.magnitude()
|
||
|
right = self.right.value if isinstance(self.right, Regular) else self.right.magnitude()
|
||
|
return left * 3 + right * 2
|
||
|
|
||
|
def __repr__(self):
|
||
|
return f"({self.left}, {self.right})"
|
||
|
|
||
|
|
||
|
def parseNumbers(fileName: str) -> list[Pair]:
|
||
|
with open(fileName) as file:
|
||
|
lines = file.read().splitlines()
|
||
|
return [Pair.parse(json.loads(line)) for line in lines]
|
||
|
|
||
|
|
||
|
allNumbers = parseNumbers("input")
|
||
|
print(sum(allNumbers, Pair.neutral()).magnitude())
|
||
|
print(max([max((a + b).magnitude(), (b + a).magnitude()) for a, b in combinations(allNumbers, 2)]))
|