├── .gitignore ├── README.md ├── day01 ├── alt.py └── main.py ├── day02 └── main.py ├── day03 ├── alt.py └── main.py ├── day04 └── main.py ├── day05 ├── alt.py └── main.py ├── day06 ├── alt.py └── main.py ├── day07 ├── alt.py └── main.py ├── day08 └── main.py ├── day09 └── main.py ├── day10 ├── alt.py └── main.py ├── day11 └── main.py ├── day12 └── main.py ├── day13 └── main.py ├── day14 └── main.py ├── day15 ├── alt.py └── main.py ├── day16 ├── alt.py └── main.py ├── day17 └── main.py ├── day18 └── main.py ├── day19 ├── alt.py └── main.py ├── day20 └── main.py ├── day21 └── main.py ├── day22 └── main.py ├── day23 └── main.py ├── day24 └── main.py └── day25 └── main.py /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2020 2 | 3 | | Day | Name | Solution | 4 | |---|---|---| 5 | |[01](https://adventofcode.com/2020/day/1)|Report Repair|[py](/day01/main.py), [alt](/day01/alt.py)| 6 | |[02](https://adventofcode.com/2020/day/2)|Password Philosophy|[py](/day02/main.py)| 7 | |[03](https://adventofcode.com/2020/day/3)|Toboggan Trajectory|[py](/day03/main.py), [alt](/day03/alt.py)| 8 | |[04](https://adventofcode.com/2020/day/4)|Passport Processing|[py](/day04/main.py)| 9 | |[05](https://adventofcode.com/2020/day/5)|Binary Boarding|[py](/day05/main.py), [alt](/day05/alt.py)| 10 | |[06](https://adventofcode.com/2020/day/6)|Custom Customs|[py](/day06/main.py), [alt](/day06/alt.py)| 11 | |[07](https://adventofcode.com/2020/day/7)|Handy Haversacks|[py](/day07/main.py), [alt](/day07/alt.py)| 12 | |[08](https://adventofcode.com/2020/day/8)|Handheld Halting|[py](/day08/main.py)| 13 | |[09](https://adventofcode.com/2020/day/9)|Encoding Error|[py](/day09/main.py)| 14 | |[10](https://adventofcode.com/2020/day/10)|Adapter Array|[py](/day10/main.py), [alt](/day10/alt.py)| 15 | |[11](https://adventofcode.com/2020/day/11)|Seating System|[py](/day11/main.py)| 16 | |[12](https://adventofcode.com/2020/day/12)|Rain Risk|[py](/day12/main.py)| 17 | |[13](https://adventofcode.com/2020/day/13)|Shuttle Search|[py](/day13/main.py)| 18 | |[14](https://adventofcode.com/2020/day/14)|Docking Data|[py](/day14/main.py)| 19 | |[15](https://adventofcode.com/2020/day/15)|Rambunctious Recitation|[py](/day15/main.py), [alt](/day15/alt.py)| 20 | |[16](https://adventofcode.com/2020/day/16)|Ticket Translation|[py](/day16/main.py), [alt](/day16/alt.py)| 21 | |[17](https://adventofcode.com/2020/day/17)|Conway Cubes|[py](/day17/main.py)| 22 | |[18](https://adventofcode.com/2020/day/18)|Operation Order|[py](/day18/main.py)| 23 | |[19](https://adventofcode.com/2020/day/19)|Monster Messages|[py](/day19/main.py), [alt](/day19/alt.py)| 24 | |[20](https://adventofcode.com/2020/day/20)|Jurassic Jigsaw|[py](/day20/main.py)| 25 | |[21](https://adventofcode.com/2020/day/21)|Allergen Assessment|[py](/day21/main.py)| 26 | |[22](https://adventofcode.com/2020/day/22)|Crab Combat|[py](/day22/main.py)| 27 | |[23](https://adventofcode.com/2020/day/23)|Crab Cups|[py](/day23/main.py)| 28 | |[24](https://adventofcode.com/2020/day/24)|Lobby Layout|[py](/day24/main.py)| 29 | |[25](https://adventofcode.com/2020/day/25)|Combo Breaker|[py](/day25/main.py)| 30 | -------------------------------------------------------------------------------- /day01/alt.py: -------------------------------------------------------------------------------- 1 | import itertools 2 | import numpy as np 3 | 4 | with open("input.txt") as f: 5 | ns = [int(x.strip()) for x in f] 6 | 7 | print([np.product(n) for n in itertools.combinations(ns, 2) if sum(n) == 2020][0]) 8 | print([np.product(n) for n in itertools.combinations(ns, 3) if sum(n) == 2020][0]) 9 | 10 | #### 11 | 12 | s = set(ns) 13 | print([a * (2020 - a) for a in s if 2020 - a in s][0]) 14 | print([a * b * (2020 - a - b) for a in s for b in s if 2020 - a - b in s][0]) 15 | 16 | -------------------------------------------------------------------------------- /day01/main.py: -------------------------------------------------------------------------------- 1 | with open("input.txt") as f: 2 | l = [int(x.strip()) for x in f] 3 | 4 | for i, n1 in enumerate(l): 5 | for j, n2 in enumerate(l[i+1:]): 6 | if n1 + n2 == 2020: 7 | print(n1 * n2) 8 | for n3 in l[i+j+1:]: 9 | if n1 + n2 + n3 == 2020: 10 | print(n1 * n2 * n3) 11 | 12 | # Output: 13 | # 485739 14 | # 161109702 -------------------------------------------------------------------------------- /day02/main.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | with open("input.txt") as f: 4 | lines = [x.strip() for x in f] 5 | 6 | part1 = 0 7 | part2 = 0 8 | 9 | for l in lines: 10 | chunks = l.split() 11 | bounds = chunks[0].split("-") 12 | lb, ub = int(bounds[0]), int(bounds[1]) 13 | c = chunks[1][0] 14 | pw = chunks[2] 15 | 16 | counts = Counter(pw)[c] 17 | if lb <= counts <= ub: 18 | part1 += 1 19 | 20 | c_pos1, c_pos2 = pw[lb-1], pw[ub-1] 21 | if (c == c_pos1) ^ (c == c_pos2): 22 | part2 += 1 23 | 24 | print(part1) 25 | print(part2) 26 | -------------------------------------------------------------------------------- /day03/alt.py: -------------------------------------------------------------------------------- 1 | from pathlib import Path 2 | import numpy as np 3 | 4 | lines = Path("input.txt").read_text().strip().split("\n") 5 | width = len(lines[0]) 6 | result = [] 7 | for dx, dy in [(3,1), (1,1), (5,1), (7,1), (1,2)]: 8 | ctr = 0 9 | x = 0 10 | for line in lines[::dy]: 11 | ctr += line[x] == '#' 12 | x = (x + dx) % width 13 | result.append(ctr) 14 | 15 | print(result[0]) 16 | print(np.prod(result)) 17 | -------------------------------------------------------------------------------- /day03/main.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | with open("input.txt") as f: 4 | lines = [x.strip() for x in f] 5 | 6 | width = len(lines[0]) 7 | result = [] 8 | for dx, dy in [(3,1), (1,1), (5,1), (7,1), (1,2)]: 9 | ctr = 0 10 | for i in range(len(lines) // dy): 11 | y = i * dy 12 | x = (i * dx) % width 13 | if lines[y][x] == "#": 14 | ctr += 1 15 | result.append(ctr) 16 | 17 | print(result[0]) 18 | print(math.prod(result)) 19 | -------------------------------------------------------------------------------- /day04/main.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | with open("input.txt") as f: 4 | lines = [x.strip() for x in f] 5 | 6 | expected = set("byr iyr eyr hgt hcl ecl pid".split()) 7 | passports = [] 8 | ctr = 0 9 | while ctr < len(lines): 10 | k = "" 11 | while len(lines[ctr]) > 0: 12 | k += " " + lines[ctr] 13 | ctr += 1 14 | if ctr == len(lines): 15 | break 16 | passports.append(k) 17 | ctr += 1 18 | 19 | valids = 0 20 | valids2 = 0 21 | for p in passports: 22 | chunks = p.split() 23 | actual = set([c.split(":")[0] for c in chunks]) 24 | if len(expected - actual) == 0: 25 | valids += 1 26 | d = dict([c.split(":") for c in chunks]) 27 | c1 = len(d["byr"]) == 4 and 1920 <= int(d["byr"]) <= 2002 28 | c2 = len(d["iyr"]) == 4 and 2010 <= int(d["iyr"]) <= 2020 29 | c3 = len(d["eyr"]) == 4 and 2020 <= int(d["eyr"]) <= 2030 30 | c4 = (re.match("^\d\d\dcm$", d["hgt"]) is not None and 150 <= int(d["hgt"][:-2]) <= 193) or (re.match("^\d\din$", d["hgt"]) is not None and 59 <= int(d["hgt"][:-2]) <= 76) 31 | c5 = re.match("^#[a-f0-9]{6}$", d["hcl"]) is not None 32 | c6 = d["ecl"] in ("amb blu brn gry grn hzl oth".split()) 33 | c7 = re.match("^\d{9}$", d["pid"]) is not None 34 | if all([c1, c2, c3, c4, c5, c6, c7]): 35 | valids2 += 1 36 | 37 | print(valids) 38 | print(valids2) 39 | -------------------------------------------------------------------------------- /day05/alt.py: -------------------------------------------------------------------------------- 1 | lines = open("input.txt").read().translate(''.maketrans('FBLR','0101')).splitlines() 2 | s = set(int(line,2) for line in lines) 3 | print(max(s),[x for x in range(max(s)) if x not in s and {x-1,x+1}.issubset(s)][0]) 4 | -------------------------------------------------------------------------------- /day05/main.py: -------------------------------------------------------------------------------- 1 | g = lambda s: int("".join([str(int(n == 'B' or n == 'R')) for n in s]), 2) 2 | 3 | with open("input.txt") as f: 4 | seats = set(g(x.strip()) for x in f) 5 | 6 | print(max(seats)) 7 | for i in range(min(seats), max(seats)): 8 | if i not in seats: 9 | print(i) 10 | -------------------------------------------------------------------------------- /day06/alt.py: -------------------------------------------------------------------------------- 1 | with open("input.txt") as f: 2 | grps = f.read().split('\n\n') 3 | 4 | print(sum(len(set.union(*[set(i) for i in a.split()])) for a in grps)) 5 | print(sum(len(set.intersection(*[set(i) for i in a.split()])) for a in grps)) 6 | -------------------------------------------------------------------------------- /day06/main.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | with open("input.txt") as f: 4 | grps = [x.strip().split() for x in f.read().split("\n\n")] 5 | 6 | print(sum([len(Counter("".join(g))) for g in grps])) 7 | print(sum([len([v for v in Counter("".join(g)).values() if v == len(g)]) for g in grps])) 8 | -------------------------------------------------------------------------------- /day07/alt.py: -------------------------------------------------------------------------------- 1 | from parse import findall, search 2 | 3 | def parse(b): 4 | return search('{} bags', b)[0], [*findall('{:d} {} bag', b)] 5 | 6 | b = dict(map(parse, open('input.txt'))) 7 | 8 | def f(c): return any(d == t or f(d) for _, d in b[c]) 9 | def g(c): return sum(n * (1 + g(d)) for n, d in b[c]) 10 | 11 | t = 'shiny gold' 12 | print(sum(map(f, b)), g(t)) 13 | -------------------------------------------------------------------------------- /day07/main.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | with open("input.txt") as f: 4 | lines = [x.strip() for x in f] 5 | 6 | d = defaultdict(list) 7 | d2 = defaultdict(list) 8 | 9 | for line in lines: 10 | color1 = " ".join(line.split()[:2]) 11 | chunks = line.split("contain ")[1].split(", ") 12 | for c in chunks: 13 | color2 = " ".join(c.split()[1:3]) 14 | d[color2].append(color1) 15 | q = c.split()[0] 16 | if q == "no": 17 | d2[color1].append((color2, 0)) 18 | else: 19 | d2[color1].append((color2, int(q))) 20 | 21 | def check(color, k): 22 | for c in d[color]: 23 | k.add(c) 24 | check(c, k) 25 | return k 26 | 27 | print(len(check("shiny gold", set()))) 28 | 29 | def count(color): 30 | return 1 + sum(n * count(c) for c, n in d2[color] if n > 0) 31 | 32 | print(count("shiny gold")-1) 33 | -------------------------------------------------------------------------------- /day08/main.py: -------------------------------------------------------------------------------- 1 | from parse import search 2 | 3 | with open("input.txt") as f: 4 | code = [search("{} {:d}", x).fixed for x in f] 5 | 6 | def execute(instrs): 7 | visited = set() 8 | pc = 0 9 | acc = 0 10 | while True: 11 | if pc == len(code): 12 | return acc, "OK" 13 | if pc in visited: 14 | return acc, "ERROR" 15 | visited.add(pc) 16 | c, n = instrs[pc] 17 | if c == "acc": acc += n 18 | elif c == "jmp": pc += n-1 19 | elif c == "noop": pass 20 | pc += 1 21 | 22 | print(execute(code)[0]) 23 | 24 | for i in range(len(code)): 25 | c, n = code[i] 26 | if c in ("jmp", "noop"): 27 | code2 = code[:] 28 | if c == "jmp": code2[i] = ("noop", n) 29 | else: code2[i] = ("jmp", n) 30 | 31 | res, exit_code = execute(code2) 32 | if exit_code == "OK": 33 | print(res) 34 | break 35 | -------------------------------------------------------------------------------- /day09/main.py: -------------------------------------------------------------------------------- 1 | from itertools import combinations 2 | 3 | with open("input.txt") as f: 4 | numbers = [int(x) for x in f] 5 | 6 | p = 25 7 | for i in range(len(numbers)-p): 8 | n = numbers[i+p] 9 | for a, b in combinations(numbers[i:i+p], 2): 10 | if a+b == n: 11 | break 12 | else: 13 | print(n) 14 | break 15 | 16 | for i in range(len(numbers)): 17 | for j in range(i+1, len(numbers)): 18 | elems = numbers[i:j] 19 | if sum(elems) == n: 20 | print(min(elems)+max(elems)) 21 | break 22 | else: 23 | continue 24 | break 25 | -------------------------------------------------------------------------------- /day10/alt.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | with open("input.txt") as file: 4 | d = sorted(map(int, file.readlines())) 5 | 6 | df = np.array([1] + list(np.diff(d)) + [3]) 7 | rle = np.diff([-1] + [i for i, x in enumerate(np.diff(df)) if x != 0] + [len(df)-1]) 8 | m = [2**x - sum("000" in bin(y)[2:].zfill(x) for y in range(2**x)) for x in range(max(rle) + 1)] 9 | print((df == 1).sum()*(df == 3).sum()) 10 | print(np.prod([m[x-1] for i, x in enumerate(rle) if i % 2 == 0])) 11 | -------------------------------------------------------------------------------- /day10/main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from more_itertools import run_length 3 | 4 | with open("input.txt") as f: 5 | numbers = sorted([int(x) for x in f]) 6 | 7 | k = np.diff(numbers) 8 | print((1+(k == 1).sum()) * (1+(k == 3).sum())) 9 | 10 | cands = [e for i, e in run_length.encode(np.diff([0]+numbers)) if i == 1 and e > 1] 11 | # Tribonacci numbers - http://oeis.org/wiki/Tribonacci_numbers 12 | # 0, 0, 1, 1, 2, 4, 7, 13, 24, 44, ... 13 | combinations = {2: 2, 3: 4, 4: 7} 14 | print(np.prod([combinations[e] for e in cands])) 15 | -------------------------------------------------------------------------------- /day11/main.py: -------------------------------------------------------------------------------- 1 | from itertools import product, cycle 2 | 3 | import numpy as np 4 | from scipy.ndimage import convolve 5 | 6 | def check(grid, cols, rows): 7 | for c, r in zip(cols, rows): 8 | k = grid[c, r] 9 | if k in (1,2): 10 | return k-1 11 | return 0 12 | 13 | def get_neightbors(grid, orig_grid, is_part1): 14 | if is_part1: 15 | kernel = np.ones((3,3), dtype=np.int8) 16 | kernel[1,1] = 0 17 | empty = convolve(orig_grid, kernel, mode="constant", cval=0) 18 | return convolve(grid, kernel, mode="constant", cval=0) - empty 19 | else: 20 | res = np.zeros_like(grid) 21 | col, row = grid.shape 22 | for r, c in product(range(row), range(col)): 23 | if grid[c,r] == 0: 24 | continue 25 | else: 26 | n = check(grid, cycle([c]), reversed(range(r))) # left 27 | n += check(grid, cycle([c]), range(r+1, row)) # right 28 | n += check(grid, reversed(range(c)), cycle([r])) # up 29 | n += check(grid, range(c+1, col), cycle([r])) # down 30 | 31 | n += check(grid, reversed(range(c)), reversed(range(r))) # diag left up 32 | n += check(grid, range(c+1, col), reversed(range(r))) # diag left down 33 | n += check(grid, reversed(range(c)), range(r+1, row)) # diag right up 34 | n += check(grid, range(c+1, col), range(r+1, row)) # diag right down 35 | 36 | res[c,r] = n 37 | 38 | return res 39 | 40 | with open("input.txt") as f: 41 | rows = [] 42 | for x in f: 43 | rows.append([c == "L" for c in x.strip()]) 44 | 45 | for is_part1 in [True, False]: 46 | 47 | if is_part1: 48 | cutoff = 4 49 | else: 50 | cutoff = 5 51 | 52 | grid = np.array(rows).astype(np.int8) 53 | seats = grid.copy() 54 | occ = (grid == 2).sum() 55 | 56 | while True: 57 | prev_occ = occ 58 | n = get_neightbors(grid, seats, is_part1) 59 | u = (n == 0).astype(int) - (n >= cutoff).astype(int) 60 | u[grid == 0] = 0 61 | grid = np.clip(grid + u, 0, 2) 62 | grid[grid == 0] = seats[grid == 0] 63 | occ = (grid == 2).sum() 64 | if occ == prev_occ: 65 | break 66 | 67 | print(occ) 68 | -------------------------------------------------------------------------------- /day12/main.py: -------------------------------------------------------------------------------- 1 | from parse import search 2 | 3 | with open("input.txt") as f: 4 | code = [search("{}{:d}", x).fixed for x in f] 5 | 6 | posx = 0 7 | posy = 0 8 | dirs = ["E", "S", "W", "N"] 9 | ptr = 0 10 | 11 | for d, n in code: 12 | if d == "N": posy += n 13 | elif d == "S": posy -= n 14 | elif d == "E": posx += n 15 | elif d == "W": posx -= n 16 | elif d == "L": ptr = (ptr - n // 90) % len(dirs) 17 | elif d == "R": ptr = (ptr + n // 90) % len(dirs) 18 | elif d == "F": 19 | if dirs[ptr] == "N": posy += n 20 | elif dirs[ptr] == "S": posy -= n 21 | elif dirs[ptr] == "E": posx += n 22 | elif dirs[ptr] == "W": posx -= n 23 | 24 | print(abs(posx)+abs(posy)) 25 | 26 | wpx = 10 27 | wpy = 1 28 | posx = 0 29 | posy = 0 30 | 31 | for d, n in code: 32 | if d == "N": wpy += n 33 | elif d == "S": wpy -= n 34 | elif d == "E": wpx += n 35 | elif d == "W": wpx -= n 36 | elif d in ("R", "L") and n == 180: 37 | wpx, wpy = -wpx, -wpy 38 | elif d == "R": 39 | if n == 90: wpx, wpy = wpy, -wpx 40 | elif n == 270: wpx, wpy = -wpy, wpx 41 | elif d == "L": 42 | if n == 90: wpx, wpy = -wpy, wpx 43 | elif n == 270: wpx, wpy = wpy, -wpx 44 | elif d == "F": 45 | posx += n * wpx 46 | posy += n * wpy 47 | 48 | print(abs(posx)+abs(posy)) 49 | -------------------------------------------------------------------------------- /day13/main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from math import prod 3 | 4 | with open("input.txt") as f: 5 | lines = [x.strip() for x in f] 6 | 7 | arrival = int(lines[0]) 8 | buses = [(i, int(e)) for i, e in enumerate(lines[1].split(",")) if e.isdigit()] 9 | 10 | times = [t for _, t in buses] 11 | b = [e - (arrival % e) for e in times] 12 | print(np.min(b) * times[np.argmin(b)]) 13 | 14 | def crt(ns, bs): 15 | # Chinese Remainder Theorem 16 | # https://brilliant.org/wiki/chinese-remainder-theorem/ 17 | N = prod(ns) 18 | x = sum(b * (N // n) * pow(N // n, -1, n) for b, n in zip(bs, ns)) 19 | return x % N 20 | 21 | offsets = [time-idx for idx, time in buses] 22 | print(crt(times, offsets)) 23 | -------------------------------------------------------------------------------- /day14/main.py: -------------------------------------------------------------------------------- 1 | from itertools import product 2 | from parse import search 3 | 4 | def possible_addrs(mask, addr): 5 | mask2 = "".join(v if m == "0" else m for m, v in zip(mask, f"{addr:036b}")) 6 | res = [] 7 | for t in product("01", repeat=mask2.count("X")): 8 | it = iter(t) 9 | res.append(int("".join(next(it) if c == "X" else c for c in mask2), 2)) 10 | return res 11 | 12 | with open("input.txt") as f: 13 | lines = [x.strip() for x in f] 14 | 15 | for is_part1 in [True, False]: 16 | mask = "" 17 | mem = {} 18 | for line in lines: 19 | if line.startswith("mask"): 20 | mask = line.split(" = ")[1] 21 | else: 22 | arg1, arg2 = search("mem[{:d}] = {:d}", line).fixed 23 | if is_part1: 24 | mem[arg1] = int("".join(v if m == "X" else m for m, v in zip(mask, f"{arg2:036b}")), 2) 25 | else: 26 | for addr in possible_addrs(mask, arg1): 27 | mem[addr] = arg2 28 | 29 | print(sum(mem.values())) 30 | -------------------------------------------------------------------------------- /day15/alt.py: -------------------------------------------------------------------------------- 1 | def solve(numbers, until): 2 | memory = {n: i+1 for i, n in enumerate(numbers[:-1])} 3 | for i in range(len(numbers), until): 4 | numbers.append(i - memory.get(numbers[-1], i)) 5 | memory[numbers[-2]] = i 6 | return numbers[-1] 7 | 8 | numbers = [int(n) for n in "12,1,16,3,11,0".split(",")] 9 | print(solve(numbers, 2020)) 10 | print(solve(numbers, 30000000)) 11 | -------------------------------------------------------------------------------- /day15/main.py: -------------------------------------------------------------------------------- 1 | # Van Eck's sequence 2 | # https://oeis.org/A181391 3 | # https://www.youtube.com/watch?v=etMJxB-igrc 4 | 5 | # Part 1: Implemented using list 6 | 7 | puzzle = "12,1,16,3,11,0" 8 | numbers = [int(n) for n in puzzle.split(",")] 9 | 10 | limit = 2020 11 | 12 | for _ in range(limit-len(numbers)): 13 | last = numbers[-1] 14 | turn = len(numbers) 15 | 16 | ptr = len(numbers)-2 17 | while ptr >= 0: 18 | if numbers[ptr] == last: 19 | numbers.append(turn-(ptr+1)) 20 | break 21 | ptr -= 1 22 | else: 23 | numbers.append(0) 24 | 25 | print(numbers[-1]) 26 | 27 | 28 | # Part 2: Reimplemented for efficiency using dict 29 | 30 | numbers = [int(n) for n in puzzle.split(",")] 31 | d = {n: (0, t+1) for t, n in enumerate(numbers)} 32 | 33 | limit = 30000000 34 | 35 | last = numbers[-1] 36 | for turn in range(len(numbers)+1, limit+1): 37 | if last in d: 38 | prev_encounter, last_encounter = d[last] 39 | if prev_encounter == 0: 40 | last = 0 41 | else: 42 | last = last_encounter - prev_encounter 43 | else: 44 | last = 0 45 | 46 | if last in d: 47 | prev_encounter, last_encounter = d[last] 48 | d[last] = (last_encounter, turn) 49 | else: 50 | d[last] = (0, turn) 51 | 52 | print(last) 53 | -------------------------------------------------------------------------------- /day16/alt.py: -------------------------------------------------------------------------------- 1 | import math 2 | import re 3 | 4 | import numpy as np 5 | from scipy.sparse import csr_matrix 6 | from scipy.sparse.csgraph import maximum_bipartite_matching 7 | 8 | with open('input.txt') as f: 9 | ls = [line.strip() for line in f] 10 | 11 | ranges = [list(map(int, re.findall('\d+', x))) for x in ls[:20]] 12 | your = np.array([int(x) for x in ls[22].split(',')], dtype=np.int64) 13 | nearby = [list(map(int, re.findall('\d+', x))) for x in ls[25:]] 14 | 15 | # Part one 16 | valid = set() 17 | for t1, t2, t3, t4 in ranges: 18 | valid |= set(range(t1, t2 + 1)) 19 | valid |= set(range(t3, t4 + 1)) 20 | 21 | print(sum(n for l in nearby for n in l if n not in valid)) 22 | 23 | # Part two 24 | valids = [l for l in nearby if all(n in valid for n in l)] 25 | loc = [[all((t1 <= l[j] <= t2) or (t3 <= l[j] <= t4) for l in valids) 26 | for t1, t2, t3, t4 in ranges] 27 | for j in range(20)] 28 | m = maximum_bipartite_matching(csr_matrix(loc)) 29 | print(your[m[:6]].prod()) 30 | -------------------------------------------------------------------------------- /day16/main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | with open("input.txt") as f: 4 | lines = [x.strip() for x in f] 5 | 6 | sep1 = "your ticket:" 7 | sep2 = "nearby tickets:" 8 | 9 | part1 = lines[:lines.index(sep1)-1] 10 | part2 = [int(n) for n in lines[lines.index(sep1)+1].split(",")] 11 | part3 = lines[lines.index(sep2)+1:] 12 | 13 | alloweds = [] 14 | for line in part1: 15 | a = set() 16 | ranges = line.split(": ")[1] 17 | for r in ranges.split(" or "): 18 | bounds = r.split("-") 19 | lb, ub = int(bounds[0]), int(bounds[1]) 20 | for i in range(lb, ub+1): 21 | a.add(i) 22 | alloweds.append(a) 23 | 24 | score = [] 25 | c = len(part1) 26 | poss_mat = np.ones((c,c), dtype=np.uint8) 27 | 28 | for line in part3: 29 | okay = True 30 | numbers = [int(n) for n in line.split(",")] 31 | for n in numbers: 32 | if not any(n in a for a in alloweds): 33 | score.append(n) 34 | okay = False 35 | if okay: 36 | for field_idx, n in enumerate(numbers): 37 | for j, a in enumerate(alloweds): 38 | if n not in a: 39 | poss_mat[field_idx][j] = 0 40 | 41 | print(sum(score)) 42 | 43 | change = True 44 | seen = set() 45 | while change: 46 | change = False 47 | for i, row in enumerate(poss_mat): 48 | if sum(row) == 1 and i not in seen: 49 | seen.add(i) 50 | change = True 51 | col = np.argmax(row) 52 | poss_mat[:, col] = 0 53 | poss_mat[i, col] = 1 54 | 55 | res = 1 56 | for j, row in enumerate(poss_mat): 57 | if np.argmax(row) < 6: 58 | res *= part2[j] 59 | 60 | print(res) 61 | -------------------------------------------------------------------------------- /day17/main.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from scipy.ndimage import generic_filter 3 | 4 | with open("input.txt") as f: 5 | lines = [list(x.strip()) for x in f] 6 | 7 | def f(x): 8 | if x[len(x) // 2] == 0: 9 | if np.sum(x) == 3: return 1 10 | else: return 0 11 | else: 12 | if np.sum(x) in (3,4): return 1 13 | else: return 0 14 | 15 | d = 20 16 | for dims in [3,4]: 17 | a = (np.array(lines) == "#").astype(np.uint8) 18 | for _ in range(dims-2): 19 | a = np.reshape(a, (1,)+a.shape) 20 | arr = np.pad(a, (d-a.shape[0]) // 2) 21 | kernel = np.ones([3]*dims, dtype=np.uint8) 22 | for i in range(6): 23 | arr = generic_filter(arr, f, footprint=kernel, mode="constant", cval=0) 24 | print(np.sum(arr)) 25 | -------------------------------------------------------------------------------- /day18/main.py: -------------------------------------------------------------------------------- 1 | from more_itertools import chunked 2 | from math import prod 3 | 4 | with open("input.txt") as f: 5 | lines = [x.strip() for x in f] 6 | 7 | def analyse(s): 8 | if "(" not in s: 9 | return calc(s) 10 | 11 | res = {} 12 | stack = [] 13 | 14 | for i, c in enumerate(s): 15 | if c == "(": 16 | stack.append(i) 17 | elif c == ")": 18 | res[stack.pop()] = i 19 | 20 | a, b = next(iter(res.items())) 21 | e = analyse(s[a+1: b]) 22 | return analyse(f"{s[:a]}{e}{s[b+1:]}") 23 | 24 | def calc1(t): 25 | s = t.split() 26 | res = int(s[0]) 27 | for o, n in chunked(s[1:], 2): 28 | if o == "+": 29 | res += int(n) 30 | elif o == "*": 31 | res *= int(n) 32 | return res 33 | 34 | def calc2(t): 35 | k = t.split("*") 36 | return prod(calc1(e) for e in k) 37 | 38 | calc = calc1 39 | print(sum(analyse(line) for line in lines)) 40 | calc = calc2 41 | print(sum(analyse(line) for line in lines)) 42 | -------------------------------------------------------------------------------- /day19/alt.py: -------------------------------------------------------------------------------- 1 | from lark import Lark, LarkError 2 | 3 | def solve(rules, is_part1): 4 | if not is_part1: 5 | rules = rules.replace('8: 42', '8: 42 | 42 8') 6 | rules = rules.replace('11: 42 31', '11: 42 31 | 42 11 31') 7 | rules = rules.translate(str.maketrans('0123456789', 'abcdefghij')) 8 | parser = Lark(rules, start='a') 9 | 10 | total = 0 11 | for line in lines.splitlines(): 12 | try: 13 | parser.parse(line) 14 | total += 1 15 | except LarkError: 16 | pass 17 | 18 | return total 19 | 20 | with open("input.txt") as f: 21 | rules, lines = [l.rstrip("\n") for l in f.read().split("\n\n")] 22 | 23 | for is_part1 in [True, False]: 24 | print(solve(rules, is_part1)) 25 | -------------------------------------------------------------------------------- /day19/main.py: -------------------------------------------------------------------------------- 1 | import re 2 | 3 | def build_regex(d, rulenum, is_part1): 4 | if not is_part1: 5 | if rulenum == "8": 6 | return build_regex(d, "42", is_part1) + "+" 7 | elif rulenum == "11": 8 | a = build_regex(d, "42", is_part1) 9 | b = build_regex(d, "31", is_part1) 10 | return "(?:" + "|".join(f"{a}{{{n}}}{b}{{{n}}}" for n in range(1, 100)) + ")" 11 | 12 | rule = d[rulenum] 13 | if re.fullmatch(r'"."', rule): 14 | return rule[1] 15 | else: 16 | parts = rule.split(" | ") 17 | res = [] 18 | for part in parts: 19 | nums = part.split(" ") 20 | res.append("".join(build_regex(d, n, is_part1) for n in nums)) 21 | return "(?:" + "|".join(res) + ")" 22 | 23 | with open("input.txt") as f: 24 | rules, msgs = [l.rstrip("\n") for l in f.read().split("\n\n")] 25 | 26 | d = dict([rule.split(": ") for rule in rules.split("\n")]) 27 | msgs = [x.strip() for x in msgs.split("\n")] 28 | 29 | for is_part1 in [True, False]: 30 | z = build_regex(d, "0", is_part1) 31 | print(sum([bool(re.fullmatch(z, msg)) for msg in msgs])) 32 | -------------------------------------------------------------------------------- /day20/main.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from itertools import product 3 | 4 | import numpy as np 5 | from imgaug.augmenters.flip import fliplr 6 | from imgaug.augmenters.geometric import Rot90 7 | from scipy.ndimage import generic_filter 8 | 9 | OrientedTile = namedtuple("OrientedTile", "idx data") 10 | 11 | ndim = 12 12 | with open("input.txt") as f: 13 | parts = f.read().split("\n\n") 14 | 15 | tiles = {} 16 | for p in parts[:-1]: 17 | p = p.split("\n") 18 | k = [list(x) for x in p[1:]] 19 | a = (np.array(k) == "#").astype(np.uint8) 20 | tiles[int(p[0].split()[1][:-1])] = a 21 | 22 | def transform(tile, rot, lr): 23 | t = Rot90(rot).augment_image(tile) 24 | if lr: 25 | t = fliplr(t) 26 | return t 27 | 28 | def get_all_transforms(arr): 29 | return [transform(arr, r, f) for r, f in product([0,1,2,3], [False, True])] 30 | 31 | def edge_has_fit(tiles, tile_idx, edge): 32 | tile_data = tiles[tile_idx] 33 | 34 | if edge == 0: e = tile_data[0] 35 | elif edge == 1: e = tile_data[:,-1] 36 | elif edge == 2: e = tile_data[-1] 37 | elif edge == 3: e = tile_data[:,0] 38 | 39 | for idx, tile in tiles.items(): 40 | if idx == tile_idx: continue 41 | 42 | for t in get_all_transforms(tile): 43 | if np.array_equal(t[0], e): 44 | return idx 45 | return None 46 | 47 | corners = [] 48 | for tile_idx in tiles: 49 | ns = [edge_has_fit(tiles, tile_idx, i) for i in range(4)] 50 | if len([e for e in ns if e is not None]) == 2: 51 | corners.append(tile_idx) 52 | if len(corners) == 4: 53 | break 54 | print(int(np.prod(corners))) 55 | 56 | # Part 2 57 | def find_neighbor(tiles, tile, direction, seen): 58 | if direction == "right": 59 | arr = tile.data[:,-1] 60 | elif direction == "down": 61 | arr = tile.data[-1] 62 | 63 | for t_idx, t_data in tiles.items(): 64 | if t_idx == tile.idx: continue 65 | if t_idx in seen: continue 66 | 67 | for data in get_all_transforms(t_data): 68 | if direction == "right" and np.array_equal(arr, data[:,0]): 69 | return OrientedTile(t_idx, data) 70 | elif direction == "down" and np.array_equal(arr, data[0]): 71 | return OrientedTile(t_idx, data) 72 | 73 | raise Exception() 74 | 75 | corner_idx = corners[0] 76 | for corner_data in get_all_transforms(tiles[corner_idx]): 77 | try: 78 | seen = set() 79 | display = [[] for _ in range(ndim)] 80 | display[0].append(OrientedTile(corner_idx, corner_data)) 81 | seen.add(corners[0]) 82 | 83 | for idx_row in range(ndim): 84 | row = display[idx_row] 85 | while len(row) < ndim: 86 | n = find_neighbor(tiles, row[-1], "right", seen) 87 | row.append(n) 88 | seen.add(n.idx) 89 | 90 | if idx_row < ndim-1: 91 | n = find_neighbor(tiles, row[0], "down", seen) 92 | display[idx_row+1].append(n) 93 | seen.add(n.idx) 94 | except: 95 | continue 96 | break 97 | 98 | display2 = np.concatenate([np.concatenate([t.data[1:-1, 1:-1] for t in row], axis=1) for row in display]) 99 | 100 | nessi = np.array([list(" # "), 101 | list("# ## ## ###"), 102 | list(" # # # # # # ")]) 103 | nessi = (nessi == "#").astype(np.uint8) 104 | 105 | def f(part): 106 | part = np.reshape(part, nessi.shape) 107 | for y, row in enumerate(part): 108 | for x, pix in enumerate(row): 109 | if nessi[y][x] == 1 and pix == 0: 110 | return 0 111 | return 1 112 | 113 | found = max([np.sum(generic_filter(img, f, nessi.shape, mode="constant", cval=0)) for img in get_all_transforms(display2)]) 114 | roughness = int(np.count_nonzero(display2) - found*np.sum(nessi)) 115 | print(roughness) 116 | -------------------------------------------------------------------------------- /day21/main.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | import numpy as np 4 | from more_itertools import flatten 5 | 6 | with open("input.txt") as f: 7 | lines = [x.strip() for x in f] 8 | 9 | foods = [x.split(" (contains ")[0].split() for x in lines] 10 | allergens = [x.split(" (contains ")[1][:-1].split(", ") for x in lines] 11 | recipe = list(zip(foods, allergens)) 12 | 13 | poss_allergens = sorted(set(list(flatten(allergens)))) 14 | 15 | poss = [] 16 | for _, pa in enumerate(poss_allergens): 17 | k = [] 18 | for fs, allergs in recipe: 19 | if pa in allergs: 20 | k.append(set(fs)) 21 | poss.append(k) 22 | 23 | reduced = [set.intersection(*ps) for ps in poss] 24 | remaining_foods = sorted(set(flatten(reduced))) 25 | count = len([f for f in flatten(foods) if f not in remaining_foods]) 26 | print(count) 27 | 28 | poss_mat = np.array([[1 if r in red else 0 for r in remaining_foods] for red in reduced]) 29 | 30 | change = True 31 | seen = set() 32 | while change: 33 | change = False 34 | for i, row in enumerate(poss_mat): 35 | if sum(row) == 1 and i not in seen: 36 | seen.add(i) 37 | change = True 38 | col = np.argmax(row) 39 | poss_mat[:, col] = 0 40 | poss_mat[i, col] = 1 41 | 42 | print(",".join(remaining_foods[np.argmax(poss_mat[i])] for i in range(len(remaining_foods)))) 43 | -------------------------------------------------------------------------------- /day22/main.py: -------------------------------------------------------------------------------- 1 | 2 | with open("input.txt") as f: 3 | chunks = f.read().split("\n\n") 4 | 5 | orig_p1 = list(map(int, chunks[0].strip().split("\n")[1:])) 6 | orig_p2 = list(map(int, chunks[1].strip().split("\n")[1:])) 7 | 8 | p1 = orig_p1.copy() 9 | p2 = orig_p2.copy() 10 | 11 | while len(p1) > 0 and len(p2) > 0: 12 | a, b = p1.pop(0), p2.pop(0) 13 | if a > b: 14 | p1 += [a,b] 15 | else: 16 | p2 += [b,a] 17 | 18 | if len(p1) > 0: 19 | winner = p1 20 | else: 21 | winner = p2 22 | 23 | print(sum(e*(len(winner)-i) for i, e in enumerate(winner))) 24 | 25 | # Part 2 26 | def play_game(p1, p2): 27 | seen1, seen2 = set(), set() 28 | 29 | while len(p1) > 0 and len(p2) > 0: 30 | s1 = ",".join([str(c) for c in p1]) 31 | s2 = ",".join([str(c) for c in p2]) 32 | if s1 in seen1 or s2 in seen2: 33 | return "p1", p1 34 | seen1.add(s1) 35 | seen2.add(s2) 36 | 37 | a, b = p1.pop(0), p2.pop(0) 38 | if a <= len(p1) and b <= len(p2): 39 | winner, _ = play_game(p1.copy()[:a], p2.copy()[:b]) 40 | else: 41 | if a > b: 42 | winner = "p1" 43 | else: 44 | winner = "p2" 45 | 46 | if winner == "p1": 47 | p1 += [a,b] 48 | else: 49 | p2 += [b,a] 50 | 51 | if len(p1) > 0: 52 | return "p1", p1 53 | else: 54 | return "p2", p2 55 | 56 | _, w = play_game(orig_p1.copy(), orig_p2.copy()) 57 | print(sum(e*(len(w)-i) for i, e in enumerate(w))) 58 | -------------------------------------------------------------------------------- /day23/main.py: -------------------------------------------------------------------------------- 1 | 2 | class Node: 3 | def __init__(self, n, pred, succ): 4 | self.n = n 5 | self.pred = pred 6 | self.succ = succ 7 | 8 | def to_linked_list(l): 9 | prev = Node(l[0], None, None) 10 | f = prev 11 | for e in l[1:]: 12 | n = Node(e, prev, None) 13 | prev.succ = n 14 | prev = n 15 | n.succ = f 16 | f.pred = n 17 | return f 18 | 19 | def to_dict(curr): 20 | d = {} 21 | n = curr.n 22 | d[n] = curr 23 | while True: 24 | curr = curr.succ 25 | if curr.n == n: break 26 | d[curr.n] = curr 27 | return d 28 | 29 | def to_list(curr): 30 | first = curr.n 31 | res = [first] 32 | while True: 33 | curr = curr.succ 34 | if curr.n == first: break 35 | res.append(curr.n) 36 | return res 37 | 38 | def make_move(curr, d, l): 39 | act = curr 40 | f1 = curr.succ 41 | f2 = f1.succ 42 | f3 = f2.succ 43 | 44 | curr.succ = f3.succ 45 | f3.pred = curr 46 | 47 | n = curr.n 48 | while True: 49 | n -= 1 50 | if n == 0: 51 | n = l 52 | if n not in [f1.n, f2.n, f3.n]: 53 | break 54 | 55 | curr = d[n] 56 | 57 | k = curr.succ 58 | curr.succ = f1 59 | f1.pred = curr 60 | f3.succ = k 61 | k.pred = f3 62 | return act.succ 63 | 64 | for is_part1 in [True, False]: 65 | n = [int(i) for i in "476138259"] 66 | 67 | if is_part1: 68 | n_moves = 100 69 | else: 70 | n += list(range(10, 1_000_001)) 71 | n_moves = 10_000_000 72 | 73 | l = len(n) 74 | n = to_linked_list(n) 75 | d = to_dict(n) 76 | 77 | for move in range(n_moves): 78 | n = make_move(n, d, l) 79 | 80 | k = to_list(d[1]) 81 | if is_part1: 82 | print("".join(str(c) for c in k[1:])) 83 | else: 84 | print(k[1]*k[2]) 85 | -------------------------------------------------------------------------------- /day24/main.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | def parse(s): 4 | i = 0 5 | res = [] 6 | while s: 7 | if s[0] == "w": res.append((-1,0)); s = s[1:] 8 | elif s[0] == "e": res.append((1,0)); s = s[1:] 9 | elif s[0:2] == "se": res.append((1,-1)); s = s[2:] 10 | elif s[0:2] == "sw": res.append((0,-1)); s = s[2:] 11 | elif s[0:2] == "ne": res.append((0,1)); s = s[2:] 12 | elif s[0:2] == "nw": res.append((-1,1)); s = s[2:] 13 | return res 14 | 15 | with open("input.txt") as f: 16 | lines = [parse(x.strip()) for x in f] 17 | 18 | d = defaultdict(int) 19 | 20 | for line in lines: 21 | xs, ys = zip(*line) 22 | coords = sum(ys), sum(xs) 23 | d[coords] = 1 - d[coords] 24 | 25 | print(sum(d.values())) 26 | 27 | def apply(n, ns): 28 | if n == 0: 29 | if sum(ns) == 2: 30 | return 1 31 | else: 32 | if sum(ns) == 0 or sum(ns) > 2: 33 | return 0 34 | return n 35 | 36 | dirs = [(1,-1),(0,-1),(-1,1),(0,1),(1,0),(-1,0)] 37 | for _ in range(100): 38 | d2 = d.copy() 39 | for row in range(-100,100): 40 | for col in range(-100,100): 41 | coords = (row,col) 42 | d2[coords] = apply(d[coords], [d[k] for k in [(y+row, x+col) for x, y in dirs]]) 43 | d = d2 44 | 45 | print(sum(d.values())) 46 | -------------------------------------------------------------------------------- /day25/main.py: -------------------------------------------------------------------------------- 1 | with open("input.txt") as f: 2 | pub1, pub2 = [int(x) for x in f] 3 | 4 | n = 1 5 | loop_size = 0 6 | while n != pub2: 7 | n = (n * 7) % 20201227 8 | loop_size += 1 9 | 10 | print(pow(pub1, loop_size, 20201227)) 11 | --------------------------------------------------------------------------------