Compare commits

..

4 commits

4 changed files with 619 additions and 0 deletions

208
2021/d18/ex1/ex1.py Executable file
View file

@ -0,0 +1,208 @@
#!/usr/bin/env python
import functools
import itertools
import sys
from dataclasses import dataclass
from typing import Iterator, List, Optional, Tuple
@dataclass
class Tree:
parent: Optional["Pair"]
@dataclass
class Pair(Tree):
left: Tree
right: Tree
@dataclass
class Num(Tree):
val: int
# True means left, False means right
Path = List[bool]
def solve(input: List[str]) -> int:
def make_pair(left: Tree, right: Tree, parent: Optional[Pair] = None) -> Pair:
pair = Pair(parent=parent, left=left, right=right)
pair.left.parent = pair
pair.right.parent = pair
return pair
def make_num(val: int, parent: Optional[Pair] = None) -> Num:
return Num(parent=parent, val=val)
# FIXME: remove this
def debug(tree: Tree) -> str:
if isinstance(tree, Pair):
return f"[{debug(tree.left)},{debug(tree.right)}]"
assert isinstance(tree, Num)
return str(tree.val)
def parse() -> List[Tree]:
def parse_snailfish_number(line: str) -> Tree:
def parse_index(input: str, index: int = 0) -> Tuple[int, Tree]:
if input[index] == "[":
left_index, left = parse_index(input, index + 1)
assert input[left_index] == "," # Sanity check
right_index, right = parse_index(input, left_index + 1)
assert input[right_index] == "]" # Sanity check
return right_index + 1, make_pair(left, right)
res = 0
for i in itertools.count(index):
if i < len(input) and input[i] in set(str(i) for i in range(10)):
res = res * 10 + int(input[i])
else:
return i, make_num(res)
assert False # Sanity check
__, res = parse_index(line)
return res
return [parse_snailfish_number(line) for line in input]
def explosion_path(number: Tree) -> Optional[Path]:
def dfs(number: Pair, path: Path = []) -> Optional[Path]:
if (
len(path) >= 4
and isinstance(number.left, Num)
and isinstance(number.right, Num)
):
return path
if isinstance(number.left, Pair):
left_path = dfs(number.left, path + [True])
if left_path is not None:
return left_path
if isinstance(number.right, Pair):
right_path = dfs(number.right, path + [False])
if right_path is not None:
return right_path
return None
assert isinstance(number, Pair) # Sanity check
return dfs(number)
def split_path(number: Tree) -> Optional[Path]:
def dfs(number: Pair, path: Path = []) -> Optional[Path]:
if isinstance(number.left, Num):
if number.left.val >= 10:
return path + [True]
else:
assert isinstance(number.left, Pair) # Sanity check
if (left_path := dfs(number.left, path + [True])) is not None:
return left_path
if isinstance(number.right, Num):
if number.right.val >= 10:
return path + [False]
else:
assert isinstance(number.right, Pair) # Sanity check
if (right_path := dfs(number.right, path + [False])) is not None:
return right_path
return None
assert isinstance(number, Pair) # Sanity check
return dfs(number)
def explode(number: Tree, path: Path) -> Tree:
def walk(number: Tree, reverse: bool) -> Iterator[Tree]:
if isinstance(number, Num):
yield number
else:
assert isinstance(number, Pair) # Sanity check
first, second = (
(number.right, number.left)
if reverse
else (number.left, number.right)
)
yield from walk(first, reverse)
yield number
yield from walk(second, reverse)
def next_num(number: Tree, reverse: bool) -> Optional[Num]:
if number.parent is None:
return None
sibling = number.parent.left if reverse else number.parent.right
if number is sibling:
return next_num(number.parent, reverse)
for node in walk(sibling, reverse=reverse):
if isinstance(node, Num):
return node
return None
assert isinstance(number, Pair) # Sanity check
if len(path) == 0:
p, n = next_num(number, reverse=True), next_num(number, reverse=False)
if p is not None:
assert isinstance(number.left, Num) # Safety check
p.val += number.left.val
if n is not None:
assert isinstance(number.right, Num) # Safety check
n.val += number.right.val
return make_num(0)
parent, left, right = number.parent, number.left, number.right
if path[0]:
left = explode(number.left, path[1:])
else:
right = explode(number.right, path[1:])
return make_pair(parent=parent, left=left, right=right)
def split(number: Tree, path: Path) -> Tree:
def split_int(num: int, parent: Optional[Pair]) -> Tree:
assert num >= 0 # Sanity check
left = num // 2
right = num - left
res = make_pair(left=make_num(left), right=make_num(right), parent=parent)
return res
if len(path) == 0:
assert isinstance(number, Num) # Sanity check
return split_int(number.val, number.parent)
assert isinstance(number, Pair) # Sanity check
parent, left, right = number.parent, number.left, number.right
if path[0]:
left = split(number.left, path[1:])
else:
right = split(number.right, path[1:])
return make_pair(parent=parent, left=left, right=right)
def reduce(number: Tree) -> Tree:
path = explosion_path(number)
if path is not None:
return reduce(explode(number, path))
path = split_path(number)
if path is not None:
return reduce(split(number, path))
return number
def add(left: Tree, right: Tree) -> Tree:
return reduce(make_pair(left=left, right=right))
def magnitude(number: Tree) -> int:
if isinstance(number, Num):
return number.val
assert isinstance(number, Pair) # Safety check
return 3 * magnitude(number.left) + 2 * magnitude(number.right)
numbers = parse()
result = functools.reduce(add, numbers)
return magnitude(result)
def main() -> None:
input = [line.strip() for line in sys.stdin.readlines()]
print(solve(input))
if __name__ == "__main__":
main()

