Compare commits
No commits in common. "f777d1a3b41d02d19c833b5cd099d25a5d4867e3" and "8d671ff778c55af06e38806a3f96602b3301ad4f" have entirely different histories.
f777d1a3b4
...
8d671ff778
116
2021/day18.py
116
2021/day18.py
|
@ -1,116 +0,0 @@
|
||||||
from itertools import permutations
|
|
||||||
from math import ceil, floor
|
|
||||||
|
|
||||||
with open("input18.txt") as f:
|
|
||||||
lines = [eval(line.rstrip()) for line in f]
|
|
||||||
|
|
||||||
|
|
||||||
class Node:
|
|
||||||
|
|
||||||
def __init__(self, left=None, right=None, value=None):
|
|
||||||
self.left = left
|
|
||||||
self.right = right
|
|
||||||
self.value = value
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
if self.value is not None:
|
|
||||||
return str(self.value)
|
|
||||||
else:
|
|
||||||
return f"[{self.left}, {self.right}]"
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def of(elem):
|
|
||||||
if isinstance(elem, list):
|
|
||||||
return Node(left=Node.of(elem[0]), right=Node.of(elem[1]))
|
|
||||||
else:
|
|
||||||
return Node(value=elem)
|
|
||||||
|
|
||||||
|
|
||||||
class Exploder:
|
|
||||||
|
|
||||||
def reset(self):
|
|
||||||
self.prev_node = None
|
|
||||||
self.to_add = None
|
|
||||||
self.done = False
|
|
||||||
|
|
||||||
def explode(self, node, level=0):
|
|
||||||
if self.done:
|
|
||||||
next_node = node
|
|
||||||
elif node.value is None:
|
|
||||||
next_node = self.explode_pair(node, level)
|
|
||||||
else:
|
|
||||||
next_node = self.explode_leaf(node)
|
|
||||||
return next_node
|
|
||||||
|
|
||||||
def explode_pair(self, node, level):
|
|
||||||
if level == 4 and self.to_add is None:
|
|
||||||
self.to_add = node.right.value
|
|
||||||
if self.prev_node:
|
|
||||||
self.prev_node.value += node.left.value
|
|
||||||
next_node = Node(value=0)
|
|
||||||
else:
|
|
||||||
left = self.explode(node.left, level + 1)
|
|
||||||
if self.done:
|
|
||||||
next_node = Node(left=left, right=node.right)
|
|
||||||
else:
|
|
||||||
right = self.explode(node.right, level + 1)
|
|
||||||
next_node = Node(left=left, right=right)
|
|
||||||
return next_node
|
|
||||||
|
|
||||||
def explode_leaf(self, node):
|
|
||||||
if self.to_add is not None:
|
|
||||||
node.value += self.to_add
|
|
||||||
self.done = True
|
|
||||||
else:
|
|
||||||
self.prev_node = node
|
|
||||||
return node
|
|
||||||
|
|
||||||
|
|
||||||
def split_node(node):
|
|
||||||
split = False
|
|
||||||
if node.value is not None:
|
|
||||||
if node.value >= 10:
|
|
||||||
next_node = Node(
|
|
||||||
left=Node(value=floor(node.value / 2)),
|
|
||||||
right=Node(value=ceil(node.value / 2))
|
|
||||||
)
|
|
||||||
split = True
|
|
||||||
else:
|
|
||||||
next_node = node
|
|
||||||
else:
|
|
||||||
left, split = split_node(node.left)
|
|
||||||
if split:
|
|
||||||
next_node = Node(left=left, right=node.right)
|
|
||||||
else:
|
|
||||||
right, split = split_node(node.right)
|
|
||||||
next_node = Node(left=left, right=right)
|
|
||||||
return next_node, split
|
|
||||||
|
|
||||||
|
|
||||||
def redux(node):
|
|
||||||
exploder = Exploder()
|
|
||||||
while True:
|
|
||||||
exploder.reset()
|
|
||||||
node = exploder.explode(node)
|
|
||||||
if exploder.done or exploder.to_add is not None:
|
|
||||||
continue
|
|
||||||
node, changed = split_node(node)
|
|
||||||
if not changed:
|
|
||||||
break
|
|
||||||
return node
|
|
||||||
|
|
||||||
|
|
||||||
def magnitude(node):
|
|
||||||
if node.value is not None:
|
|
||||||
return node.value
|
|
||||||
return 3 * magnitude(node.left) + 2 * magnitude(node.right)
|
|
||||||
|
|
||||||
|
|
||||||
root = Node.of(lines[0])
|
|
||||||
for line in lines[1:]:
|
|
||||||
root = redux(Node(left=root, right=Node.of(line)))
|
|
||||||
print(magnitude(root))
|
|
||||||
print(max(
|
|
||||||
magnitude(redux(Node(left=Node.of(a), right=Node.of(b))))
|
|
||||||
for a, b in permutations(lines, 2)
|
|
||||||
))
|
|
|
@ -1,97 +0,0 @@
|
||||||
from collections import defaultdict
|
|
||||||
from itertools import combinations, permutations
|
|
||||||
|
|
||||||
with open("input19.txt") as f:
|
|
||||||
lines = [line.rstrip() for line in f]
|
|
||||||
scanners = []
|
|
||||||
for line in lines:
|
|
||||||
if line.startswith("--"):
|
|
||||||
scanner_id = int(line.split()[2])
|
|
||||||
positions = []
|
|
||||||
elif line:
|
|
||||||
positions.append(tuple(map(int, line.split(","))))
|
|
||||||
else:
|
|
||||||
scanners.append((scanner_id, positions))
|
|
||||||
scanners.append((scanner_id, positions))
|
|
||||||
|
|
||||||
|
|
||||||
def N(x, y, z): return x, y, z
|
|
||||||
def X(x, y, z): return x, -z, y
|
|
||||||
def Y(x, y, z): return -z, y, x
|
|
||||||
def add(v1, v2): return v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]
|
|
||||||
def sub(v1, v2): return v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]
|
|
||||||
|
|
||||||
|
|
||||||
orientations = [
|
|
||||||
"N", "X", "Y", "XX", "XY", "YX", "YY", "XXX", "XXY", "XYX", "XYY", "YXX",
|
|
||||||
"YYX", "YYY", "XXXY", "XXYX", "XXYY", "XYXX", "XYYY", "YXXX", "YYYX",
|
|
||||||
"XXXYX", "XYXXX", "XYYYX"
|
|
||||||
]
|
|
||||||
rotations = {k: globals()[k] for k in "NXY"}
|
|
||||||
|
|
||||||
|
|
||||||
def orient(x, y, z, orientation):
|
|
||||||
for axis in orientation:
|
|
||||||
x, y, z = rotations[axis[::-1]](x, y, z)
|
|
||||||
return x, y, z
|
|
||||||
|
|
||||||
|
|
||||||
overlaps = {}
|
|
||||||
oriented = {
|
|
||||||
sb_id: [(o, [orient(*pos, o) for pos in sb]) for o in orientations]
|
|
||||||
for sb_id, sb in scanners
|
|
||||||
}
|
|
||||||
# Using permutations is slower than combinations but it's simpler to find the
|
|
||||||
# transform path after.
|
|
||||||
for (sa_id, sa), (sb_id, _) in permutations(scanners, 2):
|
|
||||||
for orientation, osb in oriented[sb_id]:
|
|
||||||
diffs = defaultdict(int)
|
|
||||||
for ax, ay, az in sa:
|
|
||||||
for bx, by, bz in osb:
|
|
||||||
diff = (ax - bx, ay - by, az - bz)
|
|
||||||
diffs[diff] += 1
|
|
||||||
if diffs[diff] >= 12:
|
|
||||||
overlaps[(sa_id, sb_id)] = (orientation, diff)
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
else:
|
|
||||||
continue
|
|
||||||
break
|
|
||||||
|
|
||||||
|
|
||||||
def find_transform_to_0(from_s):
|
|
||||||
chains = [[p] for p in overlaps if p[0] == from_s]
|
|
||||||
seen = set()
|
|
||||||
while chains:
|
|
||||||
chain = chains.pop(0)
|
|
||||||
if chain[-1][1] == 0:
|
|
||||||
return chain
|
|
||||||
for p in overlaps:
|
|
||||||
if p[0] == chain[-1][1] and p not in seen:
|
|
||||||
seen.add(p)
|
|
||||||
chains.append(chain + [p])
|
|
||||||
|
|
||||||
|
|
||||||
def see_as_0(x, y, z, sid):
|
|
||||||
for t in find_transform_to_0(sid):
|
|
||||||
ori, move = overlaps[t[::-1]]
|
|
||||||
x, y, z = add(orient(x, y, z, ori), move)
|
|
||||||
return x, y, z
|
|
||||||
|
|
||||||
|
|
||||||
def see_scanner_as_0(sid, sdata):
|
|
||||||
for x, y, z in sdata:
|
|
||||||
yield see_as_0(x, y, z, sid)
|
|
||||||
|
|
||||||
|
|
||||||
beacons = set()
|
|
||||||
for scanner_id in range(len(scanners)):
|
|
||||||
beacons |= set(list(see_scanner_as_0(*scanners[scanner_id])))
|
|
||||||
print(len(beacons))
|
|
||||||
scanner_positions = [see_as_0(0, 0, 0, sid) for sid in range(len(scanners))]
|
|
||||||
print(max(
|
|
||||||
sum(map(abs, sub(spb, spa)))
|
|
||||||
for spa, spb in combinations(scanner_positions, 2)
|
|
||||||
))
|
|
Loading…
Reference in a new issue