Added days 18-22, 24

master
Tibor Bizjak 2023-03-20 08:46:32 +01:00
parent 196399df32
commit 75cfd22369
6 changed files with 609 additions and 0 deletions

131
day18.py 100644
View File

@ -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))

34
day19.py 100644
View File

@ -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

264
day20.py 100644
View File

@ -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 """

29
day21.py 100644
View File

@ -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)))

70
day22.py 100644
View File

@ -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)

81
day24.py 100644
View File

@ -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)