100
2021/d18/ex1/input Normal file
View file

@ -0,0 +1,100 @@
[[3,[8,[2,1]]],[[[0,6],[0,2]],3]]
[[[1,[8,5]],[[3,9],0]],2]
[5,[[5,[3,8]],[7,4]]]
[1,[[[0,4],[8,5]],6]]
[[[1,[0,3]],2],[2,[0,[7,9]]]]
[[[4,[4,4]],[[7,2],[7,1]]],9]
[5,[4,4]]
[[0,[[2,6],[8,9]]],[[4,5],2]]
[[[8,2],0],3]
[[9,0],[3,3]]
[[[[5,2],2],5],5]
[[[1,6],[[0,4],[7,7]]],[[1,4],[[6,5],5]]]
[[[[4,1],[4,1]],[2,[5,5]]],[1,[0,[0,6]]]]
[[[[1,5],1],[8,4]],[9,[3,4]]]
[[1,[3,3]],[[[7,4],[8,1]],2]]
[3,[[[2,1],4],[5,4]]]
[6,[[0,[1,9]],[[4,0],8]]]
[5,[7,[7,[8,8]]]]
[[[[6,2],[5,8]],[5,[3,1]]],[[7,9],[[2,0],6]]]
[[[7,[7,9]],[5,7]],[[[9,3],[6,9]],[[1,2],[2,3]]]]
[[[[4,1],2],[1,[6,6]]],[[[2,2],[8,8]],4]]
[[[[3,7],4],8],[6,[[0,2],3]]]
[[[[1,8],2],3],[[9,[1,7]],[[0,0],[6,8]]]]
[[[9,[5,2]],7],[[8,6],[8,[1,2]]]]
[[[7,[1,0]],[[6,0],[8,4]]],[[[7,8],5],[3,[1,2]]]]
[[[[2,5],9],[[8,2],0]],0]
[0,[[[7,5],[4,1]],[5,[6,6]]]]
[[[[3,6],2],[[1,1],[6,6]]],0]
[[[[0,9],[2,5]],[2,[3,2]]],[6,3]]
[3,[[9,[1,4]],[[0,8],[4,6]]]]
[1,[[5,[5,9]],[9,0]]]
[[[6,8],4],[[[6,6],2],[[3,9],2]]]
[5,[[[7,5],[4,8]],0]]
[[9,[6,6]],[9,[[6,8],[6,4]]]]
[[[4,8],[0,[2,8]]],[7,[[4,5],[1,6]]]]
[[[6,[8,6]],2],[[[2,9],[2,4]],[0,2]]]
[[[0,[5,6]],[[3,8],3]],[[3,1],7]]
[[1,[8,1]],[1,[6,[7,1]]]]
[[[5,[9,6]],[3,5]],2]
[[3,7],[[[2,5],[4,1]],[3,[5,6]]]]
[[8,7],[[9,6],3]]
[[[[4,2],[4,8]],[7,[4,5]]],2]
[[[[6,7],6],3],[[[6,7],4],0]]
[[[0,1],[[9,1],[2,9]]],9]
[[[[8,5],[5,8]],[0,7]],[0,[8,[3,2]]]]
[[4,[[6,5],[1,9]]],[[[0,0],1],6]]
[[[[9,5],9],[2,[6,3]]],[[2,9],[6,9]]]
[[[7,[5,0]],1],[7,[[8,7],3]]]
[[[2,4],2],[[[3,0],6],[[0,2],[9,2]]]]
[[1,[[7,3],[4,3]]],[[[3,9],[1,1]],[3,6]]]
[[[[4,7],7],[[7,1],[2,3]]],[1,[[7,6],[5,6]]]]
[[0,[5,2]],0]
[[[[6,6],[4,8]],8],[[0,[7,4]],8]]
[[4,[7,2]],[[[0,8],1],[9,5]]]
[0,0]
[[[[3,7],6],3],[3,[[3,3],1]]]
[[[6,5],7],[[3,5],[[6,4],[4,9]]]]
[[4,[[7,9],9]],9]
[5,[8,[[7,4],1]]]
[[[[2,4],[5,7]],8],[[[7,6],[6,9]],[[3,9],[6,4]]]]
[[[4,8],3],[[[3,9],7],0]]
[0,[8,[[4,2],3]]]
[[[[0,1],[5,8]],[7,2]],[2,4]]
[[6,[8,[1,9]]],[[[6,5],[8,1]],[7,[6,4]]]]
[[9,3],[5,[0,6]]]
[[2,[7,[2,0]]],[[2,1],[5,5]]]
[[[0,[7,0]],[[0,4],[4,9]]],[8,[[6,1],[6,3]]]]
[[[[5,7],[3,2]],[0,[5,0]]],[[0,[1,6]],3]]
[[[[6,3],[9,5]],[9,9]],[[5,[8,3]],[[0,0],[0,3]]]]
[[6,[4,9]],[[[9,9],[8,4]],4]]
[0,[2,5]]
[[[[7,9],[1,2]],[3,3]],[[[7,2],7],[[1,6],0]]]
[[[[8,0],2],8],[[[1,5],9],9]]
[[[0,[6,9]],4],[[[4,8],5],4]]
[[6,[[0,3],4]],[0,[[8,3],1]]]
[[[1,2],[2,[3,3]]],[6,7]]
[[0,[[7,4],5]],[3,[[8,2],0]]]
[[[[0,1],[1,7]],[[2,7],[5,9]]],[[[7,0],0],[8,1]]]
[[6,4],[3,0]]
[[[[6,6],4],[5,1]],[7,3]]
[[[[9,2],3],[8,[4,8]]],7]
[[5,[[2,2],[9,2]]],[[[1,8],0],[8,[6,3]]]]
[2,[[0,0],[0,[9,9]]]]
[[4,4],[[6,5],[6,5]]]
[[[[9,1],2],4],5]
[[[[2,1],[3,1]],[[2,6],9]],5]
[[[9,[0,6]],7],[[8,3],[[8,1],2]]]
[[[6,[0,0]],[2,[0,0]]],[[[0,4],8],3]]
[[[[4,1],[2,9]],[6,5]],3]
[[9,[[9,4],8]],[[[5,5],3],[[3,4],4]]]
[8,[9,[[0,3],1]]]
[9,[[[6,0],4],9]]
[[6,[2,9]],[[[2,7],[5,3]],0]]
[[[4,1],5],[8,[[0,7],4]]]
[[[[2,5],5],[[8,2],[8,9]]],[[9,6],[[0,3],[2,3]]]]
[6,1]
[[1,7],4]
[[8,7],0]
[[[[5,4],7],5],[[[6,1],5],[5,[5,5]]]]
[[[6,[1,5]],[0,[7,0]]],[[[1,5],3],[5,[1,0]]]]

