advent-of-code/2016-python2/output/day_11.py

136 lines
4 KiB
Python

import re
from collections import Counter, deque
from itertools import combinations
D = {1: [2], 2: [1, 3], 3: [2, 4], 4: [3]}
def solve(data):
def parse(row):
return sorted(
[
"".join(nt).upper()
for nt in re.findall(r"(\w)\S+(?:-compatible)? (g|m)", row)
]
)
f = dict([(i, parse(row)) for i, row in enumerate(data.splitlines(), start=1)])
E = sum(map(len, f.values()))
p1 = bfs(1, f, E)
adjusted = data.splitlines()
adjusted[
0
] += """
An elerium generator.
An elerium-compatible microchip.
A dilithium generator.
A dilithium-compatible microchip.
"""
f = dict([(i, parse(row)) for i, row in enumerate(adjusted, start=1)])
E = sum(map(len, f.values()))
p2 = bfs(1, f, E)
return p1, p2
def bfs(S, m, E):
seen = set()
q = deque([(S, m, 0)])
while q:
e, m, w = q.popleft()
cs = seen_checksum(e, m) # reddit wisdom, see function for more info
if cs in seen:
continue
seen.add(cs)
if len(m[4]) == E:
return w
for n, b, a in valid_next_moves(e, m):
ns = m.copy()
ns[e] = a
ns[n] = b
q.append((n, ns, w + 1))
return None
def valid_next_moves(e, S):
g = []
for n in D[e]:
for o in [[x] for x in S[e]] + [list(x) for x in combinations(S[e], 2)]:
a = sorted(x for x in S[e] if x not in o)
b = sorted(S[n] + o)
if is_valid(a) and is_valid(b):
g.append((n, b, a))
return g
def is_valid(f):
g = [x for x in f if x.endswith("G")]
if not g:
return True
mc = [x for x in f if x.endswith("M")]
return all(f"{m[0]}G" in f for m in mc)
def seen_checksum(e, s):
# To speed up execution, a handy trick was mentioned on several
# reddit threads.
#
# The vanilla BFS method is to store the complete state (elevator
# position + all floors), which this code like many others did initially.
# This is fine, but will lead to really long execution times.
#
# The common wisdom boils down to one thing: it does not matter _what_
# name the microchips and generators have. Only the arrangement (counts) of
# any microchips or generators across the floors do.
#
# For example, these are the same as a checksum to determine if a state has
# been seen:
#
# F2 . HG . . F2 . LG . .
# F1 E . HM LM F1 E . LM HM
#
# The H's or L's do not matter, only the M's and G's do.
#
# So by storing the M's and the G's as a checksum, along with the
# elevator position, the program is a lot faster.
#
# Reddit post, giving hints:
# https://www.reddit.com/r/adventofcode/comments/5hoia9/comment/db1v1ws/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
# Blog post, providing code and reasoning:
# https://eddmann.com/posts/advent-of-code-2016-day-11-radioisotope-thermoelectric-generators/
# uncomment below line to get speed boost:
# %> 2.59s user 0.02s system 99% cpu 2.622 total
return e, tuple(tuple(Counter(x[-1] for x in f).most_common()) for f in s.values())
# uncomment below line to use vanilla BFS visited storage
# %> 896.11s user 2.58s system 99% cpu 15:01.35 total
# return e, tuple((f, tuple(mg)) for f, mg in s.items())
def M(s, e):
d = {}
for k, v in s.items():
for x in v:
d[x] = k - 1
l = len(d)
d = [
[v if x == k else ". " for x in range(l)]
for v, k in sorted(d.items(), key=lambda kv: kv[0])
]
m = [("F1", "F2", "F3", "F4"), ["E " if x == e - 1 else ". " for x in range(l)]] + d
for r in list(zip(*m))[::-1]:
print(" ".join(r))
print("")
if __name__ == "__main__":
with open("./input/11.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)