From 75cfd22369ccdb53938fdb529720a98da2979133 Mon Sep 17 00:00:00 2001 From: Tibor Bizjak Date: Mon, 20 Mar 2023 08:46:32 +0100 Subject: [PATCH] Added days 18-22, 24 --- day18.py | 131 +++++++++++++++++++++++++++ day19.py | 34 +++++++ day20.py | 264 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ day21.py | 29 ++++++ day22.py | 70 +++++++++++++++ day24.py | 81 +++++++++++++++++ 6 files changed, 609 insertions(+) create mode 100644 day18.py create mode 100644 day19.py create mode 100644 day20.py create mode 100644 day21.py create mode 100644 day22.py create mode 100644 day24.py diff --git a/day18.py b/day18.py new file mode 100644 index 0000000..d547ab3 --- /dev/null +++ b/day18.py @@ -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)) + diff --git a/day19.py b/day19.py new file mode 100644 index 0000000..aeeda8f --- /dev/null +++ b/day19.py @@ -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 + diff --git a/day20.py b/day20.py new file mode 100644 index 0000000..51fe0fd --- /dev/null +++ b/day20.py @@ -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 """ + diff --git a/day21.py b/day21.py new file mode 100644 index 0000000..5e088b9 --- /dev/null +++ b/day21.py @@ -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))) + + diff --git a/day22.py b/day22.py new file mode 100644 index 0000000..3e15e9e --- /dev/null +++ b/day22.py @@ -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) diff --git a/day24.py b/day24.py new file mode 100644 index 0000000..b5c7289 --- /dev/null +++ b/day24.py @@ -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)