211
2021/d18/ex2/ex2.py Executable file
View file

@ -0,0 +1,211 @@
#!/usr/bin/env python
import functools
import itertools
import sys
from copy import deepcopy
from dataclasses import dataclass
from typing import Iterator, List, Optional, Tuple
@dataclass
class Tree:
parent: Optional["Pair"]
@dataclass
class Pair(Tree):
left: Tree
right: Tree
@dataclass
class Num(Tree):
val: int
# True means left, False means right
Path = List[bool]
def solve(input: List[str]) -> int:
def make_pair(left: Tree, right: Tree, parent: Optional[Pair] = None) -> Pair:
pair = Pair(parent=parent, left=left, right=right)
pair.left.parent = pair
pair.right.parent = pair
return pair
def make_num(val: int, parent: Optional[Pair] = None) -> Num:
return Num(parent=parent, val=val)
# FIXME: remove this
def debug(tree: Tree) -> str:
if isinstance(tree, Pair):
return f"[{debug(tree.left)},{debug(tree.right)}]"
assert isinstance(tree, Num)
return str(tree.val)
def parse() -> List[Tree]:
def parse_snailfish_number(line: str) -> Tree:
def parse_index(input: str, index: int = 0) -> Tuple[int, Tree]:
if input[index] == "[":
left_index, left = parse_index(input, index + 1)
assert input[left_index] == "," # Sanity check
right_index, right = parse_index(input, left_index + 1)
assert input[right_index] == "]" # Sanity check
return right_index + 1, make_pair(left, right)
res = 0
for i in itertools.count(index):
if i < len(input) and input[i] in set(str(i) for i in range(10)):
res = res * 10 + int(input[i])
else:
return i, make_num(res)
assert False # Sanity check
__, res = parse_index(line)
return res
return [parse_snailfish_number(line) for line in input]
def explosion_path(number: Tree) -> Optional[Path]:
def dfs(number: Pair, path: Path = []) -> Optional[Path]:
if (
len(path) >= 4
and isinstance(number.left, Num)
and isinstance(number.right, Num)
):
return path
if isinstance(number.left, Pair):
left_path = dfs(number.left, path + [True])
if left_path is not None:
return left_path
if isinstance(number.right, Pair):
right_path = dfs(number.right, path + [False])
if right_path is not None:
return right_path
return None
assert isinstance(number, Pair) # Sanity check
return dfs(number)
def split_path(number: Tree) -> Optional[Path]:
def dfs(number: Pair, path: Path = []) -> Optional[Path]:
if isinstance(number.left, Num):
if number.left.val >= 10:
return path + [True]
else:
assert isinstance(number.left, Pair) # Sanity check
if (left_path := dfs(number.left, path + [True])) is not None:
return left_path
if isinstance(number.right, Num):
if number.right.val >= 10:
return path + [False]
else:
assert isinstance(number.right, Pair) # Sanity check
if (right_path := dfs(number.right, path + [False])) is not None:
return right_path
return None
assert isinstance(number, Pair) # Sanity check
return dfs(number)
def explode(number: Tree, path: Path) -> Tree:
def walk(number: Tree, reverse: bool) -> Iterator[Tree]:
if isinstance(number, Num):
yield number
else:
assert isinstance(number, Pair) # Sanity check
first, second = (
(number.right, number.left)
if reverse
else (number.left, number.right)
)
yield from walk(first, reverse)
yield number
yield from walk(second, reverse)
def next_num(number: Tree, reverse: bool) -> Optional[Num]:
if number.parent is None:
return None
sibling = number.parent.left if reverse else number.parent.right
if number is sibling:
return next_num(number.parent, reverse)
for node in walk(sibling, reverse=reverse):
if isinstance(node, Num):
return node
return None
assert isinstance(number, Pair) # Sanity check
if len(path) == 0:
p, n = next_num(number, reverse=True), next_num(number, reverse=False)
if p is not None:
assert isinstance(number.left, Num) # Safety check
p.val += number.left.val
if n is not None:
assert isinstance(number.right, Num) # Safety check
n.val += number.right.val
return make_num(0)
parent, left, right = number.parent, number.left, number.right
if path[0]:
left = explode(number.left, path[1:])
else:
right = explode(number.right, path[1:])
return make_pair(parent=parent, left=left, right=right)
def split(number: Tree, path: Path) -> Tree:
def split_int(num: int, parent: Optional[Pair]) -> Tree:
assert num >= 0 # Sanity check
left = num // 2
right = num - left
res = make_pair(left=make_num(left), right=make_num(right), parent=parent)
return res
if len(path) == 0:
assert isinstance(number, Num) # Sanity check
return split_int(number.val, number.parent)
assert isinstance(number, Pair) # Sanity check
parent, left, right = number.parent, number.left, number.right
if path[0]:
left = split(number.left, path[1:])
else:
right = split(number.right, path[1:])
return make_pair(parent=parent, left=left, right=right)
def reduce(number: Tree) -> Tree:
path = explosion_path(number)
if path is not None:
return reduce(explode(number, path))
path = split_path(number)
if path is not None:
return reduce(split(number, path))
return number
def add(left: Tree, right: Tree) -> Tree:
return reduce(make_pair(left=left, right=right))
def magnitude(number: Tree) -> int:
if isinstance(number, Num):
return number.val
assert isinstance(number, Pair) # Safety check
return 3 * magnitude(number.left) + 2 * magnitude(number.right)
numbers = parse()
return max(
magnitude(add(deepcopy(l), deepcopy(r)))
for l, r in itertools.permutations(numbers, 2)
)
def main() -> None:
input = [line.strip() for line in sys.stdin.readlines()]
print(solve(input))
if __name__ == "__main__":
main()

