Added days 18-22, 24
parent
196399df32
commit
75cfd22369
|
@ -0,0 +1,131 @@
|
|||
from functools import total_ordering
|
||||
from collections import defaultdict
|
||||
from itertools import combinations
|
||||
|
||||
@total_ordering
|
||||
class MaxBound:
|
||||
def __gt__(self, other):
|
||||
return True
|
||||
|
||||
def __eq__(self, other):
|
||||
return False
|
||||
|
||||
def __ge__(self, other):
|
||||
return True
|
||||
|
||||
def __le__(self, other):
|
||||
return False
|
||||
|
||||
def __repr__(self):
|
||||
return "MaxBound"
|
||||
|
||||
|
||||
def move_func(maze, i):
|
||||
w = maze.index('\n')+1
|
||||
possible = i+1, i-1, i+w, i-w
|
||||
return [j for j in possible if j < len(maze) and maze[j] not in ('\n', '#')]
|
||||
|
||||
def build_branch(maze, i, w=0, visited=None, edges=None):
|
||||
if visited == None:
|
||||
visited = defaultdict(MaxBound)
|
||||
edges = defaultdict(MaxBound)
|
||||
if visited[i] <= w:
|
||||
return
|
||||
visited[i] = w
|
||||
node = maze[i]
|
||||
if node != '.' and w > 0:
|
||||
edges[node] = min(edges[node], w)
|
||||
return
|
||||
for m in move_func(maze, i):
|
||||
build_branch(maze, m, w+1, visited, edges)
|
||||
return edges
|
||||
|
||||
def build_graph(maze):
|
||||
keys = [c for c in maze if c not in ('#', '.', '\n')]
|
||||
graph = dict()
|
||||
for key in keys:
|
||||
graph[key] = build_branch(maze, maze.index(key))
|
||||
return graph
|
||||
|
||||
def print_graph(graph):
|
||||
for node in graph:
|
||||
print(node, end='')
|
||||
for n, w in graph[node].items():
|
||||
print("--{}--> {}".format(w, n))
|
||||
print(' '*len(node), end='')
|
||||
print()
|
||||
|
||||
def unlock(graph, key):
|
||||
new = dict()
|
||||
if key not in graph:
|
||||
return graph
|
||||
for a in graph:
|
||||
if a == key:
|
||||
continue
|
||||
new[a] = defaultdict(MaxBound)
|
||||
for b, w in graph[a].items():
|
||||
if b == key:
|
||||
continue
|
||||
new[a][b] = w
|
||||
for a, b in combinations(graph[key].keys(), 2):
|
||||
w = graph[key][a] + graph[key][b]
|
||||
new[a][b] = new[b][a] = min(new[a][b], w)
|
||||
return new
|
||||
|
||||
def is_key(c):
|
||||
return c == c.lower()
|
||||
|
||||
def doorof(key):
|
||||
return key.upper() if key.isalpha() else None
|
||||
|
||||
def heu_sort(edges):
|
||||
return sorted(edges.items(), key=lambda x: x[1])
|
||||
|
||||
def search(graph, nodes, w=0, best=MaxBound(), d=0, mem=None, key=''):
|
||||
if mem == None:
|
||||
mem = defaultdict(MaxBound)
|
||||
|
||||
id = "".join(sorted(nodes)) + ''.join(sorted(n for n in graph if n not in nodes and is_key(n)))
|
||||
if w >= best or w >= mem[id]:
|
||||
return best
|
||||
|
||||
if len(mem) < 10**5:
|
||||
mem[id] = w
|
||||
|
||||
graph = unlock(graph, doorof(key))
|
||||
|
||||
for i, node in enumerate(nodes):
|
||||
bridged = unlock(graph, node)
|
||||
|
||||
for n, w2 in heu_sort(graph[node]):
|
||||
if not is_key(n):
|
||||
continue
|
||||
r = search(bridged, nodes[:i] + [n] + nodes[i+1:], w+w2, best, d+1, mem, n)
|
||||
best = min(best, r)
|
||||
if len(graph) == len(nodes) and w < best:
|
||||
best = w
|
||||
return best
|
||||
|
||||
def init_maze(maze):
|
||||
maze = list(maze)
|
||||
for i in range(maze.count('@')):
|
||||
maze[maze.index('@')] = str(i)
|
||||
return ''.join(maze), map(str, range(i+1))
|
||||
|
||||
def prepoc(puzzle_input):
|
||||
return puzzle_input
|
||||
|
||||
def partI(maze):
|
||||
graph = build_graph(maze)
|
||||
return search(graph, ['@'])
|
||||
|
||||
def partII(maze):
|
||||
maze = list(maze)
|
||||
i = maze.index('@')
|
||||
w = maze.index('\n')
|
||||
maze[i-1:i+2] = '###'
|
||||
maze[i-w-2:i-w+1] = maze[i+w:i+w+3] = '@#@'
|
||||
maze, nodes = init_maze(maze)
|
||||
graph = build_graph(maze)
|
||||
return search(graph, list(nodes))
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
from intcode import preproc
|
||||
|
||||
def partI(exe):
|
||||
mp = '\n'.join(''.join(str(next(exe(x, y))) for x in range(50))
|
||||
for y in range(50))
|
||||
return mp.count('1')
|
||||
|
||||
def partII(exe):
|
||||
def is_beam(x, y):
|
||||
return next(exe(x, y)) == 1
|
||||
|
||||
xs = [(0, 0)]
|
||||
y = 100
|
||||
width = 0
|
||||
while len(xs) < 100 or xs[0][1] - xs[-1][0] < 99:
|
||||
x = xs[-1][0]
|
||||
while not is_beam(x, y):
|
||||
x += 1
|
||||
start = x
|
||||
x += width
|
||||
while is_beam(x, y):
|
||||
width += 1
|
||||
x += 1
|
||||
width -= 1
|
||||
end = x-1
|
||||
xs.append((start, end))
|
||||
if len(xs) > 100:
|
||||
xs = xs[-100:]
|
||||
y += 1
|
||||
|
||||
X = start
|
||||
Y = y - 100
|
||||
return 10**4 * X + Y
|
||||
|
|
@ -0,0 +1,264 @@
|
|||
from collections import defaultdict
|
||||
|
||||
|
||||
WALL = '#'
|
||||
PATH = '.'
|
||||
|
||||
A = ("out", "AA")
|
||||
Z = ("out", "ZZ")
|
||||
|
||||
|
||||
class WeightedGraph(dict):
|
||||
def edges_of(self, a):
|
||||
return self[a]
|
||||
|
||||
def add_edge(self, a, b, w):
|
||||
self[a].append((b, w))
|
||||
self[b].append((a, w))
|
||||
|
||||
def add_node(self, a):
|
||||
if a not in self:
|
||||
self[a] = []
|
||||
|
||||
def rm_loops(self):
|
||||
for a in self.keys():
|
||||
for i, (b, w) in enumerate(self[a]):
|
||||
if a == b:
|
||||
del self[a][i]
|
||||
|
||||
def min_path(self, a, b):
|
||||
weights = dict()
|
||||
visited = set()
|
||||
|
||||
node, w = a, 0
|
||||
while node != b:
|
||||
for c, edge_w in self.edges_of(node):
|
||||
if c in visited:
|
||||
continue
|
||||
if c in weights and weights[c] <= w + edge_w:
|
||||
continue
|
||||
weights[c] = w + edge_w
|
||||
node, w = min(weights.items() , key=lambda nw: nw[1])
|
||||
del weights[node]
|
||||
visited.add(node)
|
||||
return w
|
||||
|
||||
def min_paths(self, a):
|
||||
weights = dict()
|
||||
def aux(a, w):
|
||||
if a in weights.keys() and weights[a] <= w:
|
||||
return
|
||||
|
||||
weights[a] = w
|
||||
|
||||
for b, ab_w in graph[node]:
|
||||
best = aux(b, w + w2, best)
|
||||
|
||||
aux(a, 0, None)
|
||||
return weights
|
||||
|
||||
def copy(self):
|
||||
return WeightedGraph({a : [bw for bw in bws]for a, bws in self.items()})
|
||||
|
||||
|
||||
def neighbours(maze, vec):
|
||||
x, y = vec
|
||||
ns = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)]
|
||||
width = max(map(len, maze))
|
||||
return filter(lambda vec: 0 <= vec[0] < len(maze) and 0 <= vec[1] < len(maze[x]), ns)
|
||||
|
||||
def find_full_name(maze, vec):
|
||||
x, y = vec
|
||||
A = maze[x][y]
|
||||
assert A.isalpha()
|
||||
for a, b in neighbours(maze, (x, y)):
|
||||
B = maze[a][b]
|
||||
if B.isalpha():
|
||||
return ''.join(sorted(A+B))
|
||||
return None
|
||||
|
||||
def find_node(maze, vec):
|
||||
x, y = vec
|
||||
width = max(map(len, maze))
|
||||
name = find_full_name(maze, (x, y))
|
||||
if x < 2 or len(maze) - 2 <= x or y < 2 or width - 2 <= y:
|
||||
loc = "out"
|
||||
else:
|
||||
loc = "in"
|
||||
node = (loc, name)
|
||||
return node
|
||||
|
||||
def other_node(packed):
|
||||
loc, name = packed
|
||||
if loc == "in":
|
||||
return ("out", name)
|
||||
return ("in", name)
|
||||
|
||||
def make_graph(maze):
|
||||
graph = dict()
|
||||
def search(node, vec, w, visited):
|
||||
x, y = vec
|
||||
if (x, y) in visited:
|
||||
return
|
||||
visited.append((x, y))
|
||||
c = maze[x][y]
|
||||
if c.isalpha():
|
||||
node2 = find_node(maze, (x, y))
|
||||
if node != node2:
|
||||
graph[node].append((node2, w-1))
|
||||
elif c == PATH:
|
||||
for n in neighbours(maze, (x, y)):
|
||||
search(node, n, w+1, visited)
|
||||
|
||||
visited = []
|
||||
for x, line in enumerate(maze):
|
||||
for y, c in enumerate(line):
|
||||
if not c.isalpha():
|
||||
continue
|
||||
node = find_node(maze, (x, y))
|
||||
if node in visited:
|
||||
continue
|
||||
paths = [(x,y) for (x,y) in neighbours(maze, (x,y)) if maze[x][y] == PATH]
|
||||
if paths == []:
|
||||
continue
|
||||
visited.append(node)
|
||||
start = paths[0]
|
||||
graph[node] = []
|
||||
search(node, start, 0, [])
|
||||
graph = WeightedGraph(graph)
|
||||
graph.rm_loops()
|
||||
return graph
|
||||
|
||||
def preproc(puzzle_input):
|
||||
maze = puzzle_input.split('\n')
|
||||
return make_graph(maze)
|
||||
|
||||
def partI(graph):
|
||||
graph = graph.copy()
|
||||
for node in graph.keys():
|
||||
if node[1] in ["AA", "ZZ"]:
|
||||
continue
|
||||
graph.add_edge(node, other_node(node), 1)
|
||||
return graph.min_path(A, Z)
|
||||
|
||||
class RecGraph(WeightedGraph):
|
||||
def edges_of(self, packed):
|
||||
lvl, a = packed
|
||||
edges = [((lvl, b), w) for b, w in self[a]]
|
||||
|
||||
loc, _ = a
|
||||
|
||||
lvl2 = lvl + (1 if loc == "in" else -1)
|
||||
b = other_node(a)
|
||||
|
||||
if b not in self or lvl2 < 0:
|
||||
return edges
|
||||
b_edge = (lvl2, b), 1
|
||||
if lvl2 < lvl:
|
||||
edges = [b_edge] + edges
|
||||
else:
|
||||
edges.append(b_edge)
|
||||
return edges
|
||||
|
||||
|
||||
def partII(graph):
|
||||
graph = RecGraph(graph)
|
||||
return graph.min_path((0, A), (0, Z))
|
||||
|
||||
test =""" A
|
||||
A
|
||||
#######.#########
|
||||
#######.........#
|
||||
#######.#######.#
|
||||
#######.#######.#
|
||||
#######.#######.#
|
||||
##### B ###.#
|
||||
BC...## C ###.#
|
||||
##.## ###.#
|
||||
##...DE F ###.#
|
||||
##### G ###.#
|
||||
#########.#####.#
|
||||
DE..#######...###.#
|
||||
#.#########.###.#
|
||||
FG..#########.....#
|
||||
###########.#####
|
||||
Z
|
||||
Z """
|
||||
|
||||
|
||||
test2 = """ A
|
||||
A
|
||||
#################.#############
|
||||
#.#...#...................#.#.#
|
||||
#.#.#.###.###.###.#########.#.#
|
||||
#.#.#.......#...#.....#.#.#...#
|
||||
#.#########.###.#####.#.#.###.#
|
||||
#.............#.#.....#.......#
|
||||
###.###########.###.#####.#.#.#
|
||||
#.....# A C #.#.#.#
|
||||
####### S P #####.#
|
||||
#.#...# #......VT
|
||||
#.#.#.# #.#####
|
||||
#...#.# YN....#.#
|
||||
#.###.# #####.#
|
||||
DI....#.# #.....#
|
||||
#####.# #.###.#
|
||||
ZZ......# QG....#..AS
|
||||
###.### #######
|
||||
JO..#.#.# #.....#
|
||||
#.#.#.# ###.#.#
|
||||
#...#..DI BU....#..LF
|
||||
#####.# #.#####
|
||||
YN......# VT..#....QG
|
||||
#.###.# #.###.#
|
||||
#.#...# #.....#
|
||||
###.### J L J #.#.###
|
||||
#.....# O F P #.#...#
|
||||
#.###.#####.#.#####.#####.###.#
|
||||
#...#.#.#...#.....#.....#.#...#
|
||||
#.#####.###.###.#.#.#########.#
|
||||
#...#.#.....#...#.#.#.#.....#.#
|
||||
#.###.#####.###.###.#.#.#######
|
||||
#.#.........#...#.............#
|
||||
#########.###.###.#############
|
||||
B J C
|
||||
U P P """
|
||||
|
||||
test3 = """ Z L X W C
|
||||
Z P Q B K
|
||||
###########.#.#.#.#######.###############
|
||||
#...#.......#.#.......#.#.......#.#.#...#
|
||||
###.#.#.#.#.#.#.#.###.#.#.#######.#.#.###
|
||||
#.#...#.#.#...#.#.#...#...#...#.#.......#
|
||||
#.###.#######.###.###.#.###.###.#.#######
|
||||
#...#.......#.#...#...#.............#...#
|
||||
#.#########.#######.#.#######.#######.###
|
||||
#...#.# F R I Z #.#.#.#
|
||||
#.###.# D E C H #.#.#.#
|
||||
#.#...# #...#.#
|
||||
#.###.# #.###.#
|
||||
#.#....OA WB..#.#..ZH
|
||||
#.###.# #.#.#.#
|
||||
CJ......# #.....#
|
||||
####### #######
|
||||
#.#....CK #......IC
|
||||
#.###.# #.###.#
|
||||
#.....# #...#.#
|
||||
###.### #.#.#.#
|
||||
XF....#.# RF..#.#.#
|
||||
#####.# #######
|
||||
#......CJ NM..#...#
|
||||
###.#.# #.###.#
|
||||
RE....#.# #......RF
|
||||
###.### X X L #.#.#.#
|
||||
#.....# F Q P #.#.#.#
|
||||
###.###########.###.#######.#########.###
|
||||
#.....#...#.....#.......#...#.....#.#...#
|
||||
#####.#.###.#######.#######.###.###.#.#.#
|
||||
#.......#.......#.#.#.#.#...#...#...#.#.#
|
||||
#####.###.#####.#.#.#.#.###.###.#.###.###
|
||||
#.......#.....#.#...#...............#...#
|
||||
#############.#.#.###.###################
|
||||
A O F N
|
||||
A A D M """
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
from intcode import preproc
|
||||
from lib import last
|
||||
|
||||
logic1 = """\
|
||||
NOT A J
|
||||
NOT B T
|
||||
OR T J
|
||||
NOT C T
|
||||
OR T J
|
||||
AND D J
|
||||
"""
|
||||
logic2 = logic1 + """\
|
||||
NOT I T
|
||||
NOT T T
|
||||
OR F T
|
||||
AND E T
|
||||
OR H T
|
||||
AND T J
|
||||
"""
|
||||
|
||||
def partI(exe):
|
||||
log = logic1 + 'WALK\n'
|
||||
return last(exe(input_iter = map(ord, log)))
|
||||
|
||||
def partII(exe):
|
||||
log = logic2 + 'RUN\n'
|
||||
return last(exe(input_iter = map(ord, log)))
|
||||
|
||||
|
|
@ -0,0 +1,70 @@
|
|||
from functools import reduce
|
||||
|
||||
deal = lambda: (-1, -1)
|
||||
dealN = lambda n: (n, 0)
|
||||
cutN = lambda n: (1, -n)
|
||||
id = (1, 0)
|
||||
|
||||
instructions = {"deal into new stack" : deal,
|
||||
"deal with increment" : dealN,
|
||||
"cut" : cutN
|
||||
}
|
||||
|
||||
def egcd(a, b):
|
||||
if a == 0:
|
||||
return (b, 0, 1)
|
||||
else:
|
||||
g, y, x = egcd(b % a, a)
|
||||
return (g, x - (b // a) * y, y)
|
||||
|
||||
def modinv(a, m):
|
||||
g, x, y = egcd(a, m)
|
||||
if g != 1:
|
||||
raise Exception('modular inverse does not exist')
|
||||
else:
|
||||
return x % m
|
||||
|
||||
def comp(ab, cd, mod):
|
||||
a, b = ab
|
||||
c, d = cd
|
||||
return ((a*c) % mod, (a*d + b) % mod)
|
||||
|
||||
def apply(ab, x, mod):
|
||||
a, b = ab
|
||||
return (a*x + b) % mod
|
||||
|
||||
def inverse(ab, mod):
|
||||
a, b = ab
|
||||
x = modinv(a, mod)
|
||||
return (x, (-b * x) % mod)
|
||||
|
||||
def powcomp(ab, pw, mod):
|
||||
a, b = ab
|
||||
return (pow(a, pw, mod), ((pow(a, pw, mod) - 1) * modinv(a-1, mod) * b) % mod)
|
||||
|
||||
def parse(line):
|
||||
for inst in instructions.keys():
|
||||
if line[:len(inst)] == inst:
|
||||
return instructions[inst](*map(int, line[len(inst):].split()))
|
||||
return NotImplemented
|
||||
|
||||
def compile(program, deck):
|
||||
return reduce(lambda f1, f2: comp(f2, f1, deck), program, id)
|
||||
|
||||
|
||||
def preproc(puzzle_input):
|
||||
return list(map(parse, puzzle_input.split('\n')))
|
||||
|
||||
def partI(program):
|
||||
deck = 10007
|
||||
shuffle = compile(program, deck)
|
||||
return apply(shuffle, 2019, deck)
|
||||
|
||||
def partII(program):
|
||||
deck = 119315717514047
|
||||
pw = 101741582076661
|
||||
shuffle = compile(program, deck)
|
||||
invshuffle = inverse(shuffle, deck)
|
||||
|
||||
repeated = powcomp(invshuffle, pw, deck)
|
||||
return apply(repeated, 2020, deck)
|
|
@ -0,0 +1,81 @@
|
|||
|
||||
BUG = '#'
|
||||
EMPTY = '.'
|
||||
LEVEL = '.'*12 + '?' + '.'*12
|
||||
|
||||
edges = {7 : range(0, 5),
|
||||
11 : range(0, 21, 5),
|
||||
13 : range(4, 25, 5),
|
||||
17 : range(20, 25)}
|
||||
|
||||
|
||||
def neighbours(lvl, i):
|
||||
r = []
|
||||
if i > 4:
|
||||
r.append(i-5)
|
||||
if i < 20:
|
||||
r.append(i+5)
|
||||
if (i % 5) < 4:
|
||||
r.append(i+1)
|
||||
if (i % 5) > 0:
|
||||
r.append(i-1)
|
||||
return list(zip(4*[lvl], r))
|
||||
|
||||
def rec_neigh(lvl, i):
|
||||
ns = neighbours(lvl, i)
|
||||
for inner, outer in edges.items():
|
||||
if i == inner:
|
||||
ns += [(lvl+1, j) for j in outer]
|
||||
elif i in outer:
|
||||
ns += [(lvl-1, inner)]
|
||||
return ns
|
||||
|
||||
def fate(val, ns):
|
||||
if val == BUG and ns.count(BUG) != 1:
|
||||
return EMPTY
|
||||
elif val == EMPTY and ns.count(BUG) in (1, 2):
|
||||
return BUG
|
||||
return val
|
||||
|
||||
def trim(stack):
|
||||
while BUG not in stack[-1]:
|
||||
stack.pop()
|
||||
while BUG not in stack[0]:
|
||||
stack.pop(0)
|
||||
|
||||
def next(bug_stack, nfunc=rec_neigh):
|
||||
bug_stack = 2*[LEVEL] + bug_stack + 2*[LEVEL]
|
||||
new = []
|
||||
for lvl in range(1, len(bug_stack)-1):
|
||||
new.append('')
|
||||
for i, val in enumerate(bug_stack[lvl]):
|
||||
ns = nfunc(lvl, i)
|
||||
nvals = [bug_stack[l][j] for l, j in ns]
|
||||
new[-1] += fate(val, nvals)
|
||||
trim(new)
|
||||
return new
|
||||
|
||||
def bugs_sim(bug_stack, i):
|
||||
for i in range(i):
|
||||
bug_stack = next(bug_stack)
|
||||
return bug_stack
|
||||
|
||||
def biodiversity(bugs):
|
||||
return sum(2**pw for pw, c in enumerate(bugs) if c == BUG)
|
||||
|
||||
def preproc(puzzle_input):
|
||||
return puzzle_input.replace('\n', '')
|
||||
|
||||
def partI(bugs):
|
||||
layouts = set()
|
||||
b = bugs
|
||||
while b not in layouts:
|
||||
layouts.add(b)
|
||||
b = next([b], neighbours)[0]
|
||||
|
||||
return biodiversity(b)
|
||||
|
||||
def partII(bugs):
|
||||
bugs = bugs[:12] + '?' + bugs[13:]
|
||||
stack = [bugs]
|
||||
return ''.join(bugs_sim(stack, 200)).count(BUG)
|
Loading…
Reference in New Issue