100
2021/d18/ex2/input Normal file
View file

@ -0,0 +1,100 @@
[[3,[8,[2,1]]],[[[0,6],[0,2]],3]]
[[[1,[8,5]],[[3,9],0]],2]
[5,[[5,[3,8]],[7,4]]]
[1,[[[0,4],[8,5]],6]]
[[[1,[0,3]],2],[2,[0,[7,9]]]]
[[[4,[4,4]],[[7,2],[7,1]]],9]
[5,[4,4]]
[[0,[[2,6],[8,9]]],[[4,5],2]]
[[[8,2],0],3]
[[9,0],[3,3]]
[[[[5,2],2],5],5]
[[[1,6],[[0,4],[7,7]]],[[1,4],[[6,5],5]]]
[[[[4,1],[4,1]],[2,[5,5]]],[1,[0,[0,6]]]]
[[[[1,5],1],[8,4]],[9,[3,4]]]
[[1,[3,3]],[[[7,4],[8,1]],2]]
[3,[[[2,1],4],[5,4]]]
[6,[[0,[1,9]],[[4,0],8]]]
[5,[7,[7,[8,8]]]]
[[[[6,2],[5,8]],[5,[3,1]]],[[7,9],[[2,0],6]]]
[[[7,[7,9]],[5,7]],[[[9,3],[6,9]],[[1,2],[2,3]]]]
[[[[4,1],2],[1,[6,6]]],[[[2,2],[8,8]],4]]
[[[[3,7],4],8],[6,[[0,2],3]]]
[[[[1,8],2],3],[[9,[1,7]],[[0,0],[6,8]]]]
[[[9,[5,2]],7],[[8,6],[8,[1,2]]]]
[[[7,[1,0]],[[6,0],[8,4]]],[[[7,8],5],[3,[1,2]]]]
[[[[2,5],9],[[8,2],0]],0]
[0,[[[7,5],[4,1]],[5,[6,6]]]]
[[[[3,6],2],[[1,1],[6,6]]],0]
[[[[0,9],[2,5]],[2,[3,2]]],[6,3]]
[3,[[9,[1,4]],[[0,8],[4,6]]]]
[1,[[5,[5,9]],[9,0]]]
[[[6,8],4],[[[6,6],2],[[3,9],2]]]
[5,[[[7,5],[4,8]],0]]
[[9,[6,6]],[9,[[6,8],[6,4]]]]
[[[4,8],[0,[2,8]]],[7,[[4,5],[1,6]]]]
[[[6,[8,6]],2],[[[2,9],[2,4]],[0,2]]]
[[[0,[5,6]],[[3,8],3]],[[3,1],7]]
[[1,[8,1]],[1,[6,[7,1]]]]
[[[5,[9,6]],[3,5]],2]
[[3,7],[[[2,5],[4,1]],[3,[5,6]]]]
[[8,7],[[9,6],3]]
[[[[4,2],[4,8]],[7,[4,5]]],2]
[[[[6,7],6],3],[[[6,7],4],0]]
[[[0,1],[[9,1],[2,9]]],9]
[[[[8,5],[5,8]],[0,7]],[0,[8,[3,2]]]]
[[4,[[6,5],[1,9]]],[[[0,0],1],6]]
[[[[9,5],9],[2,[6,3]]],[[2,9],[6,9]]]
[[[7,[5,0]],1],[7,[[8,7],3]]]
[[[2,4],2],[[[3,0],6],[[0,2],[9,2]]]]
[[1,[[7,3],[4,3]]],[[[3,9],[1,1]],[3,6]]]
[[[[4,7],7],[[7,1],[2,3]]],[1,[[7,6],[5,6]]]]
[[0,[5,2]],0]
[[[[6,6],[4,8]],8],[[0,[7,4]],8]]
[[4,[7,2]],[[[0,8],1],[9,5]]]
[0,0]
[[[[3,7],6],3],[3,[[3,3],1]]]
[[[6,5],7],[[3,5],[[6,4],[4,9]]]]
[[4,[[7,9],9]],9]
[5,[8,[[7,4],1]]]
[[[[2,4],[5,7]],8],[[[7,6],[6,9]],[[3,9],[6,4]]]]
[[[4,8],3],[[[3,9],7],0]]
[0,[8,[[4,2],3]]]
[[[[0,1],[5,8]],[7,2]],[2,4]]
[[6,[8,[1,9]]],[[[6,5],[8,1]],[7,[6,4]]]]
[[9,3],[5,[0,6]]]
[[2,[7,[2,0]]],[[2,1],[5,5]]]
[[[0,[7,0]],[[0,4],[4,9]]],[8,[[6,1],[6,3]]]]
[[[[5,7],[3,2]],[0,[5,0]]],[[0,[1,6]],3]]
[[[[6,3],[9,5]],[9,9]],[[5,[8,3]],[[0,0],[0,3]]]]
[[6,[4,9]],[[[9,9],[8,4]],4]]
[0,[2,5]]
[[[[7,9],[1,2]],[3,3]],[[[7,2],7],[[1,6],0]]]
[[[[8,0],2],8],[[[1,5],9],9]]
[[[0,[6,9]],4],[[[4,8],5],4]]
[[6,[[0,3],4]],[0,[[8,3],1]]]
[[[1,2],[2,[3,3]]],[6,7]]
[[0,[[7,4],5]],[3,[[8,2],0]]]
[[[[0,1],[1,7]],[[2,7],[5,9]]],[[[7,0],0],[8,1]]]
[[6,4],[3,0]]
[[[[6,6],4],[5,1]],[7,3]]
[[[[9,2],3],[8,[4,8]]],7]
[[5,[[2,2],[9,2]]],[[[1,8],0],[8,[6,3]]]]
[2,[[0,0],[0,[9,9]]]]
[[4,4],[[6,5],[6,5]]]
[[[[9,1],2],4],5]
[[[[2,1],[3,1]],[[2,6],9]],5]
[[[9,[0,6]],7],[[8,3],[[8,1],2]]]
[[[6,[0,0]],[2,[0,0]]],[[[0,4],8],3]]
[[[[4,1],[2,9]],[6,5]],3]
[[9,[[9,4],8]],[[[5,5],3],[[3,4],4]]]
[8,[9,[[0,3],1]]]
[9,[[[6,0],4],9]]
[[6,[2,9]],[[[2,7],[5,3]],0]]
[[[4,1],5],[8,[[0,7],4]]]
[[[[2,5],5],[[8,2],[8,9]]],[[9,6],[[0,3],[2,3]]]]
[6,1]
[[1,7],4]
[[8,7],0]
[[[[5,4],7],5],[[[6,1],5],[5,[5,5]]]]
[[[6,[1,5]],[0,[7,0]]],[[[1,5],3],[5,[1,0]]]]