├── .gitignore ├── README.md ├── aoc2021 ├── 01.py ├── 02.py ├── 03.py ├── 04.py ├── 05.py ├── 05_post.py ├── 06.py ├── 06_np.py ├── 07.py ├── 08.py ├── 08_post.py ├── 08_z3.py ├── 09.py ├── 10.py ├── 11.py ├── 12.py ├── 12_post.py ├── 13.py ├── 14.py ├── 14_post.py ├── 15.py ├── 15_post.py ├── 16.py ├── 17.py ├── 17_post.py ├── 18.py ├── 19.py ├── 19_post.py ├── 20.py ├── 21.py ├── 22.py ├── 22_post.py ├── 23.py ├── 23_post.py ├── 24.py ├── 24_post.py ├── 24_post_partial.py ├── 24_z3_optimized.py ├── 25.py ├── get_input.py └── util.py ├── aoc2022 ├── 01.pl ├── 01.py ├── 02.pl ├── 02.py ├── 03.pl ├── 03.py ├── 04.pl ├── 04.py ├── 04_post.py ├── 05.pl ├── 05.py ├── 05_post.py ├── 06.pl ├── 06.py ├── 07.pl ├── 07.py ├── 08.pl ├── 08.py ├── 09.pl ├── 09.py ├── 10.pl ├── 10.py ├── 10_post.py ├── 11.pl ├── 11.py ├── 11_post.py ├── 12.pl ├── 12.py ├── 13.pl ├── 13.py ├── 14.py ├── 14_post.py ├── 15.pl ├── 15.py ├── 15_post.py ├── 15_z3.py ├── 16.pl ├── 16.py ├── 16_post.py ├── 17.py ├── 17_post.py ├── 18.pl ├── 18.py ├── 18_post.py ├── 19.py ├── 19_post.py ├── 19_z3.py ├── 20.pl ├── 20.py ├── 21.pl ├── 21.py ├── 22.py ├── 22_post.py ├── 23.pl ├── 23.py ├── 24.pl ├── 24.py ├── 24_post.py ├── 25.pl ├── 25.py ├── get_input.py └── util.py ├── aoc2023 ├── 01.pl ├── 01.py ├── 01_2.pl ├── 02.pl ├── 02.py ├── 03.pl ├── 03.py ├── 04.pl ├── 04.py ├── 04_post.py ├── 05.pl ├── 05.py ├── 05_post.py ├── 05_z3.py ├── 06.pl ├── 06.py ├── 06_post.py ├── 07.pl ├── 07.py ├── 08.pl ├── 08.py ├── 09.pl ├── 09.py ├── 10.pl ├── 10.py ├── 10_post.py ├── 11.pl ├── 11.py ├── 12.pl ├── 12.py ├── 13.pl ├── 13.py ├── 14.pl ├── 14.py ├── 15.pl ├── 15.py ├── 16.pl ├── 16.py ├── 16_post.py ├── 17.pl ├── 17.py ├── 18.pl ├── 18.py ├── 19.pl ├── 19.py ├── 20.pl ├── 20.py ├── 21.pl ├── 21.py ├── 21_post.py ├── 22.pl ├── 22.py ├── 23.pl ├── 23.py ├── 23_post.py ├── 24.pl ├── 24.py ├── 24_post.py ├── 25.pl ├── 25.py ├── README.md ├── get_input.py └── util.py ├── aoc2024 ├── 01.py ├── 02.py ├── 03.py ├── 04.py ├── 05.py ├── 06.py ├── 07.py ├── 08.py ├── 09.py ├── 09_post.py ├── 10.py ├── 11.py ├── 12.py ├── 13.pl ├── 13.py ├── 14.py ├── 15.py ├── 15_post.py ├── 16.py ├── 17.pl ├── 17.py ├── 17_post.py ├── 18.py ├── 18_post.py ├── 19.py ├── 20.py ├── 21.py ├── 21_post.py ├── 22.py ├── 22_post.py ├── 23.pl ├── 23.py ├── 24.py ├── 24_post.py ├── 25.py ├── get_input.py ├── template.py └── util.py └── pyproject.toml /.gitignore: -------------------------------------------------------------------------------- 1 | __pycache__ 2 | *.in 3 | *.sin 4 | *.session 5 | .response 6 | .state 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2 | 3 | This repository contains my solutions and helper scripts for the [Advent of Code](https://adventofcode.com/) challenges from 2021 onwards. 4 | 5 | To compete for global leaderboard positions, I use Python with [PyPy](https://www.pypy.org/). 6 | Sometimes I use [Z3](https://github.com/Z3Prover/z3) through its Python bindings, and occasionally the [networkx](https://networkx.org/) library, 7 | but mostly I use a small set of utility functions and classes that I've developed over the years. 8 | 9 | | Year | Points | Rank | 10 | |------|-------:|-----:| 11 | | 2021 | 1874 | 28 | 12 | | 2022 | 1902 | 24 | 13 | | 2023 | 1992 | 16 | 14 | | 2024 | 720 | 88 | 15 | 16 | Most `[dd].py` files contain the un-edited solution that I used to solve the puzzle initially (very messy!). 17 | In the interest of speed, the solution for part 1 is often clobbered to solve part 2 quickly. 18 | 19 | There are also some `[dd]_post.py` file that contain cleaned-up versions of the solutions that solves both parts. 20 | 21 | #### Prolog 22 | 23 | I have also written some solutions in [SWI Prolog](https://www.swi-prolog.org/) for fun (not for the leaderboard). 24 | 25 | They are run from the command line like so: 26 | ```bash 27 | swipl -O -t halt -g main 13.pl < 13.in 28 | ``` 29 | -------------------------------------------------------------------------------- /aoc2021/01.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | sys.stdin = open(__file__.replace('py', 'in')) 5 | 6 | L = list(ints()) 7 | 8 | 9 | prev = 0 10 | r = 0 11 | 12 | for j, i in enumerate(L): 13 | i = sum(L[j:j+3]) 14 | if i > prev: 15 | r += 1 16 | 17 | prev = i 18 | 19 | print(r-1) 20 | -------------------------------------------------------------------------------- /aoc2021/02.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | sys.stdin = open(__file__.replace('py', 'in')) 5 | 6 | 7 | depth = pos = 0 8 | d2 = 0 9 | 10 | for l in lines(): 11 | a, x = l.split() 12 | x = int(x) 13 | if a == 'forward': 14 | pos += x 15 | d2 += x * depth 16 | elif a == 'down': 17 | depth += x 18 | else: 19 | depth -= x 20 | 21 | 22 | print(pos * depth) 23 | print(pos * d2) 24 | 25 | -------------------------------------------------------------------------------- /aoc2021/03.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | sys.stdin = open(__file__.replace('py', 'in')) 5 | 6 | g = e = 0 7 | 8 | L = lines() 9 | N = len(L[0]) 10 | 11 | def f(t): 12 | P = list(L) 13 | 14 | for x in range(N): 15 | C = Counter() 16 | for l in P: 17 | C[l[x]] += 1 18 | 19 | o, z = C['1'], C['0'] 20 | 21 | if t: 22 | keep = '1' if o >= z else '0' 23 | else: 24 | keep = '0' if z <= o else '1' 25 | 26 | P = [l for l in P if l[x] == keep] 27 | if len(P) == 1: 28 | print('found', P[0]) 29 | return int(P[0], 2) 30 | 31 | assert P 32 | 33 | print(f(True) * f(False)) 34 | exit() 35 | 36 | for x in range(N): 37 | C = Counter() 38 | for l in L: 39 | C[l[x]] += 1 40 | 41 | g <<= 1 42 | e <<= 1 43 | o, z = C['1'], C['0'] 44 | if o > z: 45 | g += 1 46 | else: 47 | e += 1 48 | 49 | 50 | print(g * e) 51 | -------------------------------------------------------------------------------- /aoc2021/04.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | sys.stdin = open(__file__.replace('py', 'in')) 5 | input = sys.stdin.readline 6 | 7 | I = list(map(int, input().split(','))) 8 | 9 | boards = [] 10 | M = defaultdict(list) 11 | 12 | bs = 0 13 | while True: 14 | _ = input() 15 | if _ == '': break 16 | 17 | B = [list(ints(input())) for _ in range(5)] 18 | boards.append(B) 19 | 20 | for c in range(5): 21 | s = {B[y][c] for y in range(5)} 22 | for i in s: M[i].append((s, bs)) 23 | 24 | for y in range(5): 25 | s = {B[y][c] for c in range(5)} 26 | for i in s: M[i].append((s, bs)) 27 | 28 | bs += 1 29 | 30 | 31 | rem = set(range(bs)) 32 | D = set() 33 | for i in I: 34 | D.add(i) 35 | 36 | for s, bi in M[i]: 37 | if i in s: 38 | s.remove(i) 39 | 40 | if not s: 41 | rem.discard(bi) 42 | 43 | if not rem: 44 | r = sum(v for l in boards[bi] for v in l if v not in D) 45 | print(r * i) 46 | exit() 47 | 48 | """ 49 | for s, bi in M[i]: 50 | assert i in s 51 | s.remove(i) 52 | 53 | if not s: 54 | r = sum(v for l in boards[bi] for v in l if v not in D) 55 | print(r * i) 56 | exit() 57 | """ 58 | -------------------------------------------------------------------------------- /aoc2021/05.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | sys.stdin = open(__file__.replace('py', 'in')) 5 | 6 | l = lines() 7 | 8 | O = Counter() 9 | 10 | def f(s): 11 | return map(int, s.split(',')) 12 | 13 | for s in l: 14 | a, b = s.split(' -> ') 15 | 16 | x1, y1 = f(a) 17 | x2, y2 = f(b) 18 | 19 | #if not (x1 == x2 or y1 == y2): continue 20 | 21 | if x1 == x2: 22 | dx = 0 23 | else: 24 | dx = 1 if x2 > x1 else -1 25 | 26 | if y1 == y2: 27 | dy = 0 28 | else: 29 | dy = 1 if y2 > y1 else -1 30 | 31 | x, y = x1, y1 32 | while True: 33 | O[(x, y)] += 1 34 | 35 | if (x, y) == (x2, y2): break 36 | 37 | x += dx; y += dy 38 | 39 | print(sum(v > 1 for v in O.values())) 40 | -------------------------------------------------------------------------------- /aoc2021/05_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | sys.stdin = open(__file__.replace('_post.py', '.in')) 5 | 6 | O = Counter() 7 | 8 | def f(s) -> Point[int]: 9 | return Point.of(*ints(s)) 10 | 11 | for p1, p2 in map(lambda s: map(f, s.split(' -> ')), lines()): 12 | diff = p2 - p1 13 | d = Point.of(*map(sign, diff)) 14 | #if not (p1.x == p2.x or p1.y == p2.y): continue 15 | O.update(p1 + d * i for i in range(max(abs(diff))+1)) 16 | 17 | print(sum(v > 1 for v in O.values())) 18 | -------------------------------------------------------------------------------- /aoc2021/06.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 34/8 3 | from util import * 4 | 5 | sys.stdin = open(__file__.replace('py', 'in')) 6 | 7 | C = Counter(ints()) 8 | 9 | for _ in range(256): 10 | NC = Counter() 11 | for k, v in C.items(): 12 | if k == 0: 13 | NC[6] += v 14 | NC[8] += v 15 | else: 16 | NC[k-1] += v 17 | 18 | C = NC 19 | 20 | 21 | prints(sum(C.values())) 22 | -------------------------------------------------------------------------------- /aoc2021/06_np.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from util import * 3 | 4 | sys.stdin = open(__file__.replace("_np.py", ".in")) 5 | 6 | import numpy as np 7 | 8 | C = Counter(ints()) 9 | V = [C[i] for i in range(9)] 10 | 11 | A = np.eye(9, k=1, dtype=int) 12 | 13 | A[6][0] = 1 14 | A[8][0] = 1 15 | 16 | def f(n: int) -> int: 17 | return sum(np.linalg.matrix_power(A, n) @ V) 18 | 19 | print(f(80)) 20 | print(f(256)) 21 | -------------------------------------------------------------------------------- /aoc2021/07.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | sys.stdin = open(__file__.replace('py', 'in')) 5 | 6 | V = list(ints()) 7 | V.sort() 8 | n = len(V)//2 9 | 10 | x = V[n//2] 11 | 12 | # Using the "median" didn't work because I divided len(V) by 4... 13 | print(min(sum(abs(y - x) for y in V) for x in V)) 14 | 15 | prints(min(sum(abs(y - x) * (abs(y - x) + 1) // 2 for y in V) for x in range(min(V), max(V)+1))) 16 | -------------------------------------------------------------------------------- /aoc2021/08_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('_post.py', '.in')) 6 | 7 | 8 | nums = [ 9 | (0, 1, 2, 4, 5, 6), 10 | (2, 5), 11 | (0, 2, 3, 4, 6), 12 | (0, 2, 3, 5, 6), 13 | (1, 2, 3, 5), 14 | (0, 1, 3, 5, 6), 15 | (0, 1, 3, 4, 5, 6), 16 | (0, 2, 5), 17 | tuple(range(7)), 18 | (0, 1, 2, 3, 5, 6), 19 | ] 20 | 21 | r = 0 22 | for l in lines(): 23 | pat, e = l.split(' | ') 24 | 25 | ins = pat.split() 26 | out = e.split() 27 | 28 | def f(c): 29 | return 'abcdefg'.index(c) 30 | 31 | ins = [list(map(f, s)) for s in ins] 32 | 33 | res = 0 34 | for perm in permutations(range(7)): 35 | if all(tuple(sorted(perm[j] for j in i)) in nums for i in ins): 36 | for o in out: 37 | res *= 10 38 | 39 | x = tuple(sorted(perm[f(c)] for c in o)) 40 | res += nums.index(x) 41 | 42 | break 43 | else: assert False 44 | 45 | r += res 46 | 47 | prints(r) 48 | -------------------------------------------------------------------------------- /aoc2021/08_z3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from util import * 3 | 4 | from functools import reduce 5 | from z3 import And, BitVec, Distinct, Or, Solver 6 | 7 | 8 | if len(sys.argv) == 1: 9 | sys.stdin = open(__file__.replace("_z3.py", ".in")) 10 | 11 | nums = [ 12 | (0, 1, 2, 4, 5, 6), 13 | (2, 5), 14 | (0, 2, 3, 4, 6), 15 | (0, 2, 3, 5, 6), 16 | (1, 2, 3, 5), 17 | (0, 1, 3, 5, 6), 18 | (0, 1, 3, 4, 5, 6), 19 | (0, 2, 5), 20 | tuple(range(7)), 21 | (0, 1, 2, 3, 5, 6), 22 | ] 23 | 24 | # Represent the segments as bitmasks 25 | numb = [sum(1 << i for i in t) for t in nums] 26 | 27 | # X represents the permutation, the values should therefore be distinct and 28 | # range from 0 to 6 29 | X = [BitVec(f"X__{i}", 7) for i in range(7)] 30 | S = Solver() 31 | S.add(Distinct(X), *[And(0 <= x, x < 7) for x in X]) 32 | 33 | 34 | def f(c): 35 | return "abcdefg".index(c) 36 | 37 | 38 | r = 0 39 | for l in lines(): 40 | S.push() 41 | 42 | pat, e = l.split(" | ") 43 | 44 | ins = pat.split() 45 | out = e.split() 46 | 47 | for i, signal_ in enumerate(ins): 48 | signal = list(map(f, signal_)) 49 | 50 | # Make a helper variable that is equal to the bitmask of the signal 51 | # after the permutation is applied. 52 | bv = BitVec(f"bv_{i}", 7) 53 | signal_shifts = [1 << X[v] for v in signal] 54 | S.add(bv == reduce(lambda expr, v: expr | v, signal_shifts)) 55 | 56 | # The bitmask should be equal to one of the valid bitmasks. 57 | # We can save some time by only considering bitmasks with the same number of bits set. 58 | S.add(Or([bv == exp for t, exp in zip(nums, numb) if len(t) == len(signal)])) 59 | 60 | print(S.check()) 61 | m = S.model() 62 | 63 | M = [m.evaluate(x).as_long() for x in X] 64 | 65 | res = 0 66 | for l in out: 67 | res *= 10 68 | 69 | v = sum(1 << M[i] for i in map(f, l)) 70 | res += numb.index(v) 71 | 72 | S.pop() 73 | r += res 74 | 75 | prints(r) 76 | -------------------------------------------------------------------------------- /aoc2021/09.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 61/8 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | M = [list(map(int, l)) for l in lines()] 9 | H = len(M) 10 | W = len(M[0]) 11 | 12 | Q = [] 13 | r = 0 14 | for y, l in enumerate(M): 15 | for x, d in enumerate(l): 16 | m = 10 17 | for dx, dy in ((1, 0), (0, 1), (-1, 0), (0, -1)): 18 | nx, ny = x +dx, y + dy 19 | if not 0 <= nx < W or not 0 <= ny < H: continue 20 | m = min(m, M[ny][nx]) 21 | 22 | if d < m: 23 | Q.append((x, y)) 24 | r += d + 1 25 | 26 | print(r) 27 | 28 | B = [] 29 | V = [[False] * W for _ in range(H)] 30 | for sx, sy in Q: 31 | 32 | NQ = [(sx, sy)] 33 | V[sy][sx] = True 34 | 35 | for x, y in NQ: 36 | for dx, dy in ((1, 0), (0, 1), (-1, 0), (0, -1)): 37 | nx, ny = x +dx, y + dy 38 | if not 0 <= nx < W or not 0 <= ny < H: continue 39 | m = M[ny][nx] 40 | if m == 9 or V[ny][nx]: continue 41 | V[ny][nx] = True 42 | NQ.append((nx, ny)) 43 | 44 | B.append(len(NQ)) 45 | 46 | B.sort(reverse=True) 47 | a, b, c = B[:3] 48 | prints(a * b *c) 49 | -------------------------------------------------------------------------------- /aoc2021/10.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 13/6 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | r = 0 9 | 10 | pt = {')': 3, ']': 57, '}': 1197, '>': 25137} 11 | op = {')': '(', ']': '[', '}': '{', '>': '<'} 12 | 13 | score = ' )]}>' 14 | 15 | sc = [] 16 | 17 | for l in lines(): 18 | S = [] 19 | for c in l: 20 | if c in op: 21 | if not S or S[-1] != op[c]: 22 | r += pt[c] 23 | break 24 | 25 | S.pop() 26 | else: 27 | S.append(c) 28 | 29 | else: 30 | x = 0 31 | while S: 32 | x *= 5 33 | c = S.pop() 34 | 35 | for k, v in op.items(): 36 | if v == c: 37 | x += score.index(k) 38 | break 39 | else: 40 | assert False 41 | 42 | sc.append(x) 43 | 44 | sc.sort() 45 | 46 | print(r) 47 | prints(sc[len(sc)//2]) 48 | -------------------------------------------------------------------------------- /aoc2021/11.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('py', 'in')) 6 | 7 | r = 0 8 | 9 | G = [list(map(int, input())) for _ in range(10)] 10 | 11 | for i in range(10 ** 10): 12 | V = [[False] * 10 for _ in range(10)] 13 | 14 | Q = [] 15 | for y, l in enumerate(G): 16 | for x, c in enumerate(l): 17 | G[y][x] += 1 18 | if G[y][x] > 9: 19 | V[y][x] = True 20 | Q.append((x, y)) 21 | 22 | for x, y in Q: 23 | for dx in range(-1, 2): 24 | for dy in range(-1, 2): 25 | nx = x + dx 26 | ny = y + dy 27 | if nx < 0 or ny < 0 or nx >= 10 or ny >= 10: continue 28 | G[ny][nx] += 1 29 | 30 | if not V[ny][nx] and G[ny][nx] > 9: 31 | V[ny][nx] = True 32 | Q.append((nx, ny)) 33 | 34 | r += len(Q) 35 | 36 | if len(Q) == 10 * 10: 37 | prints(i+1) 38 | exit() 39 | 40 | for x, y in Q: 41 | G[y][x] = 0 42 | 43 | prints(r) 44 | -------------------------------------------------------------------------------- /aoc2021/12.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 1/37 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | r = 0 9 | 10 | adj = defaultdict(list) 11 | for l in lines(): 12 | a, b = l.split('-') 13 | adj[a].append(b) 14 | adj[b].append(a) 15 | 16 | 17 | def f(i, v, b): 18 | global r 19 | 20 | if i.islower() and v[i]: 21 | if i == 'start' or b: return 22 | b = True 23 | 24 | if i == 'end': 25 | r += 1 26 | return 27 | 28 | v[i] += 1 29 | 30 | for j in adj[i]: 31 | f(j, v, b) 32 | 33 | v[i] -= 1 34 | 35 | 36 | f('start', Counter(), True) 37 | print(r) 38 | 39 | r = 0 40 | f('start', Counter(), False) 41 | prints(r) 42 | -------------------------------------------------------------------------------- /aoc2021/12_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('_post.py', '.in')) 6 | 7 | adj = make_adj((l.split('-') for l in lines()), both=True) 8 | idx = {name: i for i, name in enumerate(a for a in adj if a.islower())} 9 | 10 | @lru_cache(maxsize=None) 11 | def f(i: str, v: int, d: bool) -> int: 12 | if i == 'end': 13 | return 1 14 | 15 | r = 0 16 | for j in adj[i]: 17 | if j.isupper(): r += f(j, v, d) 18 | elif j != 'start': 19 | seen = v & (1 << idx[j]) != 0 20 | if not seen or not d: 21 | r += f(j, v | (1 << idx[j]), seen or d) 22 | 23 | return r 24 | 25 | 26 | s = 'start' 27 | print(f(s, 1 << idx[s], True)) 28 | prints(f(s, 1 << idx[s], False)) 29 | 30 | 31 | -------------------------------------------------------------------------------- /aoc2021/13.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('py', 'in')) 6 | 7 | 8 | A, B = sys.stdin.read().split('\n\n') 9 | 10 | dots = tile(list(ints(A)), 2) 11 | 12 | for s in lines(B): 13 | c = next(ints(s)) 14 | is_x = 'x' in s 15 | 16 | ndots = set() 17 | 18 | for a, b in dots: 19 | if is_x: a = min(a, 2 * c - a) 20 | else: b = min(b, 2 * c - b) 21 | ndots.add((a, b)) 22 | 23 | dots = ndots 24 | 25 | print_coords(dots) 26 | -------------------------------------------------------------------------------- /aoc2021/14.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 42/82 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | 9 | s = input() 10 | input() 11 | 12 | adj = {} 13 | 14 | for l in sys.stdin: 15 | l = l.rstrip() 16 | a, b = l.split(" -> ") 17 | adj[a] = b 18 | 19 | after = defaultdict(Counter) 20 | for c1, c2 in zip(s, s[1:]): 21 | after[c1][c2] += 1 22 | 23 | for _ in range(40): 24 | nafter = defaultdict(Counter) 25 | for c1, v in after.items(): 26 | for c2, cnt in v.items(): 27 | nafter[c1][adj[c1 + c2]] += cnt 28 | nafter[adj[c1 + c2]][c2] += cnt 29 | 30 | after = nafter 31 | 32 | C = sum(after.values(), start=Counter()) 33 | C[s[0]] += 1 34 | 35 | S = sorted(C.values()) 36 | prints(S[-1] - S[0]) 37 | -------------------------------------------------------------------------------- /aoc2021/14_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace("_post.py", ".in")) 6 | 7 | 8 | start, _, *rest = lines() 9 | adj = dict(l.split(" -> ") for l in rest) 10 | 11 | pairs = Counter(a + b for a, b in zip(start, start[1:])) 12 | 13 | for steps in (10, 30): 14 | for i in range(steps): 15 | npairs = Counter() 16 | for p, cnt in pairs.items(): 17 | npairs[p[0] + adj[p]] += cnt 18 | npairs[adj[p] + p[1]] += cnt 19 | 20 | pairs = npairs 21 | 22 | C = Counter(start[0]) 23 | for p, cnt in pairs.items(): C[p[1]] += cnt 24 | print(max(C.values()) - min(C.values())) 25 | -------------------------------------------------------------------------------- /aoc2021/15.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 16/44 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | 9 | G = [list(map(int, l)) for l in lines()] 10 | Q = [(0, 0, 0)] 11 | INF = 10 ** 10 12 | H = len(G) 13 | W = len(G[0]) 14 | 15 | for _ in range(4): 16 | G.extend([] for _ in range(H)) 17 | 18 | for dy in range(5): 19 | sy = dy * H 20 | 21 | for dx in range(5): 22 | if dx == dy == 0: continue 23 | 24 | dist = dy + dx 25 | 26 | for y in range(H): 27 | G[sy + y].extend((G[y][x] + dist - 1) % 9 + 1 for x in range(W)) 28 | 29 | 30 | W *= 5 31 | H *= 5 32 | 33 | 34 | D = [[INF] * W for _ in range(H)] 35 | D[0][0] = 0 36 | 37 | while Q: 38 | d, x, y = heappop(Q) 39 | if d > D[y][x]: continue 40 | 41 | if x == W-1 and y == H-1: 42 | print(d) 43 | 44 | for dx, dy in DIR: 45 | nx, ny = x + dx, y + dy 46 | if not 0 <= nx < W or not 0 <= ny < H: continue 47 | nd = d + G[ny][nx] 48 | if nd < D[ny][nx]: 49 | D[ny][nx] = nd 50 | heappush(Q, (nd, nx, ny)) 51 | 52 | -------------------------------------------------------------------------------- /aoc2021/15_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace("_post.py", ".in")) 6 | 7 | 8 | G = [list(map(int, l)) for l in lines()] 9 | Q = [(0, 0, 0)] 10 | INF = 10 ** 10 11 | H = len(G) 12 | W = len(G[0]) 13 | 14 | RW, RH = W * 5, H * 5 15 | 16 | D = [[INF] * RW for _ in range(RH)] 17 | D[0][0] = 0 18 | 19 | V = set(product(range(RW), range(RH))) # valid tiles 20 | 21 | while Q: 22 | d, x, y = heappop(Q) 23 | 24 | if (x+1, y+1) in ((RW, RH), (W, H)): 25 | prints(d) 26 | 27 | for nx, ny in neighbours(x, y, V=V): 28 | # It's simple to implicitly extend the graph in this way 29 | dist = nx // W + ny // H 30 | nd = d + (G[ny % H][nx % W] + dist - 1) % 9 + 1 31 | if nd < D[ny][nx]: 32 | D[ny][nx] = nd 33 | heappush(Q, (nd, nx, ny)) 34 | 35 | -------------------------------------------------------------------------------- /aoc2021/16.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 27/22 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | 9 | DEC = '''0 = 0000 10 | 1 = 0001 11 | 2 = 0010 12 | 3 = 0011 13 | 4 = 0100 14 | 5 = 0101 15 | 6 = 0110 16 | 7 = 0111 17 | 8 = 1000 18 | 9 = 1001 19 | A = 1010 20 | B = 1011 21 | C = 1100 22 | D = 1101 23 | E = 1110 24 | F = 1111'''.split('\n') 25 | 26 | D = {} 27 | for l in DEC: 28 | a, b = l.split(' = ') 29 | D[a] = b 30 | 31 | I = ''.join(D[v] for v in input()) 32 | 33 | packets = [] 34 | S = [] 35 | 36 | def f(i): 37 | V = int(I[i:i+3], 2) 38 | i += 3 39 | ID = int(I[i:i+3], 2) 40 | i += 3 41 | 42 | if ID == 4: 43 | s = [] 44 | while True: 45 | s.append(I[i+1:i+5]) 46 | if I[i] == '0': break 47 | i += 5 48 | 49 | i += 5 50 | 51 | packets.append((V, ID, int(''.join(s), 2))) 52 | S.append(packets[-1][-1]) 53 | else: 54 | lid = int(I[i]) 55 | i += 1 56 | 57 | 58 | if lid == 0: 59 | le = int(I[i:i+15], 2) 60 | i += 15 61 | 62 | start = i 63 | cnt = 0 64 | while i - start < le: 65 | i = f(i) 66 | cnt += 1 67 | else: 68 | cnt = int(I[i:i+11], 2) 69 | i += 11 70 | 71 | for _ in range(cnt): 72 | i = f(i) 73 | 74 | packets.append((V, ID, cnt)) 75 | 76 | sub = [S.pop() for _ in range(cnt)] 77 | sub.reverse() 78 | 79 | if ID == 0: 80 | r = sum(sub) 81 | elif ID == 1: 82 | r = 1 83 | for v in sub: r *= v 84 | elif ID == 2: 85 | r = min(sub) 86 | elif ID == 3: 87 | r = max(sub) 88 | elif ID == 5: 89 | r = int(sub[0] > sub[1]) 90 | elif ID == 6: 91 | r = int(sub[0] < sub[1]) 92 | elif ID == 7: 93 | r = int(sub[0] == sub[1]) 94 | 95 | S.append(r) 96 | 97 | return i 98 | 99 | f(0) 100 | 101 | print(sum(v for v, _, _ in packets)) 102 | prints(S.pop()) 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /aoc2021/17.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 17/7 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | x1, x2, y1, y2 = ints(input()) 9 | 10 | best = -1000 11 | r = 0 12 | for yvel in range(y1, 300): 13 | for xvel in range(x2+1): 14 | vel = Point.of(xvel, yvel) 15 | p = Point.of(0, 0) 16 | 17 | hy = 0 18 | 19 | while True: 20 | p += vel 21 | vel.x -= sign(vel.x) 22 | vel.y -= 1 23 | 24 | hy = max(hy, p.y) 25 | 26 | if x1 <= p.x <= x2 and y1 <= p.y <= y2: 27 | best = max(best, hy) 28 | r += 1 29 | break 30 | 31 | if p.y < y1 or p.x > x2: break 32 | 33 | print(best) 34 | prints(r) 35 | -------------------------------------------------------------------------------- /aoc2021/17_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace("_post.py", ".in")) 6 | 7 | x1, x2, y1, y2 = ints(input()) 8 | 9 | def tri(x: int) -> int: 10 | return x * (x+1) // 2 11 | 12 | best = -1000 13 | r = 0 14 | for yvel in range(y1, abs(y1)): 15 | def ypos(t: int) -> int: 16 | return yvel * t - tri(t-1) 17 | 18 | start = binary_search(lambda t: ypos(t) <= y2, 1) 19 | for xvel in range(int(x1 ** .5), x2+1): 20 | def xpos(t: int) -> int: 21 | if t <= xvel: 22 | return xvel * t - tri(t-1) 23 | return tri(xvel) 24 | 25 | t = start 26 | while y1 <= ypos(t) <= y2: 27 | if x1 <= xpos(t) <= x2: 28 | r += 1 29 | if yvel >= 0: 30 | best = max(best, tri(yvel)) 31 | break 32 | 33 | t += 1 34 | 35 | print(best) 36 | prints(r) 37 | -------------------------------------------------------------------------------- /aoc2021/18.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 23/19 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | L = lines() 9 | res = eval(L[0]) 10 | 11 | def addl(a, v): 12 | assert isinstance(a, list) 13 | if isinstance(a[1], int): 14 | a[1] += v 15 | else: 16 | addl(a[1], v) 17 | 18 | def addr(a, v): 19 | assert isinstance(a, list) 20 | if isinstance(a[0], int): 21 | a[0] += v 22 | else: 23 | addr(a[0], v) 24 | 25 | def explode(a, depth=0): 26 | if isinstance(a, list): 27 | if depth == 4: 28 | return True, a[0], a[1] 29 | 30 | r = explode(a[0], depth+1) 31 | if r is not None: 32 | imm, b, c = r 33 | if imm: 34 | a[0] = 0 35 | 36 | if isinstance(a[1], int): 37 | a[1] += c 38 | else: 39 | addr(a[1], c) 40 | 41 | c = 0 42 | return False, b, c 43 | 44 | r = explode(a[1], depth+1) 45 | if r is not None: 46 | imm, b, c = r 47 | if imm: 48 | a[1] = 0 49 | 50 | if isinstance(a[0], int): 51 | a[0] += b 52 | else: 53 | addl(a[0], b) 54 | 55 | b = 0 56 | return False, b, c 57 | 58 | def split(a): 59 | if isinstance(a, list): 60 | for i in range(2): 61 | if isinstance(a[i], int): 62 | if a[i] >= 10: 63 | a[i] = [a[i] // 2, (a[i] + 1) // 2] 64 | return True 65 | elif split(a[i]): 66 | return True 67 | 68 | 69 | def reduce(): 70 | while True: 71 | if explode(res): 72 | pass 73 | elif split(res): 74 | pass 75 | else: 76 | break 77 | 78 | def magn(a): 79 | if isinstance(a, int): 80 | return a 81 | 82 | return 3 * magn(a[0]) + 2 * magn(a[1]) 83 | 84 | print(res) 85 | reduce() 86 | print(res) 87 | 88 | for l in L[1:]: 89 | b = eval(l) 90 | res = [res, b] 91 | reduce() 92 | print(res) 93 | 94 | 95 | print(magn(res)) 96 | 97 | 98 | best = 0 99 | for i in range(len(L)): 100 | for j in range(len(L)): 101 | if i != j: 102 | res = [eval(L[i]), eval(L[j])] 103 | reduce() 104 | best = max(best, magn(res)) 105 | 106 | prints(best) 107 | -------------------------------------------------------------------------------- /aoc2021/20.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from functools import reduce 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | alg, _, *I = lines() 10 | 11 | D = {(x, y): c == "#" for y, l in enumerate(I) for x, c in enumerate(l)} 12 | 13 | H, W = len(I), len(I[0]) 14 | 15 | for time in range(50): 16 | D = { 17 | (x, y): alg[ 18 | reduce( 19 | lambda acc, v: acc * 2 + v, 20 | ( 21 | D.get((x + dx, y + dy), time % 2) 22 | for dy in range(-1, 2) 23 | for dx in range(-1, 2) 24 | ), 25 | ) 26 | ] 27 | == "#" 28 | for y in range(-time - 1, H + time + 1) 29 | for x in range(-time - 1, W + time + 1) 30 | } 31 | 32 | if time in (1, 49): 33 | prints(sum(D.values())) 34 | -------------------------------------------------------------------------------- /aoc2021/21.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 3/19 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | 9 | _, a, _, b = ints() 10 | 11 | @lru_cache(maxsize=None) 12 | def f(a, b, s1, s2, my_turn): 13 | 14 | res = 0 15 | tot = 0 16 | for roll in product(range(1, 4), repeat=3): 17 | na = (a + sum(roll) - 1) % 10 + 1 18 | 19 | if s1 + na >= 21: 20 | res += my_turn 21 | tot += 1 22 | else: 23 | x, y = f(b, na, s2, s1 + na, not my_turn) 24 | res += x 25 | tot += y 26 | 27 | return res, tot 28 | 29 | 30 | w1, total = f(a, b, 0, 0, True) 31 | print(max(w1, total - w1)) 32 | 33 | rolls = 0 34 | s1 = s2 = 0 35 | 36 | die = cycle(range(1, 101)) 37 | 38 | while True: 39 | rolls += 3 40 | r = sum(next(die) for _ in range(3)) 41 | a = (a + r - 1) % 10 + 1 42 | s1 += a 43 | 44 | if s1 >= 1000: 45 | break 46 | 47 | a, b, s1, s2 = b, a, s2, s1 48 | 49 | print(s2 * rolls) 50 | -------------------------------------------------------------------------------- /aoc2021/22.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 3/>100 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | 9 | L = 50 10 | cub = [] 11 | D = {} 12 | for l in lines(): 13 | x1, x2, y1, y2, z1, z2 = ints(l) 14 | on = int(l.startswith("on")) 15 | 16 | me = (x1, x2, y1, y2, z1, z2) 17 | 18 | ncub = [] 19 | for pcub in cub: 20 | avoid = any(me[i + 1] < pcub[i] or me[i] > pcub[i + 1] for i in range(0, 6, 2)) 21 | 22 | if avoid: 23 | ncub.append(pcub) 24 | else: 25 | for split_at in range(0, 6, 2): 26 | def f(cur, i): 27 | if i == 6: 28 | yield cur 29 | else: 30 | (x, y), (a, b) = me[i : i + 2], pcub[i : i + 2] 31 | if i < split_at: 32 | yield from f(cur + (max(x, a), min(y, b)), i + 2) 33 | elif i > split_at: 34 | yield from f(cur + (a, b), i + 2) 35 | else: 36 | if a < x: 37 | yield from f(cur + (a, x - 1), i + 2) 38 | if y < b: 39 | yield from f(cur + (y + 1, b), i + 2) 40 | 41 | ncub.extend(f((), 0)) 42 | 43 | cub = ncub 44 | if on: 45 | cub.append(me) 46 | 47 | for x in range(max(x1, -L), min(x2, L) + 1): 48 | for y in range(max(y1, -L), min(y2, L) + 1): 49 | for z in range(max(z1, -L), min(z2, L) + 1): 50 | D[(x, y, z)] = on 51 | 52 | print(sum(D.values())) 53 | 54 | 55 | res = 0 56 | for x1, x2, y1, y2, z1, z2 in cub: 57 | res += (x2 + 1 - x1) * (y2 + 1 - y1) * (z2 + 1 - z1) 58 | 59 | prints(res) 60 | -------------------------------------------------------------------------------- /aoc2021/24.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace("py", "in")) 6 | 7 | FS = sys.stdin.read() 8 | secs = FS.split('inp w\n')[1:] 9 | secs = [[s.split() for s in lines(sec)] for sec in secs] 10 | N = len(secs) 11 | assert N == 14 12 | 13 | cache = [set() for _ in range(14)] 14 | VARS = 'wxyz' 15 | 16 | def run(sec_no: int, pz: int) -> int: 17 | if sec_no == N or pz > 10 ** 7: 18 | return 0 if pz == 0 else -1 19 | 20 | if pz in cache[sec_no]: return -1 21 | 22 | #for w in range(9, 0, -1): 23 | for w in range(1, 10): 24 | vs = [0] * 4 25 | vs[0] = vs[1] = w 26 | vs[3] = pz 27 | 28 | def get(x): 29 | if x[0] == '-' or x.isdigit(): 30 | return int(x) 31 | return vs[ord(x) - ord('w')] 32 | 33 | for instr, *args in secs[sec_no]: 34 | if instr == 'inp': 35 | assert False 36 | else: 37 | x, y = args 38 | x = ord(x) - ord('w') 39 | y = get(y) 40 | 41 | if instr == 'add': 42 | vs[x] += y 43 | elif instr == 'mul': 44 | vs[x] *= y 45 | elif instr == 'div': 46 | assert y != 0 47 | vs[x] //= y 48 | elif instr == 'mod': 49 | assert vs[x] >= 0 50 | assert y > 0 51 | vs[x] %= y 52 | elif instr == 'eql': 53 | vs[x] = int(vs[x] == y) 54 | 55 | r = run(sec_no + 1, vs[-1]) 56 | if r != -1: 57 | return r + w * 10 ** (N - sec_no - 1) 58 | 59 | cache[sec_no].add(pz) 60 | return -1 61 | 62 | print(run(0, 0)) 63 | -------------------------------------------------------------------------------- /aoc2021/24_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace("_post.py", ".in")) 6 | 7 | FS = sys.stdin.read() 8 | secs = FS.split('inp w\n')[1:] 9 | secs = [[s.split() for s in lines(sec)] for sec in secs] 10 | N = len(secs) 11 | assert N == 14 12 | 13 | op_map = dict(zip('add mul div mod'.split(), '+ * // %'.split())) 14 | 15 | generated_code = '' 16 | for i, sec in enumerate(secs): 17 | opt = [] 18 | for instr, x, y in sec: 19 | if instr == 'eql': 20 | opt.append(f'{x} = int({x} == {y})') 21 | else: 22 | opt.append(f'{x} {op_map[instr]}= {y}') 23 | 24 | generated_code += f'def f_{i}(w, z, x=0, y=0):\n' 25 | opt.append('return z') 26 | generated_code += '\n'.join('\t' + s for s in opt) + '\n\n' 27 | 28 | generated_code += 'funs = [' + ', '.join(f'f_{i}' for i in range(14)) + ']' 29 | exec(generated_code) 30 | 31 | cache = [set() for _ in range(14)] 32 | 33 | def run(sec_no: int, pz: int) -> int: 34 | if sec_no == N: #or pz > 10 ** 7: 35 | return 0 if pz == 0 else -1 36 | 37 | if pz in cache[sec_no]: return -1 38 | 39 | for w in range(9, 0, -1): 40 | #for w in range(1, 10): 41 | nz = funs[sec_no](w, pz) 42 | r = run(sec_no + 1, nz) 43 | if r != -1: 44 | return r + w * 10 ** (N - sec_no - 1) 45 | 46 | cache[sec_no].add(pz) 47 | return -1 48 | 49 | print(run(0, 0)) 50 | -------------------------------------------------------------------------------- /aoc2021/24_post_partial.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | FS = sys.stdin.read() 5 | secs = FS.split("inp w\n")[1:] 6 | secs = [[s.split() for s in lines(sec)] for sec in secs] 7 | N = len(secs) 8 | assert N == 14 9 | 10 | 11 | def add(x, y, vs): 12 | vs[x] += y(vs) 13 | 14 | def mul(x, y, vs): 15 | vs[x] *= y(vs) 16 | 17 | def div(x, y, vs): 18 | vs[x] //= y(vs) 19 | 20 | def mod(x, y, vs): 21 | vs[x] %= y(vs) 22 | 23 | def eql(x, y, vs): 24 | vs[x] = int(vs[x] == y(vs)) 25 | 26 | 27 | from functools import partial 28 | 29 | fun_map = {fun.__name__: fun for fun in (add, mul, div, mod, eql)} 30 | 31 | opt_secs = [] 32 | for sec in secs: 33 | funs = [] 34 | for instr, x, y in sec: 35 | x = ord(x) - ord("w") 36 | yf = ( 37 | (lambda vs, y=int(y): y) 38 | if y[0] == "-" or y.isdigit() 39 | else (lambda vs, i=(ord(y) - ord("w")): vs[i]) 40 | ) 41 | funs.append(partial(fun_map[instr], x, yf)) 42 | 43 | opt_secs.append(funs) 44 | 45 | 46 | cache = [set() for _ in range(14)] 47 | VARS = "wxyz" 48 | 49 | 50 | def run(sec_no: int, pz: int) -> int: 51 | if sec_no == N or pz > 10 ** 7: 52 | return 0 if pz == 0 else -1 53 | 54 | if pz in cache[sec_no]: return -1 55 | 56 | for w in range(9, 0, -1): 57 | # for w in range(1, 10): 58 | vs = [0] * 4 59 | vs[0] = w 60 | vs[3] = pz 61 | 62 | for fun in opt_secs[sec_no]: fun(vs) 63 | 64 | r = run(sec_no + 1, vs[-1]) 65 | if r != -1: 66 | return r + w * 10 ** (N - sec_no - 1) 67 | 68 | cache[sec_no].add(pz) 69 | return -1 70 | 71 | 72 | print(run(0, 0)) 73 | -------------------------------------------------------------------------------- /aoc2021/24_z3_optimized.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | from util import * 3 | 4 | program = sys.stdin.read() 5 | 6 | parts = re.findall( 7 | r"""inp w 8 | mul x 0 9 | add x z 10 | mod x 26 11 | div z (\d+) 12 | add x (-?\d+) 13 | eql x w 14 | eql x 0 15 | mul y 0 16 | add y 25 17 | mul y x 18 | add y 1 19 | mul z y 20 | mul y 0 21 | add y w 22 | add y (\d+) 23 | mul y x 24 | add z y""", 25 | program, 26 | ) 27 | 28 | from z3 import * 29 | 30 | o = Optimize() 31 | 32 | cnt = Counter() 33 | VARS = "zw" 34 | vs = {(c, 0): Int(f"{c}_0") for c in VARS} 35 | o.add(vs[("z", 0)] == 0) 36 | 37 | 38 | def pv(x): 39 | a = vs[(x, cnt[x])] 40 | cnt[x] += 1 41 | b = Int(f"{x}_{cnt[x]}") 42 | vs[(x, cnt[x])] = b 43 | return a, b 44 | 45 | 46 | """ 47 | x = ((z % 26) + add_x != w) 48 | z //= div_z 49 | 50 | if x == 1: 51 | z = z * 26 + (w + add_y) 52 | """ 53 | 54 | objective = 0 55 | 56 | for args in parts: 57 | div, addx, addy = map(int, args) 58 | 59 | _, w = pv("w") 60 | objective = objective * 10 + w 61 | o.add(And(1 <= w, w <= 9)) 62 | 63 | pz, nz = pv("z") 64 | o.add(nz == If((pz % 26) + addx == w, pz / div, pz / div * 26 + w + addy)) 65 | 66 | o.add(nz == 0) 67 | 68 | print(o) 69 | 70 | o.set(priority="box") 71 | max_id = o.maximize(objective) 72 | min_id = o.minimize(objective) 73 | 74 | assert o.check() == sat 75 | 76 | print(max_id.value()) 77 | print(min_id.value()) 78 | -------------------------------------------------------------------------------- /aoc2021/25.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 69/54 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | G = [l.rstrip() for l in sys.stdin] 9 | H = len(G) 10 | W = len(G[0]) 11 | 12 | D = {(x, y): c for y, l in enumerate(G) for x, c in enumerate(l) if c != '.'} 13 | 14 | for step in range(10 ** 10): 15 | moved = False 16 | 17 | for C, (dx, dy) in zip('>v', ((1, 0), (0, 1))): 18 | ND = {} 19 | for (x, y), c in D.items(): 20 | nx, ny = (x + dx) % W, (y + dy) % H 21 | if c == C and (nx, ny) not in D: 22 | moved = True 23 | ND[(nx, ny)] = c 24 | else: 25 | ND[(x, y)] = c 26 | 27 | D = ND 28 | #print_coords(D, '.') 29 | 30 | if not moved: 31 | prints(step+1) 32 | break 33 | 34 | 35 | -------------------------------------------------------------------------------- /aoc2021/get_input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Fetch AoC input as files. 5 | By Asger Hautop Drewsen: https://github.com/Tyilo 6 | """ 7 | 8 | import sys 9 | from pathlib import Path 10 | from typing import Optional 11 | 12 | import requests 13 | 14 | YEAR = 2021 15 | URL_PREFIX = f"https://adventofcode.com/{YEAR}" 16 | 17 | 18 | def validate_session(session): 19 | test_url = f"{URL_PREFIX}/settings" 20 | r = session.get(test_url) 21 | return r.status_code == 200 and r.url == test_url 22 | 23 | 24 | session_cookie: Optional[str] 25 | try: 26 | with open(".session", "r") as f: 27 | session_cookie = f.read().strip() 28 | except FileNotFoundError: 29 | session_cookie = None 30 | 31 | while True: 32 | if not session_cookie: 33 | session_cookie = input("Session cookie value: ").strip() 34 | with open(".session", "w") as f: 35 | f.write(session_cookie) 36 | 37 | session = requests.Session() 38 | session.cookies.set("session", session_cookie, domain=".adventofcode.com", path="/") 39 | if validate_session(session): 40 | break 41 | 42 | print("That session cookie doesn't seem to work. Try again.") 43 | session_cookie = None 44 | 45 | 46 | for i in range(1, 26): 47 | path = Path(f"{i:02}.in") 48 | 49 | if path.exists(): 50 | continue 51 | 52 | r = session.get(f"{URL_PREFIX}/day/{i}/input") 53 | if r.ok: 54 | with path.open("wb") as fb: 55 | fb.write(r.content) 56 | print(f"Downloaded {path.name}") 57 | else: 58 | if r.status_code == 404: 59 | print(f"Day {i} not released yet") 60 | break 61 | else: 62 | sys.exit(f"Got unknown status code: {r.status_code}\n{r.text.strip()}") 63 | -------------------------------------------------------------------------------- /aoc2022/01.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_input(L), 3 | max_list(L, Best), 4 | writeln(Best), 5 | msort(L, Sorted), 6 | reverse(Sorted, [A, B, C | _]), 7 | P2 is A + B + C, 8 | writeln(P2). 9 | 10 | read_elf("", 0). 11 | read_elf(end_of_file, 0). 12 | read_elf(Line, E) :- 13 | atom_number(Line, X), 14 | read_line_to_string(user_input, NLine), 15 | read_elf(NLine, Y), 16 | E is X + Y. 17 | 18 | read_input(L) :- 19 | read_line_to_string(user_input, Line), 20 | (Line == end_of_file, L = []; 21 | read_elf(Line, E), 22 | read_input(Tail), 23 | L = [E | Tail] 24 | ). 25 | -------------------------------------------------------------------------------- /aoc2022/01.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 68/498 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | L = lines() 9 | 10 | s = 0 11 | best = 0 12 | S = [] 13 | for l in L: 14 | if l == "": 15 | best = max(best, s) 16 | S.append(s) 17 | s = 0 18 | else: 19 | s += int(l) 20 | best = max(best, s) 21 | S.append(s) 22 | print(best) 23 | 24 | prints(sum(sorted(S)[-3:])) 25 | -------------------------------------------------------------------------------- /aoc2022/02.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_input(L), 3 | part1(L, 0), 4 | part2(L, L2), 5 | part1(L2, 0). 6 | 7 | outcome(X, X, 3). 8 | outcome(X, Y, S) :- 9 | (mod(X+1, 3) =:= Y -> S is 6) ; S is 0. 10 | 11 | score(X, Y, S) :- 12 | outcome(X, Y, Z), 13 | S is Z + Y + 1. 14 | 15 | part1([], X) :- writeln(X). 16 | part1([[A, B] | Tail], X) :- 17 | score(A, B, Y), 18 | Z is X + Y, 19 | part1(Tail, Z). 20 | 21 | part2([], _). 22 | part2([[A, B] | Tail], [[A, M] | NTail]) :- 23 | M is mod(A + (B-1), 3), 24 | part2(Tail, NTail). 25 | 26 | read_input(L) :- 27 | read_line_to_string(user_input, Line), 28 | (Line == end_of_file, L = []; 29 | split_string(Line, " ", "", [A, B]), 30 | char_code(A, C1), 31 | char_code(B, C2), 32 | I1 is C1 - 65, 33 | I2 is C2 - 88, 34 | read_input(Tail), 35 | L = [[I1, I2] | Tail] 36 | ). -------------------------------------------------------------------------------- /aoc2022/02.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 4/3 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | res = 0 9 | 10 | S = "ABC" 11 | S2 = "XYZ" 12 | 13 | for l in lines(): 14 | a, b = l.split() 15 | 16 | 17 | i, j = S.index(a), S2.index(b) 18 | 19 | if j == 0: 20 | j = (i-1) % 3 21 | elif j == 1: 22 | j = i 23 | else: 24 | j = (i +1) % 3 25 | 26 | res += j+1 27 | if i == j: 28 | res += 3 29 | elif j == (i+1)%3: 30 | res += 6 31 | 32 | 33 | prints(res) 34 | -------------------------------------------------------------------------------- /aoc2022/03.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | 3 | main :- 4 | read_string(user_input, _, Data), 5 | split_string(Data, "\n", "\n", L), 6 | maplist(part1, L, LP1), 7 | sum_list(LP1, P1), 8 | writeln(P1), 9 | part2(L, 0). 10 | 11 | prio(C, X) :- 12 | (C #>= 96 -> X is C-96) ; X is (C-64)+26. 13 | 14 | part1(S, Z) :- 15 | string_length(S, L), 16 | HL is L // 2, 17 | sub_string(S, 0, HL, _, S1), 18 | sub_string(S, HL, HL, _, S2), 19 | string_code(_, S1, X), 20 | string_code(_, S2, X), 21 | prio(X, Z). 22 | 23 | 24 | common([], _). 25 | common([H | T], X) :- 26 | string_code(_, H, X), 27 | common(T, X). 28 | 29 | part2([], X) :- writeln(X). 30 | part2([A, B, C | T], X) :- 31 | common([A, B, C], D), 32 | prio(D, Y), 33 | Z is X + Y, 34 | part2(T, Z). 35 | -------------------------------------------------------------------------------- /aoc2022/03.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 132/54 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | res = 0 9 | 10 | 11 | L = lines() 12 | for t in tile(L, 3): 13 | for c in set(t[0]) & set(t[1]) & set(t[2]): 14 | if c.isupper(): 15 | res += ord(c) - ord('A') + 26+1 16 | else: 17 | res += ord(c) - ord('a') + 1 18 | 19 | 20 | prints(res) 21 | -------------------------------------------------------------------------------- /aoc2022/04.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | 3 | main :- 4 | read_input(L), 5 | part1(L), 6 | part2(L). 7 | 8 | inclusion([L1, R1, L2, R2]) :- 9 | (L1 #=< L2, R2 #=< R1) ; (L2 #=< L1, R1 #=< R2). 10 | 11 | part1(L) :- 12 | include(inclusion, L, Matches), 13 | length(Matches, N), 14 | writeln(N). 15 | 16 | overlap([L1, R1, L2, R2]) :- 17 | inclusion([L1, R1, X, X]), inclusion([L2, R2, X, X]). 18 | 19 | part2(L) :- 20 | include(overlap, L, Matches), 21 | length(Matches, N), 22 | writeln(N). 23 | 24 | read_input(L) :- 25 | read_line_to_string(user_input, Line), 26 | (Line == end_of_file -> L = []; 27 | term_string(L1-R1','L2-R2, Line), 28 | %split_string(Line, ",", "", Split), 29 | %maplist([P, [L, R]]>>term_string(L-R, P), Split, Parsed), 30 | %append(Parsed, Head), 31 | read_input(Tail), 32 | L = [[L1, R1, L2, R2] | Tail] 33 | %L = [Head | Tail] 34 | ). 35 | -------------------------------------------------------------------------------- /aoc2022/04.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 28/810 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | res = 0 9 | re2 = 0 10 | for l in lines(): 11 | a, b = l.split(',') 12 | l1, r1 = map(int, a.split('-')) 13 | l2, r2 = map(int, b.split('-')) 14 | 15 | 16 | ok = False 17 | for i in range(l1, r1+1): 18 | ok |= l2 <= i <= r2 19 | for i in range(l2, r2+1): 20 | ok |= l1 <= i <= r1 21 | 22 | 23 | re2 += bool(ok) 24 | #print(a, b, ok, r2) 25 | res += (l1 <= l2 and r2 <= r1) or (l2 <= l1 and r1 <= r2) 26 | 27 | 28 | prints(re2) 29 | -------------------------------------------------------------------------------- /aoc2022/04_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('_post.py', '.in')) 6 | 7 | p1 = p2 = 0 8 | for l1, r1, l2, r2 in map(ints, lines()): 9 | r1 *= -1; r2 *= -1 10 | p1 += (min(l1, l2), max(r1, r2)) in ((l1, r1), (l2, r2)) 11 | p2 += max(l1, l2) <= min(r1, r2) 12 | 13 | print(p1, p2, sep="\n") 14 | -------------------------------------------------------------------------------- /aoc2022/05.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_string(user_input, _, Data), 3 | re_split("\n\n", Data, L), 4 | maplist([S, OL]>>split_string(S, "\n", "\n", OL), L, [StackSpecWE, _, MoveSpec]), 5 | append(StackSpec, [_], StackSpecWE), 6 | numlist(0, 8, Ind), 7 | maplist({StackSpec}/[I, O]>>( 8 | convlist({I}/[S, C]>>( 9 | J is 4*I+2, string_code(J, S, C), 0' \= C 10 | ), StackSpec, O) 11 | ), Ind, Stacks), 12 | maplist([S, O]>>( 13 | split_string(S, " ", "", SS), 14 | convlist([X, Y]>>number_string(Y, X), SS, O) 15 | ), MoveSpec, Moves), 16 | 17 | crane(Moves, Stacks, P1, rev), 18 | writeans(P1), 19 | crane(Moves, Stacks, P2, norev), 20 | writeans(P2). 21 | 22 | 23 | writeans(Stacks) :- 24 | maplist([[C | _], C]>>true, Stacks, Codes), 25 | format('~s~n', [Codes]). 26 | 27 | crane([], S, S, _). 28 | crane([[A, B, C] | Tail], IS, OS, R) :- 29 | % Maybe it's time to learn about the dictionary datastructure. 30 | % Manipulating lists like this is tedious. 31 | nth1(B, IS, FromStack, ISWOFrom), 32 | length(FromLift, A), 33 | append(FromLift, FromRem, FromStack), 34 | nth1(B, IS1, FromRem, ISWOFrom), 35 | 36 | nth1(C, IS1, ToStack, ISWOTo), 37 | (R == rev -> reverse(FromLift, FromLiftR) ; FromLiftR = FromLift), 38 | append(FromLiftR, ToStack, NewToStack), 39 | nth1(C, IS2, NewToStack, ISWOTo), 40 | 41 | crane(Tail, IS2, OS, R). 42 | -------------------------------------------------------------------------------- /aoc2022/05.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 144/98 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | res = 0 9 | stack, moves = sys.stdin.read().split('\n\n') 10 | 11 | stacks = [[] for _ in range(10)] 12 | for l in (lines(stack)[:-1]): 13 | for i in range(9): 14 | c = l[4*i+1] 15 | if c != " ": 16 | stacks[i+1].append(c) 17 | 18 | for l in stacks: 19 | l.reverse() 20 | 21 | 22 | for l in lines(moves): 23 | a, b, c = ints(l) 24 | ss = [stacks[b].pop() for _ in range(a)] 25 | ss.reverse() 26 | stacks[c] += ss 27 | """ 28 | for _ in range(a): 29 | stacks[c].append(stacks[b].pop()) 30 | """ 31 | 32 | prints("".join(s[-1] for s in stacks if s)) 33 | -------------------------------------------------------------------------------- /aoc2022/05_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('_post.py', '.in')) 6 | 7 | res = 0 8 | stack, moves = sys.stdin.read().split('\n\n') 9 | stacks_ = [" ".join(l).split() for l in rotate(lines(stack)[:-1], times=-1)[1::4]] 10 | 11 | for part in (1, -1): 12 | stacks = [*map(list, stacks_)] 13 | for a, b, c in map(ints, lines(moves)): 14 | stacks[c-1] += [stacks[b-1].pop() for _ in range(a)][::part] 15 | 16 | print("".join(s.pop() for s in stacks)) 17 | -------------------------------------------------------------------------------- /aoc2022/06.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | % Very useful for debugging! 3 | %leash(-all), trace, 4 | read_line_to_string(user_input, Inp), 5 | process(Inp, 4), 6 | process(Inp, 14). 7 | 8 | process(Inp, N) :- 9 | aggregate_all(min(I + N), (sub_string(Inp, I, N, _, Sub), string_chars(Sub, Codes), is_set(Codes)), Ans), 10 | writeln(Ans). 11 | 12 | -------------------------------------------------------------------------------- /aoc2022/06.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 15/15 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | S = input() 9 | for i in range(len(S)+1): 10 | if len(set(S[max(0, i-14):i])) == 14: 11 | prints(i) 12 | break 13 | 14 | -------------------------------------------------------------------------------- /aoc2022/07.pl: -------------------------------------------------------------------------------- 1 | :- dynamic(size/2), dynamic(isdir/1). 2 | 3 | main :- 4 | % Very useful for debugging! 5 | %leash(-all), trace, 6 | read_string(user_input, _, Data), 7 | split_string(Data, "$", " \n", [_ | Commands]), 8 | process([], Commands), !, % Prevent backtracking over input parsing (I think) 9 | % Find all the directories with size less than 100000 10 | findall(S, (isdir(Dir), size(Dir, S), S =< 100000), Sizes), 11 | sum_list(Sizes, P1), 12 | writeln(P1), 13 | size(["/"], Used), % Get the total amount of used space into Used 14 | % Find all directories matching P2 criteria, sorted due to setof 15 | setof(S, Dir^(isdir(Dir), size(Dir, S), 70000000 - Used + S >= 30000000), [P2 | _]), 16 | writeln(P2). 17 | 18 | 19 | isdir(["/"]). 20 | 21 | process(_, []). 22 | process(Pwd, [Command | Tail]) :- 23 | % First split a command into lines, then split the first line on spaces. 24 | % "ls" becomes ["ls"], "cd X" becomes ["cd", X]. 25 | split_string(Command, "\n", "", [Cmd | CTail]), 26 | split_string(Cmd, " ", "", CSplit), 27 | % Process this command, then proceed with the remaining commands. 28 | process([CSplit | CTail], Pwd, NPwd), 29 | process(NPwd, Tail). 30 | 31 | process([["cd", "/"]] , _, ["/"]). 32 | process([["cd", ".."]], [_ | Pwd], Pwd). 33 | process([["cd", Dir]] , Pwd, [Dir | Pwd]). 34 | process([["ls"] | L], Pwd, Pwd) :- 35 | maplist([S, O]>>split_string(S, " ", "", O), L, LS), 36 | maplist({Pwd}/[[DirOrSize, Name]]>>( 37 | NP = [Name | Pwd], 38 | % The current directory contains the child. 39 | assertz(contains(Pwd, NP)), 40 | (DirOrSize == "dir" -> 41 | % If the listing is a directory, then assert it. 42 | assertz(isdir(NP)); 43 | % Otherwise assert the size of the file. 44 | number_string(S, DirOrSize), 45 | assertz(size(NP, S))) 46 | ), LS). 47 | 48 | size(Path, Size) :- 49 | % We only want to compute sizes for directories. 50 | % File sizes are asserted into the fact database. 51 | isdir(Path), 52 | % Get a list of the sizes of entries that the directory contains. 53 | findall(S, (contains(Path, NP), size(NP, S)), Sizes), 54 | sum_list(Sizes, Size). 55 | -------------------------------------------------------------------------------- /aoc2022/07.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 76/85 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | def Node(parent=None): 9 | return [{}, parent] 10 | 11 | root = Node() 12 | _root = root 13 | 14 | res = 0 15 | 16 | L = lines() 17 | i = 0 18 | while i < len(L): 19 | l = L[i] 20 | assert l.startswith("$") 21 | p = l[1:].split() 22 | if len(p) == 2 and p[0] == "cd": 23 | d = p[1] 24 | if d == "..": 25 | root = root[1] 26 | else: 27 | if d not in root[0]: 28 | root[0][d] = Node(root) 29 | 30 | root = root[0][d] 31 | i += 1 32 | elif len(p) == 1 and p[0] == "ls": 33 | i += 1 34 | 35 | while i < len(L) and not L[i].startswith("$"): 36 | a, d = L[i].split() 37 | if a == "dir": 38 | if d not in root[0]: 39 | root[0][d] = Node(root) 40 | else: 41 | root[0][d] = int(a) 42 | 43 | i += 1 44 | 45 | else: 46 | assert False 47 | 48 | 49 | LIM = 100000 50 | AVA = 70000000 51 | NEED = 30000000 52 | def f(n): 53 | global res 54 | assert isinstance(n, list) 55 | 56 | cur = 0 57 | for d in n[0].values(): 58 | if isinstance(d, list): 59 | cur += f(d) 60 | else: 61 | cur += d 62 | 63 | if cur <= LIM: 64 | res += cur 65 | 66 | return cur 67 | 68 | used = f(_root) 69 | #print(used) 70 | 71 | res = 1<<60 72 | 73 | def g(n): 74 | global res 75 | assert isinstance(n, list) 76 | 77 | cur = 0 78 | for d in n[0].values(): 79 | if isinstance(d, list): 80 | cur += g(d) 81 | else: 82 | cur += d 83 | 84 | if AVA - used + cur >= NEED: 85 | res = min(res, cur) 86 | 87 | return cur 88 | 89 | g(_root) 90 | 91 | prints(res) 92 | -------------------------------------------------------------------------------- /aoc2022/08.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | :- dynamic(height/3). 3 | 4 | main :- 5 | read_string(user_input, _, Data), 6 | string_lines(Data, Lines), 7 | length(Lines, N), 8 | 9 | forall((between(1, N, X), between(1, N, Y)), 10 | (nth1(Y, Lines, Line), 11 | string_code(X, Line, C), H is C - 0'0, 12 | assertz(height(X, Y, H))) 13 | ), !, 14 | 15 | %leash(-all), trace, 16 | aggregate_all(count, visible(X, Y, N), P1), 17 | %findall(true, visible(X, Y, N), Ls), 18 | %length(Ls, P1), 19 | format("Part 1: ~d~n", [P1]), 20 | aggregate_all(max(S), scenic_score(X, Y, N, S), P2), 21 | format("Part 2: ~d~n", [P2]). 22 | 23 | visible(X, Y, N) :- 24 | height(X, Y, H), 25 | H2 #< H, 26 | once( 27 | X is 1; Y is 1; X is N; Y is N; 28 | aggregate_all(max(H3), (X2 #< X, height(X2, Y, H3)), H2) ; 29 | aggregate_all(max(H3), (X2 #> X, height(X2, Y, H3)), H2) ; 30 | aggregate_all(max(H3), (Y2 #< Y, height(X, Y2, H3)), H2) ; 31 | aggregate_all(max(H3), (Y2 #> Y, height(X, Y2, H3)), H2) 32 | ). 33 | 34 | scenic_score(X, Y, N, S) :- 35 | height(X, Y, H), 36 | %format("~d ~d ~d ~d~n", [X, Y, N, H]), 37 | (aggregate_all(max(X2), (X2 #< X, H2 #>= H, height(X2, Y, H2)), XL) -> true; XL is 1), 38 | (aggregate_all(min(X2), (X2 #> X, H2 #>= H, height(X2, Y, H2)), XR) -> true; XR is N), 39 | (aggregate_all(max(Y2), (Y2 #< Y, H2 #>= H, height(X, Y2, H2)), YL) -> true; YL is 1), 40 | (aggregate_all(min(Y2), (Y2 #> Y, H2 #>= H, height(X, Y2, H2)), YR) -> true; YR is N), 41 | S is (X - XL) * (XR - X) * (Y - YL) * (YR - Y). 42 | -------------------------------------------------------------------------------- /aoc2022/08.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 20/5 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | res = 0 9 | 10 | L = [list(map(int, l)) for l in lines()] 11 | H = len(L) 12 | W = len(L[0]) 13 | print(H, W) 14 | 15 | for y, l in enumerate(L): 16 | for x, h in enumerate(l): 17 | ok = False 18 | seen = [] 19 | for dy, dx in DIR: 20 | cx, cy = x + dx, y + dy 21 | i = 0 22 | while 0 <= cx < W and 0 <= cy < H: 23 | i += 1 24 | if L[cy][cx] >= h: break 25 | cx += dx; cy += dy 26 | else: 27 | ok = True 28 | #break 29 | 30 | seen.append(i) 31 | 32 | #res += ok 33 | 34 | z = 1 35 | for v in seen: 36 | z *= v 37 | 38 | res = max(res, z) 39 | 40 | 41 | prints(res) 42 | -------------------------------------------------------------------------------- /aoc2022/09.pl: -------------------------------------------------------------------------------- 1 | % Memoize calls to pos (dynamic programming), otherwise 2 | % the program doesn't terminate in reasonable time. 3 | :- table pos/4 as dynamic. 4 | 5 | main :- 6 | % Very useful for debugging! 7 | %leash(-all), trace, 8 | read_input(0), 9 | aggregate_all(max(T), pos(0, T, _, _), FT), !, 10 | forall(member(Part-Tail, [1-1, 2-9]), 11 | (aggregate_all(count, X-Y, (between(0, FT, T), pos(Tail, T, X, Y)), Ans), 12 | format("Part ~d: ~d~n", [Part, Ans])) 13 | ). 14 | 15 | dir("R", 1, 0). 16 | dir("L", -1, 0). 17 | dir("U", 0, 1). 18 | dir("D", 0, -1). 19 | 20 | read_input(T) :- 21 | read_line_to_string(user_input, Line), 22 | ( Line == end_of_file -> !; 23 | split_string(Line, " ", "", [D, S]), 24 | number_string(Cnt, S), 25 | ST is T + 1, 26 | NT is T + Cnt, 27 | dir(D, DX, DY), 28 | forall(between(ST, NT, CT), ( 29 | PT is CT-1, 30 | pos(0, PT, PX, PY), 31 | NX is PX + DX, 32 | NY is PY + DY, 33 | assertz(pos(0, CT, NX, NY)) 34 | )), 35 | read_input(NT) 36 | ). 37 | 38 | pos(_, 0, 0, 0). 39 | pos(I, T, X, Y) :- 40 | I \= 0, T > 0, 41 | PT is T-1, 42 | pos(I, PT, PX, PY), 43 | H is I-1, 44 | pos(H, T, HX, HY), 45 | DX is HX - PX, 46 | DY is HY - PY, 47 | ( max(abs(DX), abs(DY)) =< 1 -> 48 | X = PX, Y = PY ; 49 | X is PX + sign(DX), 50 | Y is PY + sign(DY) 51 | ). 52 | 53 | -------------------------------------------------------------------------------- /aoc2022/09.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 72/21 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | res = 0 9 | 10 | h = Point.of(0, 0) 11 | 12 | tails = [Point.of(0, 0) for _ in range(9)] 13 | V = {tuple(tails[-1])} 14 | 15 | for l in lines(): 16 | d, i = l.split() 17 | i = int(i) 18 | d = "RULD".index(d) 19 | dx, dy = DIR[d] 20 | dp = Point.of(dx, dy) 21 | for _ in range(i): 22 | h += dp 23 | prev = h 24 | for j, t in enumerate(tails): 25 | diff = prev - t 26 | if h != t and max(abs(diff)) == 2: 27 | if abs(diff.x) == 2: 28 | diff.x //= 2 29 | 30 | if abs(diff.y) == 2: 31 | diff.y //= 2 32 | 33 | t += diff 34 | tails[j] = t 35 | prev = t 36 | 37 | V.add(tuple(t)) 38 | #print(h, t) 39 | 40 | 41 | prints(len(V)) 42 | -------------------------------------------------------------------------------- /aoc2022/10.pl: -------------------------------------------------------------------------------- 1 | :- dynamic value/2. 2 | 3 | value(1, 1). 4 | 5 | main :- 6 | read_input(1), 7 | aggregate_all(sum(T * X), (value(T, X), T mod 40 =:= 20), P1), 8 | format("Part 1: ~d~n", [P1]), 9 | %leash(-all), trace, 10 | forall(between(0, 5, Y), 11 | (forall(between(0, 39, X), 12 | (T is Y * 40 + X + 1, value(T, V), 13 | (abs(V - X) =< 1 -> C = "#" ; C = " "), 14 | write(C))), 15 | writeln("") 16 | )). 17 | 18 | read_input(T) :- 19 | read_line_to_string(user_input, Line), 20 | ( Line == end_of_file -> !; 21 | split_string(Line, " ", "", Split), 22 | process(Split, T, NT), 23 | read_input(NT) 24 | ). 25 | 26 | process(["noop"], T, NT) :- 27 | value(T, PV), 28 | NT is T + 1, 29 | assertz(value(NT, PV)). 30 | 31 | process(["addx", S], T, NT) :- 32 | process(["noop"], T, T1), 33 | value(T1, PV), 34 | NT is T1 + 1, 35 | number_string(N, S), 36 | NV is PV + N, 37 | assertz(value(NT, NV)). 38 | -------------------------------------------------------------------------------- /aoc2022/10.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('py', 'in')) 6 | 7 | res = 0 8 | 9 | L = [1] 10 | 11 | cyc = 0 12 | y = 0 13 | D = set() 14 | for i, l in enumerate(lines()): 15 | if l == "noop": 16 | ex = [L[-1]] 17 | else: 18 | x = int(l.split()[1]) 19 | ex = [L[-1], L[-1] + x] 20 | 21 | for x, s in enumerate(ex): 22 | j = cyc + x 23 | rs = L[-1] 24 | rx = j % 40 25 | if abs(rx - rs) <= 1: 26 | D.add((rx, y)) 27 | if j % 40 == 39: 28 | y += 1 29 | if j % 40 == 19: 30 | res += (j+1) * rs 31 | L += s, 32 | 33 | cyc += len(ex) 34 | 35 | print(res) 36 | print_coords(D) 37 | -------------------------------------------------------------------------------- /aoc2022/10_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('_post.py', '.in')) 6 | 7 | res = 0 8 | cycles = dict(zip("noop addx".split(), (1, 2))) 9 | cyc = 0 10 | X = 1 11 | for comm, *args in map(str.split, lines()): 12 | for _ in range(cycles[comm]): 13 | cyc += 1 14 | i = cyc % 40 15 | print("#" if abs((i-1)%40 - X) <= 1 else " ", end="") 16 | if i == 20: res += cyc * X 17 | if i == 0: print() 18 | 19 | if comm == "addx": 20 | X += int(args[0]) 21 | print(res) 22 | -------------------------------------------------------------------------------- /aoc2022/11.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(prolog_stack)). 2 | 3 | odds([X], [X]). 4 | odds([X, _ | Tail], [X | NTail]) :- odds(Tail, NTail). 5 | 6 | ints(String, Ints) :- 7 | string(String), 8 | re_foldl([re_match{0:I}, [I | T], T]>>true, "\\d+"/t, String, Ints, [], []). 9 | 10 | parse_monkey( 11 | Spec, 12 | monkey{id: Id, items: Items, test: Div, tId: TId, fId: FId, op: Op, throws: 0} 13 | ) :- 14 | split_string(Spec, "\n", "\n", [IDLine, ItemLine, OpLine, TestLine, TLine, FLine]), 15 | ints(IDLine, [Id]), 16 | ints(ItemLine, Items), 17 | re_matchsub("new = (\\S+) (\\S) (\\S+)"/t, OpLine, Sub, []), 18 | bagof(X, I^(between(1, 3, I), get_dict(I, Sub, X)), Op), 19 | ints(TestLine, [Div]), 20 | ints(TLine, [TId]), 21 | ints(FLine, [FId]). 22 | 23 | 24 | getv(Old, old, Old) :- !. 25 | getv(_, X, X) :- number(X). 26 | 27 | finalize(p1, V, New) :- New is V // 3. 28 | finalize(p2(Mod), V, New) :- New is V mod Mod. 29 | 30 | eval(Mode, Monkey, Old, New) :- 31 | [A, Op, B] = Monkey.op, 32 | maplist(getv(Old), [A, B], [AV, BV]), 33 | Term =.. [Op, AV, BV], 34 | finalize(Mode, Term, New). 35 | 36 | do_throw(Mode, Monkey, V, A, B) :- 37 | eval(Mode, Monkey, V, NV), 38 | (NV mod Monkey.test =:= 0 -> NId = Monkey.tId ; NId = Monkey.fId), 39 | nth0(NId, A, PM, Rest), 40 | NM = PM.put(items, [NV | PM.items]), 41 | nth0(NId, B, NM, Rest). 42 | 43 | 44 | process_monkey(Mode, Id, IState, OState) :- 45 | nth0(Id, IState, Monkey), 46 | 47 | foldl(do_throw(Mode, Monkey), Monkey.items, IState, TState), 48 | length(Monkey.items, N), 49 | 50 | nth0(Id, TState, Monkey, TStateWOMonkey), 51 | NThrows is Monkey.throws + N, 52 | NMonkey = Monkey.put([throws:NThrows, items:[]]), 53 | nth0(Id, OState, NMonkey, TStateWOMonkey). 54 | 55 | 56 | rounds(_, 0, A, A). 57 | rounds(Mode, T, A, C) :- T > 0, 58 | NT is T - 1, 59 | maplist(get_dict(id), A, Ids), 60 | foldl(process_monkey(Mode), Ids, A, B), 61 | rounds(Mode, NT, B, C). 62 | 63 | extract_ans(Monkeys, Ans) :- 64 | maplist(get_dict(throws), Monkeys, Throws), 65 | sort(0, @>=, Throws, [A, B | _]), 66 | Ans is A * B. 67 | 68 | 69 | main :- 70 | read_string(user_input, _, Data), 71 | re_split("\n\n", Data, Split), 72 | odds(Split, MonkeySpecs), 73 | maplist(parse_monkey, MonkeySpecs, Monkeys), 74 | 75 | rounds(p1, 20, Monkeys, P1Monkeys), 76 | 77 | extract_ans(P1Monkeys, P1), 78 | format("Part 1: ~d~n", [P1]), 79 | 80 | maplist(get_dict(test), Monkeys, Tests), 81 | foldl([T, O, R]>>(R is O * T), Tests, 1, Mod), 82 | rounds(p2(Mod), 10_000, Monkeys, P2Monkeys), 83 | 84 | extract_ans(P2Monkeys, P2), 85 | format("Part 2: ~d~n", [P2]). 86 | -------------------------------------------------------------------------------- /aoc2022/11.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 25/71 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | 9 | monkeys = sys.stdin.read().split('\n\n') 10 | 11 | def do_op(op: str, old: tuple[int]) -> tuple[int]: 12 | return tuple( 13 | eval(op, globals={"old": io}) % m[2] 14 | for io, m in zip(old, mon)) 15 | 16 | mon = [] 17 | for i, monk in enumerate(monkeys): 18 | _, st, op, t, ift, iff = lines(monk) 19 | 20 | items = list(ints(st)) 21 | mon.append(( 22 | [tuple([x] * 8) for x in items], 23 | op.split(": ")[1].split("new = ")[1], 24 | next(ints(t)), 25 | next(ints(ift)), 26 | next(ints(iff)))) 27 | print(mon[-1]) 28 | 29 | #print(st, op, t, ift, iff) 30 | 31 | throws = [0] * len(mon) 32 | for rnd in range(10**4): 33 | if rnd % 10 == 0: 34 | print(rnd) 35 | for i, (st, op, t, ift, iff) in enumerate(mon): 36 | assert ift != i and iff != i 37 | its = st[::] 38 | st.clear() 39 | for it in its: 40 | throws[i] += 1 41 | worry = do_op(op, it) #// 3 42 | 43 | if worry[i] % t == 0: 44 | mon[ift][0].append(worry) 45 | else: 46 | mon[iff][0].append(worry) 47 | 48 | 49 | throws.sort(reverse=True) 50 | print(throws) 51 | print(throws[0] * throws[1]) 52 | 53 | -------------------------------------------------------------------------------- /aoc2022/11_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace("_post.py", ".in")) 6 | 7 | 8 | monkeys = sys.stdin.read().split("\n\n") 9 | 10 | for part, rounds in enumerate((20, 10**4), start=1): 11 | mon = [] 12 | reduce = 1 13 | for i, monk in enumerate(monkeys): 14 | _, st, ops, ts, ifts, iffs = lines(monk) 15 | 16 | mon.append( 17 | ( 18 | [*ints(st)], 19 | eval("lambda old: " + ops.split("new = ")[1]), 20 | next(ints(ts)), 21 | next(ints(ifts)), 22 | next(ints(iffs)), 23 | ) 24 | ) 25 | reduce *= mon[-1][2] 26 | # print(mon[-1]) 27 | 28 | throws = [0] * len(mon) 29 | for rnd in range(rounds): 30 | for i, (its, op, t, ift, iff) in enumerate(mon): 31 | for worry in map(op, its): 32 | throws[i] += 1 33 | if part == 1: worry //= 3 34 | else: worry %= reduce 35 | mon[ift if worry % t == 0 else iff][0].append(worry) 36 | 37 | its.clear() 38 | 39 | a, b = sorted(throws)[-2:] 40 | print(a * b) 41 | -------------------------------------------------------------------------------- /aoc2022/12.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | :- dynamic value/2. 3 | :- table path(_, _, min) as subsumptive, can_traverse/2. 4 | 5 | height('S', H) :- height(a, H). 6 | height('E', H) :- height(z, H). 7 | height(C, H) :- C \= 'E', C \= 'S', 8 | char_code(C, Code), 9 | H is Code - 97. 10 | 11 | main :- 12 | read_string(user_input, _, Data), 13 | split_string(Data, "\n", "\n", Lines), 14 | length(Lines, H), 15 | forall(between(1, H, Y), 16 | ( 17 | nth1(Y, Lines, Line), string_chars(Line, Chars), length(Chars, W), 18 | forall(between(1, W, X), 19 | ( 20 | nth1(X, Chars, C), 21 | assertz(value(X-Y, C)) 22 | ) 23 | ) 24 | ) 25 | ), !, 26 | %leash(-all), trace, 27 | value(Start, 'S'), 28 | value(End, 'E'), 29 | path(Start, End, P1), 30 | format("Part 1: ~w~n", [P1]), 31 | aggregate_all(min(C), (value(St, a), path(St, End, C)), P2), 32 | format("Part 2: ~w~n", [P2]). 33 | 34 | path(P, P, 0). 35 | % This rule is not good! 36 | %path(A, B, X) :- path(B, A, X). 37 | path(A, C, X) :- 38 | can_traverse(A, B), 39 | path(B, C, V1), 40 | X is V1 + 1. 41 | 42 | can_traverse(A, B) :- 43 | X1-Y1 = A, X2-Y2 = B, 44 | DX #= X2 - X1, DY #= Y2 - Y1, 45 | abs(DX) + abs(DY) #= 1, 46 | label([X2, Y2]), 47 | value(A, V1), height(V1, H1), 48 | value(B, V2), height(V2, H2), 49 | H2 - H1 =< 1. 50 | -------------------------------------------------------------------------------- /aoc2022/12.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 30/19 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | G = [list(l) for l in lines()] 9 | H = len(G) 10 | W = len(G[0]) 11 | 12 | D = {} 13 | Q = [] 14 | for y, l in enumerate(G): 15 | for x, c in enumerate(l): 16 | if c == "S": 17 | start = (x, y) 18 | c = l[x] = "a" 19 | elif c == "E": 20 | end = (x, y) 21 | l[x] = "z" 22 | if c == "a": 23 | start = (x, y) 24 | D[start] = 0 25 | Q.append(start) 26 | 27 | for x, y in Q: 28 | d = D[(x, y)] 29 | if (x, y) == end: 30 | prints(d) 31 | break 32 | 33 | h = G[y][x] 34 | for nx, ny in neighbours(x, y): 35 | if 0 <= nx < W and 0 <= ny < H: 36 | nd = d + 1 37 | if nd < D.get((nx, ny), 1<<30) and ord(G[ny][nx]) - ord(h) <= 1: 38 | D[(nx, ny)] = nd 39 | Q.append((nx, ny)) 40 | else: 41 | assert False 42 | -------------------------------------------------------------------------------- /aoc2022/13.pl: -------------------------------------------------------------------------------- 1 | read_lines(L) :- 2 | read_line_to_string(user_input, Line), 3 | (Line == end_of_file -> !, L = [] ; 4 | read_lines(Tail), 5 | (Line == "" -> L = Tail ; 6 | term_string(Term, Line), 7 | is_list(Term), 8 | L = [Term | Tail] 9 | ) 10 | ). 11 | 12 | 13 | cmp([], [], '='). 14 | cmp([], [_|_], '<'). 15 | cmp([_|_], [], '>'). 16 | cmp([A|As], [B|Bs], C) :- 17 | cmp(A, B, T), 18 | (T == '=' -> 19 | cmp(As, Bs, C) ; 20 | C = T). 21 | 22 | cmp(X, Y, C) :- number(X), number(Y), compare(C, X, Y). 23 | cmp(X, Y, C) :- number(X), is_list(Y), cmp([X], Y, C). 24 | cmp(X, Y, C) :- is_list(X), number(Y), cmp(X, [Y], C). 25 | 26 | 27 | main :- 28 | read_lines(Lines), 29 | %leash(-all), trace, 30 | aggregate_all(sum(I+1), 31 | (append([Prefix, [A, B], _], Lines), length(Prefix, P), 32 | divmod(P, 2, I, 0), cmp(A, B, '<')), 33 | P1), 34 | format("Part 1: ~d~n", [P1]), 35 | 36 | aggregate_all(count, (member(A, Lines), cmp(A, 2, '<')), X), 37 | aggregate_all(count, (member(A, [2|Lines]), cmp(A, 6, '<')), Y), 38 | format("Part 2: ~d~n", [(X+1) * (Y+1)]). 39 | -------------------------------------------------------------------------------- /aoc2022/13.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 32/30 3 | from itertools import zip_longest 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace('py', 'in')) 8 | 9 | res = 0 10 | 11 | def compare(a, b): 12 | if isinstance(a, list): 13 | if not isinstance(b, list): 14 | return compare(a, [b]) 15 | 16 | for x, y in zip_longest(a, b, fillvalue=None): 17 | if x is None: return -1 18 | if y is None: return 1 19 | r = compare(x, y) 20 | if r != 0: 21 | return r 22 | 23 | return 0 24 | elif isinstance(b, list) and not isinstance(a, list): 25 | return compare([a], b) 26 | 27 | if a < b: 28 | return -1 29 | elif a > b: 30 | return 1 31 | return 0 32 | 33 | 34 | """ 35 | for i, pas in enumerate(ps): 36 | a, b = map(eval, lines(pas)) 37 | if compare(a, b) <= 0: 38 | print(i+1) 39 | res += i + 1 40 | """ 41 | class Comp: 42 | def __init__(self, x): 43 | self.x = x 44 | 45 | def __lt__(s, o): 46 | return compare(s.x, o.x) == -1 47 | 48 | ls = [eval(l) for l in lines() if l] 49 | ls.extend(([[2]], [[6]])) 50 | ls.sort(key=Comp) 51 | 52 | r = 1 53 | for i, l in enumerate(ls): 54 | if l == [[2]] or l == [[6]]: 55 | r *= i+1 56 | 57 | prints(r) 58 | -------------------------------------------------------------------------------- /aoc2022/14.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 184/107 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | rock = set() 9 | 10 | for coords in lines(): 11 | ps = [Point(list(map(int, l.split(',')))) for l in coords.split(' -> ')] 12 | for i in range(1, len(ps)): 13 | b = ps[i] 14 | a = ps[i-1] 15 | d = b - a 16 | d2 = Point([sign(v) for v in d]) 17 | 18 | rock.add(a) 19 | while a != b: 20 | a += d2 21 | rock.add(a) 22 | 23 | 24 | MAXY = max(p.y + 2 for p in rock) 25 | for x in range(-1000, 1000): 26 | rock.add(Point.of(x, MAXY)) 27 | print(MAXY) 28 | 29 | sand = set() 30 | t = 0 31 | done = False 32 | while True: 33 | p = Point.of(500, 0) 34 | if p in sand: 35 | prints(t) 36 | break 37 | while True: 38 | for dx, dy in ((0, 1), (-1, 1), (1, 1)): 39 | np = p + Point.of(dx, dy) 40 | if np in sand or np in rock: continue 41 | p = np 42 | if p.y > MAXY: 43 | prints(t) 44 | exit() 45 | break 46 | else: 47 | t += 1 48 | sand.add(p) 49 | break 50 | 51 | 52 | -------------------------------------------------------------------------------- /aoc2022/14_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('_post.py', '.in')) 6 | 7 | rock = set() 8 | 9 | for coords in lines(): 10 | ps = [Point([*map(int, l.split(','))]) for l in coords.split(' -> ')] 11 | for a, b in zip(ps, ps[1:]): 12 | d = b - a 13 | dist = d.manh_dist() 14 | d //= dist 15 | rock |= {a + d * i for i in range(dist+1)} 16 | 17 | 18 | MAXY = max(p.y for p in rock) + 2 19 | 20 | part1 = False 21 | for t in range(1<<30): 22 | p = Point.of(500, 0) 23 | if p in rock: 24 | prints(t) 25 | break 26 | 27 | while p.y < MAXY-1: 28 | for dx in (0, -1, 1): 29 | np = p + Point.of(dx, 1) 30 | if np in rock: continue 31 | p = np 32 | break 33 | else: 34 | break 35 | 36 | rock.add(p) 37 | if p.y == MAXY-1 and not part1: 38 | part1 = True 39 | print(t) 40 | 41 | 42 | -------------------------------------------------------------------------------- /aoc2022/15.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | :- dynamic beacon/2, sensor/3. 3 | 4 | read_input :- 5 | read_line_to_string(user_input, Line), 6 | (Line == end_of_file -> !; 7 | re_foldl([Match, [Num|Tail], Tail]>>(get_dict(0, Match, V), number_string(Num, V)), 8 | "-?\\d+", Line, [Sx, Sy, Bx, By], [], []), 9 | assertz(beacon(Bx, By)), 10 | Dist is abs(Bx - Sx) + abs(By - Sy), 11 | assertz(sensor(Sx, Sy, Dist)), 12 | read_input). 13 | 14 | prdom(X) :- 15 | copy_term([X], [X], Gs), 16 | writeln(Gs). 17 | 18 | ximp(Y, C) :- 19 | % Collect ranges of covered grid points on the line y=Y 20 | findall(L..R, ( 21 | sensor(Sx, Sy, Dist), Rem is Dist - abs(Sy - Y), 22 | L is Sx-Rem, R is Sx+Rem, L =< R 23 | ), [D1 | CTail]), 24 | % Union the ranges 25 | foldl([A,B,A\/B]>>true, CTail, D1, Dom), 26 | C in Dom. 27 | 28 | main :- 29 | read_input, !, 30 | P1Y is 2000000, 31 | %P1Y is 10, 32 | ximp(P1Y, P1V), 33 | % Remove known beacons on the line 34 | bagof(true, X^(beacon(X, P1Y), P1V #\= X), _), 35 | fd_size(P1V, P1), 36 | format("Part 1: ~d~n", [P1]), 37 | MAX is 4000000, 38 | %MAX is 20, 39 | [X, Y] ins 0..MAX, 40 | % Downwards is a bit faster on my input 41 | % It should terminate within a minute or so 42 | labeling([down], [Y]), 43 | ximp(Y, XD), fd_dom(XD, Dom), 44 | #\ X in Dom, indomain(X), !, 45 | /* 46 | * This code seems cleaner than the above, but unfortunately 47 | * it doesn't seem to terminate. At least not within a couple 48 | * of hours... 49 | bagof(true, Sx^Sy^Dist^( 50 | sensor(Sx, Sy, Dist), 51 | abs(X - Sx) + abs(Y - Sy) #> Dist), _), !, 52 | */ 53 | format("Part 2: ~d~n", [X*MAX + Y]). 54 | -------------------------------------------------------------------------------- /aoc2022/15.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 97/41 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | res = 0 9 | #Y = 10 10 | Y = 2000000 11 | 12 | bad = set() 13 | beac = set() 14 | MAX = 4000000 15 | #MAX = 20 16 | MAXM = 4000000 17 | bads = defaultdict(list) 18 | for l in lines(): 19 | sx, sy, bx, by = ints(l) 20 | 21 | if by == Y: beac.add(bx) 22 | 23 | dy = abs(by - sy) 24 | dx = abs(bx - sx) 25 | dist = dy + dx 26 | """ 27 | yd = abs(sy - Y) 28 | if yd > dist: 29 | continue 30 | """ 31 | 32 | for offy in range(-dist, dist+1): 33 | ry = sy + offy 34 | x1 = sx - (dist - abs(offy)) 35 | x2 = sx + (dist - abs(offy)) 36 | bads[ry].append((x1, x2)) 37 | #print(sx, sy, bx, by, dist) 38 | """ 39 | for x in range(sx - (dist - yd), sx + (dist - yd)+1): 40 | #print(x) 41 | bad.add(x) 42 | """ 43 | #print() 44 | 45 | #prints(len(bad - beac)) 46 | 47 | for y in range(MAX+1): 48 | l = bads[y] 49 | l.sort() 50 | done = 0 51 | #print(l) 52 | for x1, x2 in l: 53 | if done < x1: 54 | print(done, y) 55 | prints(done * MAXM + y) 56 | exit() 57 | 58 | done = max(done, x2+1) 59 | if done > MAX: break 60 | 61 | assert False 62 | 63 | -------------------------------------------------------------------------------- /aoc2022/15_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('_post.py', '.in')) 6 | 7 | #Y = 10 8 | Y = 2000000 9 | 10 | MAX = 4000000 11 | #MAX = 20 12 | MAXM = 4000000 13 | beacons = set() 14 | constraints = defaultdict(list) 15 | for sx, sy, bx, by in map(ints, lines()): 16 | if by == Y: beacons.add(bx) 17 | 18 | dist = abs(by - sy) + abs(bx - sx) 19 | for ry in range(max(0, sy-dist), min(sy+dist+1, MAX+1)): 20 | rem = dist - abs(ry-sy) 21 | constraints[ry].append((sx-rem, sx+rem)) 22 | 23 | L = sorted(constraints[Y]) 24 | i = L[0][0] 25 | j = i 26 | part1 = 0 27 | for x1, x2 in L: 28 | j = max(x1, j) 29 | if x2 >= j: 30 | part1 += x2 - j + 1 31 | j = x2+1 32 | 33 | print(part1 - sum(any(x1 <= x <= x2 for x1, x2 in L) for x in beacons)) 34 | 35 | for y in range(MAX+1): 36 | done = 0 37 | for x1, x2 in sorted(constraints[y]): 38 | if done < x1: 39 | prints(done * MAXM + y) 40 | exit() 41 | 42 | done = max(done, x2+1) 43 | if done > MAX: break 44 | 45 | assert False 46 | 47 | -------------------------------------------------------------------------------- /aoc2022/15_z3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python3 2 | import z3 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('_z3.py', '.in')) 7 | 8 | #Y = 10 9 | Y = 2000000 10 | 11 | MAX = 4000000 12 | #MAX = 20 13 | MAXM = 4000000 14 | beacons = set() 15 | bad = set() 16 | 17 | solver = z3.Solver() 18 | x, y = z3.Ints("X Y") 19 | for v in (x, y): 20 | solver.add(0 <= v, v <= MAX) 21 | 22 | def Abs(v): 23 | return z3.If(v < 0, -v, v) 24 | 25 | for sx, sy, bx, by in map(ints, lines()): 26 | if by == Y: beacons.add(bx) 27 | 28 | dist = abs(by - sy) + abs(bx - sx) 29 | rem = dist - abs(sy - Y) 30 | bad.update(range(sx - rem, sx + rem + 1)) 31 | 32 | solver.add(Abs(sx - x) + Abs(sy - y) > dist) 33 | 34 | print(len(bad - beacons)) 35 | 36 | assert solver.check() == z3.sat 37 | print(solver.model().eval(x * MAX + y)) 38 | -------------------------------------------------------------------------------- /aoc2022/16.pl: -------------------------------------------------------------------------------- 1 | :- dynamic tunnel/2, rate/2. 2 | :- set_prolog_flag(table_space, 4294967296). % 4 GiB 3 | :- table pressure(_, _, _, max), dist(_, _, min). 4 | 5 | read_input(Stream) :- 6 | read_line_to_string(Stream, Line), 7 | (Line == end_of_file -> !; 8 | re_matchsub("Valve (?\\w{2}) has flow rate=(?\\d+)", Line, 9 | re_match{0:_, name: Name, rate: RateS}, []), 10 | number_string(Rate, RateS), 11 | assertz(rate(Name, Rate)), 12 | re_split("valves? ", Line, [_, _, TunnelSpec]), 13 | re_foldl({Name}/[re_match{0:Next}, _, _]>>assertz(tunnel(Name, Next)), 14 | "\\w{2}", TunnelSpec, _, _, []), 15 | read_input(Stream)). 16 | 17 | dist(A, A, 0). 18 | dist(A, C, D) :- tunnel(A, B), dist(B, C, X), D is X+1. 19 | 20 | pressure(Time, Set, P) :- pressure(Time, "AA", Set, P). 21 | 22 | pressure(Time, _, [], 0) :- (Time >= 0 -> !). 23 | pressure(Time, Pos, Rem, Pres) :- Time >= 0, 24 | select(NPos, Rem, NRem), 25 | dist(Pos, NPos, Dist), 26 | NTime is Time - Dist - 1, 27 | pressure(NTime, NPos, NRem, NScore), 28 | rate(NPos, Rate), 29 | Pres is NScore + Rate * NTime. 30 | 31 | 32 | powerset([], []). 33 | powerset([_|T], P) :- powerset(T,P). 34 | powerset([H|T], [H|P]) :- powerset(T,P). 35 | 36 | main :- 37 | % The program takes a couple of minutes to run. 38 | % I'm not sure how to improve it. 😕 39 | %open("16.in", read, Stream), 40 | read_input(user_input), 41 | %close(Stream), 42 | setof(Name, Rate^(rate(Name, Rate), Rate > 0), NonZ), 43 | aggregate_all( 44 | max(Score), 45 | (powerset(NonZ, Open), is_ordset(Open), pressure(30, Open, Score)), 46 | P1 47 | ), 48 | format("Part 1: ~d~n", [P1]), 49 | aggregate_all( 50 | max(Score), 51 | (powerset(NonZ, Open), powerset(Open, Open1), 52 | pressure(26, Open1, S1), 53 | ord_subtract(Open, Open1, Open2), 54 | pressure(26, Open2, S2), 55 | Score is S1 + S2), 56 | P2 57 | ), 58 | format("Part 2: ~d~n", [P2]). 59 | 60 | -------------------------------------------------------------------------------- /aoc2022/18.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | :- dynamic lava/1. 3 | :- table air(_, _). 4 | 5 | read_input(Stream) :- 6 | read_line_to_string(Stream, Line), 7 | (Line == end_of_file -> !; 8 | split_string(Line, ",", "", Split), 9 | maplist(number_string, [X,Y,Z], Split), 10 | assertz(lava(p(X, Y, Z))), 11 | read_input(Stream)). 12 | 13 | neighbours(p(X, Y, Z), p(A, B, C)) :- 14 | abs(A-X) + abs(B-Y) + abs(C-Z) #= 1, 15 | label([A, B, C]). 16 | 17 | inbounds(p(X, Y, Z), b(XB, YB, ZB)) :- X in XB, Y in YB, Z in ZB. 18 | 19 | 20 | air(P, Bounds) :- \+ lava(P), 21 | (\+ inbounds(P, Bounds) ; (neighbours(P, NP), air(NP, Bounds))). 22 | 23 | main :- 24 | read_input(user_input), 25 | aggregate_all(count, (lava(P), neighbours(P, NP), \+ lava(NP)), P1), 26 | format("Part 1: ~d~n", [P1]), 27 | aggregate_all( 28 | b(min(X), min(Y), min(Z), max(X), max(Y), max(Z)), 29 | lava(p(X, Y, Z)), 30 | b(X1, Y1, Z1, X2, Y2, Z2) 31 | ), 32 | Bounds = b(X1..X2, Y1..Y2, Z1..Z2), 33 | aggregate_all(count, (lava(P), neighbours(P, NP), air(NP, Bounds)), P2), 34 | format("Part 2: ~d~n", [P2]). 35 | 36 | -------------------------------------------------------------------------------- /aoc2022/18.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 35/54 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | res = 0 9 | 10 | cubes = set(Point.of(*map(int, l.split(","))) for l in lines()) 11 | 12 | diffs = [] 13 | for dx, dy, dz in product(range(-1, 2), repeat=3): 14 | d = Point.of(dx, dy, dz) 15 | if d.manh_dist() != 1: continue 16 | diffs.append(d) 17 | 18 | mins = [1000] * 3 19 | maxs = [-1000] * 3 20 | 21 | for a in cubes: 22 | for i, v in enumerate(a): 23 | mins[i] = min(mins[i], v) 24 | maxs[i] = max(maxs[i], v) 25 | for d in diffs: 26 | if a + d not in cubes: 27 | res += 1 28 | 29 | 30 | print(res) 31 | res = 0 32 | 33 | def free(p, V): 34 | if p in cubes: return False 35 | for i, v in enumerate(p): 36 | if v < mins[i] or v > maxs[i]: 37 | return True 38 | 39 | for d in diffs: 40 | np = p + d 41 | if np not in V: 42 | V.add(np) 43 | if free(np, V): 44 | return True 45 | 46 | return False 47 | 48 | for a in cubes: 49 | for d in diffs: 50 | if free(a + d, {a+d}): 51 | res += 1 52 | 53 | 54 | prints(res) 55 | -------------------------------------------------------------------------------- /aoc2022/18_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace("_post.py", ".in")) 6 | 7 | cubes = set(Point([*ints(l)]) for l in lines()) 8 | 9 | dirs = [ 10 | d for t in product(range(-1, 2), repeat=3) if (d := Point(list(t))).manh_dist() == 1 11 | ] 12 | 13 | mins = [min(p[i] for p in cubes) for i in range(3)] 14 | maxs = [max(p[i] for p in cubes) for i in range(3)] 15 | 16 | res = sum(sum(p + d not in cubes for d in dirs) for p in cubes) 17 | print(f"Part 1: {res}") 18 | 19 | res = 0 20 | 21 | 22 | def dfs(p: Point[int], V=set()): 23 | global res 24 | 25 | if not all(mins[i] - 1 <= v <= maxs[i] + 1 for i, v in enumerate(p)) or p in V: 26 | return 27 | 28 | V.add(p) 29 | for d in dirs: 30 | np = p + d 31 | if np in cubes: 32 | res += 1 33 | else: 34 | dfs(np) 35 | 36 | 37 | dfs(Point([v - 1 for v in mins])) 38 | print(f"Part 2: {res}") 39 | -------------------------------------------------------------------------------- /aoc2022/19.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 15/4 3 | #import z3 4 | import gc 5 | from util import * 6 | 7 | if len(sys.argv) == 1: 8 | sys.stdin = open(__file__.replace('py', 'in')) 9 | 10 | 11 | res = 1 12 | 13 | for id, l in enumerate(lines(), start=1): 14 | _, ore, clay, oore, oclay, gore, gobs = ints(l) 15 | #if id == 1 or id == 2: continue 16 | if id == 4: break 17 | 18 | """ 19 | vars = { 20 | var: [z3.Int(f"{var}_{i}") for i in range(25)] 21 | for var in "ore clay obs geo r1 r2 r3 r4".split() 22 | } 23 | 24 | 25 | s = z3.Optimize() 26 | for var in "ore clay obs r2 r3 r4".split(): 27 | s.add(vars[var][0] == 0) 28 | 29 | s.add(vars["r1"][0] == 1) 30 | 31 | for t in range(1, 24): 32 | """ 33 | 34 | 35 | @lru_cache(maxsize=None) 36 | def f(i, o, c, obs, r1, r2, r3, r4): 37 | if i == 32: 38 | return 0 39 | 40 | #print(i, o, c, obs, r1, r2, r3, r4) 41 | best = f(i+1, o+r1, c+r2, obs+r3, r1, r2, r3, r4) 42 | if o >= gore and obs >= gobs: 43 | best = max(best, f(i+1, o-gore+r1, c+r2, obs-gobs+r3, r1, r2, r3, r4+1)) 44 | elif o >= oore and c >= oclay: 45 | best = max(best, f(i+1, o-oore+r1, c-oclay+r2, obs+r3, r1, r2, r3+1, r4)) 46 | else: 47 | canb = False 48 | if o >= ore and r1 < max(gore, oore, clay): 49 | canb = True 50 | best = max(best, f(i+1, o-ore+r1, c+r2, obs+r3, r1+1, r2, r3, r4)) 51 | 52 | if o >= clay and r2 < oclay: 53 | canb = True 54 | best = max(best, f(i+1, o-clay+r1, c+r2, obs+r3, r1, r2+1, r3, r4)) 55 | 56 | #if not canb: 57 | #best = max(best, f(i+1, o+r1, c+r2, obs+r3, r1, r2, r3, r4)) 58 | 59 | return best+r4 60 | 61 | 62 | print(id) 63 | lv = f(0, 0, 0, 0, 1, 0, 0, 0) 64 | #res += id * lv 65 | res *= lv 66 | print(id, lv) 67 | gc.collect() 68 | 69 | prints(res) 70 | -------------------------------------------------------------------------------- /aoc2022/19_z3.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | import z3 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("_z3.py", ".in")) 8 | 9 | 10 | def max_geodes(time: int, blueprint: str) -> int: 11 | _, r1_cost, r2_cost, r3_cost_ore, r3_cost_clay, r4_cost_ore, r4_cost_obs = ints( 12 | blueprint 13 | ) 14 | 15 | vars = [ 16 | { 17 | var: z3.Int(f"{var}_{i}") 18 | for var in "ore clay obs r1 r2 r3 b1 b2 b3 b4".split() 19 | } 20 | for i in range(time + 1) 21 | ] 22 | 23 | s = z3.Optimize() 24 | for var in "ore clay obs r2 r3".split(): 25 | s.add(vars[0][var] == 0) 26 | 27 | s.add(vars[0]["r1"] == 1) 28 | 29 | score = 0 30 | 31 | for t in range(time): 32 | vs = vars[t] 33 | if t: 34 | pvs = vars[t - 1] 35 | for i in range(1, 4): 36 | s.add(vs[f"r{i}"] == pvs[f"r{i}"] + pvs[f"b{i}"]) 37 | 38 | s.add( 39 | vs["ore"] 40 | == pvs["ore"] 41 | + pvs["r1"] 42 | - ( 43 | pvs["b1"] * r1_cost 44 | + pvs["b2"] * r2_cost 45 | + pvs["b3"] * r3_cost_ore 46 | + pvs["b4"] * r4_cost_ore 47 | ) 48 | ) 49 | 50 | s.add(vs["clay"] == pvs["clay"] + pvs["r2"] - (pvs["b3"] * r3_cost_clay)) 51 | s.add(vs["obs"] == pvs["obs"] + pvs["r3"] - (pvs["b4"] * r4_cost_obs)) 52 | 53 | bsum = 0 54 | for i in range(1, 5): 55 | v = vs[f"b{i}"] 56 | s.add(0 <= v, v <= 1) 57 | bsum += v 58 | 59 | s.add(bsum <= 1) 60 | 61 | for i, ore_cost in enumerate( 62 | (r1_cost, r2_cost, r3_cost_ore, r4_cost_ore), start=1 63 | ): 64 | s.add(vs[f"b{i}"] * ore_cost <= vs["ore"]) 65 | 66 | s.add(vs["b3"] * r3_cost_clay <= vs["clay"]) 67 | s.add(vs["b4"] * r4_cost_obs <= vs["obs"]) 68 | 69 | score += vs["b4"] * (time - t - 1) 70 | 71 | sc = s.maximize(score) 72 | 73 | assert s.check() == z3.sat 74 | # return s.model().eval(score).as_long() 75 | return sc.value().as_long() 76 | 77 | 78 | blueprints = lines() 79 | 80 | res = sum(id * max_geodes(24, b) for id, b in enumerate(blueprints, start=1)) 81 | print(f"Part 1: {res}") 82 | 83 | res = math.prod(max_geodes(32, b) for b in blueprints[:3]) 84 | print(f"Part 2: {res}") 85 | -------------------------------------------------------------------------------- /aoc2022/20.pl: -------------------------------------------------------------------------------- 1 | mix(0, _, L) :- printans(L). 2 | mix(Iters, Order, IL) :- 3 | smix(Order, IL, OL), 4 | NIters is Iters - 1, 5 | mix(NIters, Order, OL). 6 | 7 | smix([], L, L). 8 | smix([P | Tail], IL, OL) :- 9 | nth0(I, IL, P, Rest), 10 | _-Change = P, 11 | length(IL, N), 12 | J is (I + Change) mod (N-1), 13 | nth0(J, ML, P, Rest), 14 | % Discard choice points to save stack memory! 15 | !, 16 | smix(Tail, ML, OL). 17 | 18 | 19 | printans(L) :- 20 | length(L, N), 21 | nth0(I, L, _-0), 22 | aggregate_all(sum(C), (between(1, 3, X), J is (I+(X*1000)) mod N, nth0(J, L, _-C)), Ans), 23 | writeln(Ans). 24 | 25 | main :- 26 | read_string(user_input, _, Data), 27 | split_string(Data, "\n", "\n", Split), 28 | length(Split, N), 29 | numlist(1, N, Ind), 30 | maplist([S, I, I-V]>>number_string(V, S), Split, Ind, L), 31 | mix(1, L, L), 32 | maplist([I-V, I-V2]>>(V2 is V * 811589153), L, L2), 33 | mix(10, L2, L2). 34 | -------------------------------------------------------------------------------- /aoc2022/20.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace('py', 'in')) 6 | 7 | 8 | res = 0 9 | 10 | key = 811589153 11 | L = list((i, v * key) for i, v in enumerate(ints())) 12 | order = list(L) 13 | 14 | for _ in range(10): 15 | for p in order: 16 | i = L.index(p) 17 | L.pop(i) 18 | j = (i + p[1]) % len(L) 19 | L.insert(j, p) 20 | 21 | p = [p for p in order if p[1] == 0][0] 22 | i = L.index(p) 23 | N = len(L) 24 | for x in range(1, 4): 25 | res += L[(i+x*1000)%N][1] 26 | 27 | prints(res) 28 | -------------------------------------------------------------------------------- /aoc2022/21.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)), use_module(library(prolog_stack)). 2 | :- dynamic monkey/2. 3 | 4 | read_input :- 5 | read_line_to_string(user_input, Line), 6 | (Line == end_of_file -> !; 7 | split_string(Line, ":", " ", [Name, OpS]), 8 | split_string(OpS, " ", "", Op), 9 | assertz(monkey(Name, Op)), 10 | read_input). 11 | 12 | eval(Name, Res, Humn) :- 13 | (monkey(Name, Op) -> do_op(Op, Res, Humn); 14 | % Hack for part 2 15 | Name == "humn" -> Res = Humn). 16 | 17 | do_op([V], Res, Humn) :- (string(V) -> number_string(Res, V); 18 | Res = V, Humn = V). 19 | do_op([A, Op, B], Res, Humn) :- 20 | eval(A, VA, Humn), eval(B, VB, Humn), 21 | atom_string(F, Op), 22 | (F == / -> VB * Res #= VA ; 23 | Term =.. [F, VA, VB], 24 | Res #= Term). 25 | 26 | main :- 27 | read_input, 28 | eval("root", P1, _), 29 | format("Part 1: ~d~n", [P1]), 30 | 31 | retract(monkey("humn", _)), 32 | monkey("root", [A, _, B]), 33 | eval(A, VA, Humn), eval(B, VB, Humn), VA #= VB, 34 | format("Part 2: ~d~n", [Humn]). 35 | -------------------------------------------------------------------------------- /aoc2022/21.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 108/18 3 | import z3 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace('py', 'in')) 8 | 9 | 10 | res = 0 11 | 12 | adj = defaultdict(list) 13 | 14 | do = {} 15 | 16 | for l in lines(): 17 | name, op = l.split(': ') 18 | ops = op.split() 19 | if len(ops) == 1: 20 | do[name] = int(op) 21 | else: 22 | adj[ops[0]].append(name) 23 | adj[ops[2]].append(name) 24 | do[name] = ops 25 | 26 | 27 | L, cyclic = topsort(adj) 28 | assert not cyclic 29 | 30 | z3var = z3.Int("x") 31 | s = z3.Solver() 32 | 33 | do["humn"] = z3var 34 | 35 | val = {} 36 | for name in L: 37 | op = do[name] 38 | if not isinstance(op, list): 39 | val[name] = op 40 | else: 41 | a = val[op[0]] 42 | b = val[op[2]] 43 | 44 | if name == "root": 45 | s.add(a == b) 46 | 47 | assert s.check() == z3.sat 48 | prints(s.model().eval(z3var)) 49 | exit() 50 | 51 | c = op[1] 52 | 53 | if c == "+": 54 | r = a + b 55 | elif c == "-": 56 | r = a - b 57 | elif c == "*": 58 | r = a * b 59 | else: 60 | r = a / b 61 | 62 | val[name] = r 63 | 64 | prints(val["root"]) 65 | -------------------------------------------------------------------------------- /aoc2022/23.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | 3 | state(T, T, E, E) :- !. 4 | state(T, TEnd, IElves, OElves) :- 5 | T < TEnd, 6 | 7 | assoc_to_keys(IElves, LElves), 8 | maplist(propose(T, IElves), LElves, Proposals), 9 | 10 | transpose_pairs(Proposals, TProp), 11 | group_pairs_by_key(TProp, PJoined), 12 | maplist([P-L, P-N]>>length(L, N), PJoined, PWCnt), 13 | list_to_assoc(PWCnt, NumProposals), 14 | maplist({NumProposals}/[E-P, O-0]>>( 15 | get_assoc(P, NumProposals, 1) -> O = P ; O = E 16 | ), Proposals, UMElves), 17 | 18 | list_to_assoc(UMElves, MElves), 19 | 20 | NT is T + 1, !, 21 | state(NT, TEnd, MElves, OElves). 22 | 23 | addp(X-Y, DX-DY, T, NX-NY) :- NX is X + DX * T, NY is Y + DY * T. 24 | perp(X-Y, NX-X) :- NX is -Y. 25 | 26 | 27 | dir(0, 0-(-1)). 28 | dir(1, 0-1). 29 | dir(2, (-1)-0). 30 | dir(3, 1-0). 31 | dir(X, V) :- X >= 4, T is X mod 4, dir(T, V). 32 | 33 | any_nearby(Elves, EP) :- 34 | between(-1, 1, DX), 35 | between(-1, 1, DY), 36 | abs(DX) + abs(DY) =\= 0, 37 | addp(EP, DX-DY, 1, NP), 38 | get_assoc(NP, Elves, _), !. 39 | 40 | occupied(Elves, P, D) :- 41 | between(-1, 1, T), 42 | addp(P, D, T, NP), 43 | get_assoc(NP, Elves, _). 44 | 45 | propose(T, Elves, EP, EP-OP) :- 46 | any_nearby(Elves, EP), 47 | FT is T+3, 48 | between(T, FT, DT), 49 | dir(DT, D), 50 | addp(EP, D, 1, OP), 51 | perp(D, DP), 52 | %format("~w tries ~w~n", [EP, OP]), 53 | \+ occupied(Elves, OP, DP), !. 54 | 55 | propose(_, _, EP, EP-EP). 56 | 57 | 58 | read_input(Stream, Elves) :- 59 | read_string(Stream, _, Data), 60 | split_string(Data, "\n", "\n", Split), 61 | foldl([Line, Y-ILE, NY-OLE]>>( 62 | string_chars(Line, Chars), 63 | foldl({Y}/[C, X-IE, NX-OE]>>( 64 | (C == '.' -> OE = IE ; 65 | OE = [X-Y|IE] 66 | ), 67 | NX is X + 1 68 | ), Chars, 0-ILE, _-OLE), 69 | NY is Y + 1 70 | ), Split, 0-[], _-UElves), 71 | maplist([P,P-0]>>true, UElves, PUElves), 72 | list_to_assoc(PUElves, Elves). 73 | 74 | part2(Elves, T, OT) :- 75 | NT is T + 1, 76 | state(T, NT, Elves, NElves), 77 | (Elves == NElves -> OT = NT ; 78 | part2(NElves, NT, OT)). 79 | 80 | main :- 81 | %leash(-all), trace, 82 | %open("23.in", read, Stream), 83 | read_input(user_input, IElves), 84 | %close(Stream), 85 | 86 | state(0, 10, IElves, OElves), 87 | aggregate_all( 88 | p(min(X), max(X), min(Y), max(Y), count), 89 | gen_assoc(X-Y, OElves, _), 90 | p(X1, X2, Y1, Y2, NumElves) 91 | ), 92 | format("Part 1: ~d~n", [(X2 - X1 + 1) * (Y2 - Y1 + 1) - NumElves]), 93 | 94 | part2(OElves, 10, P2), 95 | format("Part 2: ~d~n", [P2]). 96 | -------------------------------------------------------------------------------- /aoc2022/23.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 92/90 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | 9 | res = 0 10 | 11 | 12 | 13 | #DIR = ((1, 0), (0, -1), (-1, 0), (0, 1)) 14 | DIR = ((0, -1), (0, 1), (-1, 0), (1, 0)) 15 | elves = set() 16 | 17 | for y, l in enumerate(lines()): 18 | for x, c in enumerate(l): 19 | if c == "#": 20 | elves.add(Point.of(x, y)) 21 | 22 | 23 | for round in range(1<<30): 24 | prop = Counter() 25 | mov = {} 26 | #print(DIR[(round)%4]) 27 | for elf in elves: 28 | if all((elf + Point.of(*d)) not in elves for d in OCTDIR): 29 | continue 30 | 31 | for i in range(4): 32 | np = elf + DIR[(round+i)%4] 33 | 34 | #print(elf, np) 35 | ok = True 36 | if np.y == elf.y: 37 | for dy in range(-1, 2): 38 | ok &= (np + Point.of(0, dy)) not in elves 39 | else: 40 | for dx in range(-1, 2): 41 | ok &= (np + Point.of(dx, 0)) not in elves 42 | 43 | if ok: 44 | prop[np] += 1 45 | mov[elf] = np 46 | break 47 | else: 48 | #print(elf, "no move") 49 | mapp = {elf: "@" for elf in elves} 50 | mapp[elf] = "#" 51 | #print_coords(mapp) 52 | 53 | anym = False 54 | for elf, np in mov.items(): 55 | if prop[np] == 1: 56 | elves.remove(elf) 57 | elves.add(np) 58 | anym = True 59 | 60 | if not anym: 61 | prints(round+1) 62 | exit() 63 | 64 | #print_coords(elves) 65 | 66 | xs, ys = zip(*elves) 67 | min_x, max_x = min(xs), max(xs) 68 | min_y, max_y = min(ys), max(ys) 69 | 70 | 71 | for x in range(min_x, max_x + 1): 72 | for y in range(min_y, max_y+1): 73 | res += Point.of(x, y) not in elves 74 | 75 | 76 | prints(res) 77 | -------------------------------------------------------------------------------- /aoc2022/24.pl: -------------------------------------------------------------------------------- 1 | :- dynamic map/3. 2 | 3 | read_input(Stream, Y) :- 4 | read_line_to_string(Stream, Line), 5 | (Line == end_of_file -> compile_predicates([map/3]) ; 6 | string_chars(Line, Chars), 7 | length(Chars, W), 8 | W1 is W-1, 9 | numlist(0, W1, XS), 10 | maplist({Y}/[C, X]>>assertz(map(X, Y, C)), Chars, XS), 11 | NY is Y + 1, 12 | read_input(Stream, NY) 13 | ). 14 | 15 | dir('>', 1-0). 16 | dir('<', (-1)-0). 17 | dir('v', 0-1). 18 | dir('^', 0-(-1)). 19 | dir(' ', 0-0). 20 | 21 | addp(X-Y, DX-DY, T, NX-NY) :- 22 | NX is X + DX * T, NY is Y + DY * T. 23 | 24 | blizz(T, P, W, H) :- 25 | _-Y = P, 0 < Y, Y < H-1, 26 | dir(C, D), D \= ' ', 27 | addp(P, D, -T, NX-NY), 28 | CX is (NX - 1) mod (W - 2) + 1, 29 | CY is (NY - 1) mod (H - 2) + 1, 30 | map(CX, CY, C). 31 | 32 | 33 | path(StartT, EndT, Start, End, W, H) :- 34 | bfs(StartT, EndT, [Start], End, W, H). 35 | 36 | bfs(T, T, Frontier, End, _, _) :- member(End, Frontier), !. 37 | bfs(StartT, EndT, Frontier, End, W, H) :- 38 | !, Frontier \= [], 39 | NT is StartT+1, 40 | setof(NP, neighbours(Frontier, NT, W, H, NP), NFrontier), 41 | bfs(NT, EndT, NFrontier, End, W, H). 42 | 43 | inbounds(X-Y) :- map(X, Y, C), C \= '#'. 44 | 45 | neighbours(Frontier, NT, W, H, NP) :- 46 | member(P, Frontier), 47 | dir(_, DP), 48 | addp(P, DP, 1, NP), 49 | inbounds(NP), 50 | \+ blizz(NT, NP, W, H). 51 | 52 | 53 | main :- 54 | %open("24.in", read, Stream), 55 | read_input(user_input, 0), 56 | %close(Stream), 57 | 58 | aggregate(p(max(X+1), max(Y+1)), map(X, Y, _), p(W, H)), 59 | H1 is H-1, 60 | map(SX, 0, '.'), 61 | map(EX, H1, '.'), 62 | 63 | path(0, P1, SX-0, EX-H1, W, H), 64 | format("Part 1: ~d~n", [P1]), 65 | 66 | path(P1, ToStart, EX-H1, SX-0, W, H), 67 | path(ToStart, P2, SX-0, EX-H1, W, H), 68 | format("Part 2: ~d~n", [P2]). 69 | -------------------------------------------------------------------------------- /aoc2022/24.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 56/41 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | 9 | res = 0 10 | 11 | map = [] 12 | BP = dict(zip(">v<^", [Point.of(*d) for d in DIR])) 13 | 14 | for l in lines(): 15 | map.append(l) 16 | 17 | for x, c in enumerate(map[0]): 18 | if c == ".": 19 | start = (x, 0) 20 | break 21 | 22 | H = len(map) 23 | W = len(map[0]) 24 | 25 | for x, c in enumerate(map[-1]): 26 | if c == ".": 27 | end = (x, H-1) 28 | break 29 | 30 | 31 | def blizz(time, x, y): 32 | # col 33 | ch = H - 2 + (map[0][x] == ".") + (map[-1][x] == ".") 34 | for by in range(H): 35 | if map[by][x] == "v": 36 | dist = y - by 37 | if dist % ch == time % ch: 38 | return True 39 | if map[by][x] == "^": 40 | dist = by - y 41 | if dist % ch == time % ch: 42 | return True 43 | 44 | ch = W - 2 45 | for bx in range(W): 46 | if map[y][bx] == ">": 47 | dist = x - bx 48 | if dist % ch == time % ch: 49 | return True 50 | if map[y][bx] == "<": 51 | dist = bx - x 52 | if dist % ch == time % ch: 53 | return True 54 | 55 | return False 56 | 57 | 58 | Q = [(0, start, 0)] 59 | V = {(0, start, 0)} 60 | for i, p, state in Q: 61 | if p == end and state == 2: 62 | prints(i) 63 | exit() 64 | 65 | """ 66 | mapp = {(x, y): c for y, l in enumerate(map) for x, c in enumerate(l)} 67 | mapp[p] = "E" 68 | print(i) 69 | print_coords(mapp) 70 | print() 71 | """ 72 | 73 | x, y = p 74 | for dx, dy in DIR + ((0, 0),): 75 | nx = x + dx 76 | ny = y + dy 77 | 78 | if 0 <= nx < W and 0 <= ny < H: 79 | nst = state 80 | if state == 0 and (nx, ny) == end: 81 | nst = 1 82 | elif state == 1 and (nx, ny) == start: 83 | nst = 2 84 | 85 | nt = (i+1, (nx, ny), nst) 86 | if map[ny][nx] != "#" and nt not in V and not blizz(i+1, nx, ny): 87 | V.add(nt) 88 | Q.append(nt) 89 | 90 | prints(res) 91 | -------------------------------------------------------------------------------- /aoc2022/24_post.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | from util import * 3 | 4 | if len(sys.argv) == 1: 5 | sys.stdin = open(__file__.replace("_post.py", ".in")) 6 | 7 | 8 | map = lines() 9 | H = len(map) 10 | W = len(map[0]) 11 | 12 | BP = dict(zip(">v<^ ", [Point.of(*d) for d in DIR] + [Point.of(0, 0)])) 13 | 14 | start = Point.of(map[0].index("."), 0) 15 | end = Point.of(map[-1].index("."), H - 1) 16 | 17 | # There are no vertical blizzards in the start or end column 18 | assert not set("^v") & {map[y][p.x] for y in range(H) for p in (start, end)} 19 | 20 | period = math.lcm(H - 2, W - 2) 21 | p11 = Point.of(1, 1) 22 | 23 | 24 | def blizz(time, p): 25 | if p.y in (0, H - 1): 26 | return False 27 | for c, d in BP.items(): 28 | np = p - d * time - p11 29 | if map[np.y % (H - 2) + 1][np.x % (W - 2) + 1] == c: 30 | return True 31 | 32 | return False 33 | 34 | def bfs(st: int, start: Point[int], end: Point[int]): 35 | Q = [(st, start)] 36 | V = {(st % period, start)} 37 | for i, p in Q: 38 | if p == end: 39 | return i 40 | 41 | j = i + 1 42 | for d in BP.values(): 43 | np = p + d 44 | 45 | if ( 46 | 0 <= np.y < H 47 | and map[np.y][np.x] != "#" 48 | and (j % period, np) not in V 49 | and not blizz(j, np) 50 | ): 51 | V.add((j % period, np)) 52 | Q.append((j, np)) 53 | else: 54 | assert False 55 | 56 | 57 | p1 = bfs(0, start, end) 58 | print(f"Part 1: {p1}") 59 | 60 | p2 = bfs(bfs(p1, end, start), start, end) 61 | print(f"Part 2: {p2}") 62 | -------------------------------------------------------------------------------- /aoc2022/25.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | 3 | dig('=', -2). 4 | dig('-', -1). 5 | dig('0', 0). 6 | dig('1', 1). 7 | dig('2', 2). 8 | 9 | conv([], 0). 10 | conv([C | Tail], O) :- 11 | length(Tail, I), 12 | DV in -2..2, 13 | O #= V + DV * 5^I, 14 | conv(Tail, V), 15 | dig(C, DV). 16 | 17 | main :- 18 | read_string(user_input, _, Data), 19 | split_string(Data, "\n", "\n", Split), 20 | maplist(string_chars, Split, CSplit), 21 | maplist(conv, CSplit, Converted), 22 | sumlist(Converted, Sum), 23 | conv(Digs, Sum), 24 | format("Part 1: ~s~n", [Digs]). 25 | -------------------------------------------------------------------------------- /aoc2022/25.py: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env pypy3 2 | # 118 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace('py', 'in')) 7 | 8 | 9 | res = 0 10 | S = "=-012" 11 | 12 | for l in lines(): 13 | v = 0 14 | for p, c in enumerate(l[::-1]): 15 | i = S.index(c) - 2 16 | v += 5**p * i 17 | print(l, v) 18 | res += v 19 | 20 | print(res) 21 | 22 | import z3 23 | 24 | s = z3.Solver() 25 | 26 | vars = [] 27 | z3s = 0 28 | for i in range(30): 29 | x = z3.Int(f"d_{i}") 30 | s.add(-2 <= x, x <= 2) 31 | z3s += 5**i * x 32 | vars.append(x) 33 | 34 | s.add(z3s == res) 35 | 36 | assert s.check() == z3.sat 37 | 38 | print("".join(S[s.model()[v].as_long()+2] for v in vars)[::-1]) 39 | -------------------------------------------------------------------------------- /aoc2022/get_input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Fetch AoC input as files. 5 | By Asger Hautop Drewsen: https://github.com/Tyilo 6 | """ 7 | 8 | import sys 9 | from pathlib import Path 10 | from typing import Optional 11 | 12 | import requests 13 | 14 | YEAR = 2022 15 | URL_PREFIX = f"https://adventofcode.com/{YEAR}" 16 | 17 | 18 | def validate_session(session): 19 | test_url = f"{URL_PREFIX}/settings" 20 | r = session.get(test_url) 21 | return r.status_code == 200 and r.url == test_url 22 | 23 | 24 | session_cookie: Optional[str] 25 | try: 26 | with open(".session", "r") as f: 27 | session_cookie = f.read().strip() 28 | except FileNotFoundError: 29 | session_cookie = None 30 | 31 | while True: 32 | if not session_cookie: 33 | session_cookie = input("Session cookie value: ").strip() 34 | with open(".session", "w") as f: 35 | f.write(session_cookie) 36 | 37 | session = requests.Session() 38 | session.headers["User-Agent"] = "get_input.py @ github.com/BarrensZeppelin/adventofcode2022" 39 | session.cookies.set("session", session_cookie, domain=".adventofcode.com", path="/") 40 | if validate_session(session): 41 | break 42 | 43 | print("That session cookie doesn't seem to work. Try again.") 44 | session_cookie = None 45 | 46 | 47 | for i in range(1, 26): 48 | path = Path(f"{i:02}.in") 49 | 50 | if path.exists(): 51 | continue 52 | 53 | r = session.get(f"{URL_PREFIX}/day/{i}/input") 54 | if r.ok: 55 | with path.open("wb") as fb: 56 | fb.write(r.content) 57 | print(f"Downloaded {path.name}") 58 | else: 59 | if r.status_code == 404: 60 | print(f"Day {i} not released yet") 61 | break 62 | else: 63 | sys.exit(f"Got unknown status code: {r.status_code}\n{r.text.strip()}") 64 | -------------------------------------------------------------------------------- /aoc2023/01.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_string(user_input, _, Input), split_string(Input, "\n", "\n", L), 3 | maplist(score, [part1, part2], [L, L], Ans), 4 | format("Part 1: ~d~nPart 2: ~d~n", Ans). 5 | 6 | score(Part, Lines, Res) :- 7 | aggregate_all(sum(D1 * 10 + D2), ( 8 | member(L, Lines), 9 | aggregate_all(r(min(I, D), max(I, D)), ( 10 | valid_number(Part, Sub, D), sub_string(L, I, _, _, Sub) 11 | ), r(min(_, D1), max(_, D2))) 12 | ), Res). 13 | 14 | valid_number(_, Sub, I) :- sub_string("_123456789", I, 1, _, Sub). 15 | valid_number(part2, Sub, I) :- 16 | nth1(I, ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine"], Sub). 17 | -------------------------------------------------------------------------------- /aoc2023/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 1717/57 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | res = 0 10 | 11 | D = dict(zip("one, two, three, four, five, six, seven, eight, nine".split(", "), range(1, 10))) 12 | 13 | for l in lines(): 14 | digs = [] 15 | for i, c in enumerate(l): 16 | if c.isdigit(): 17 | digs.append(int(c)) 18 | else: 19 | for k, v in D.items(): 20 | if l[i:].startswith(k): 21 | digs.append(v) 22 | break 23 | # ls = list(map(int, (c for c in l if c.isdigit()))) 24 | # if not ls: continue 25 | # print(ls) 26 | # assert ls[0] > 0 and ls[-1] > 0 27 | res += digs[0] * 10 + digs[-1] 28 | 29 | 30 | prints(res) 31 | -------------------------------------------------------------------------------- /aoc2023/01_2.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_input(L), 3 | foldl([S, Acc, Res] >> (solve(S, R, part1), Res is Acc + R), L, 0, Part1), 4 | format("Part 1: ~d~n", [Part1]), 5 | foldl([S, Acc, Res] >> (solve(S, R, part2), Res is Acc + R), L, 0, Part2), 6 | format("Part 2: ~d~n", [Part2]). 7 | 8 | solve(S, R, Part) :- 9 | first_digit(S, D1, Part), 10 | last_digit(S, D2, Part), 11 | R is D1 * 10 + D2. 12 | 13 | first_digit(S, D, Part) :- 14 | string_concat(_, R, S), 15 | conv_digit(SDigit, D, Part), 16 | string_concat(SDigit, _, R). 17 | 18 | last_digit(S, D, Part) :- 19 | string_length(S, Len), 20 | between(1, Len, SLen), 21 | string_concat(_, R, S), 22 | string_length(R, SLen), 23 | conv_digit(SDigit, D, Part), 24 | string_concat(SDigit, _, R). 25 | 26 | conv_digit("one", 1, part2). 27 | conv_digit("two", 2, part2). 28 | conv_digit("three", 3, part2). 29 | conv_digit("four", 4, part2). 30 | conv_digit("five", 5, part2). 31 | conv_digit("six", 6, part2). 32 | conv_digit("seven", 7, part2). 33 | conv_digit("eight", 8, part2). 34 | conv_digit("nine", 9, part2). 35 | conv_digit(S, D, _) :- 36 | between(0'0, 0'9, C), 37 | string_codes(S, [C]), 38 | D is C - 0'0. 39 | 40 | read_input(L) :- 41 | read_line_to_string(user_input, Line), 42 | (Line == end_of_file -> L = []; 43 | read_input(Tail), 44 | L = [Line | Tail] 45 | ). 46 | -------------------------------------------------------------------------------- /aoc2023/02.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_string(user_input, _, Input), 3 | split_string(Input, "\n", "\n", Lines), 4 | maplist([L, G]>>( 5 | re_foldl([re_match{0:_, count:Count, color:Color}, DIn, DOut]>>( 6 | get_dict(Color, DIn, OldCount), 7 | (OldCount >= Count -> DOut = DIn; put_dict(Color, DIn, Count, DOut)) 8 | ), "(?\\d+) (?r|g|b)", L, cnt{r:0, g:0, b:0}, G, []) 9 | ), Lines, Games), 10 | 11 | foldl([G, Acc-Id, Res-NId]>>( 12 | NId is Id + 1, 13 | (maxcount(Color, Max), get_dict(Color, G, Count), Count > Max -> 14 | Res is Acc; Res is Acc + NId) 15 | ), Games, 0-0, Part1-_), 16 | format("Part 1: ~d~n", [Part1]), 17 | 18 | foldl([G, Acc, Res]>>( 19 | findall(V, get_dict(_, G, V), Vs), 20 | foldl([V, A, R]>>(R is A * V), Vs, 1, Power), 21 | Res is Acc + Power 22 | ), Games, 0, Part2), 23 | format("Part 2: ~d~n", [Part2]). 24 | 25 | maxcount(r, 12). 26 | maxcount(g, 13). 27 | maxcount(b, 14). 28 | -------------------------------------------------------------------------------- /aoc2023/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | res = 0 9 | CNT = dict(zip("red green blue".split(), (12, 13, 14))) 10 | 11 | for l in lines(): 12 | # l = l.replace(";", ",") 13 | _, rest = l.split("ame ") 14 | id, rest = rest.split(": ", maxsplit=1) 15 | maxx = Counter() 16 | for reveal in rest.split("; "): 17 | print(reveal) 18 | used = Counter() 19 | for blk in reveal.split(", "): 20 | print(blk) 21 | cnt, type = blk.split(" ") 22 | maxx[type] = max(maxx[type], int(cnt)) 23 | used[type] += int(cnt) 24 | # print(cnt, type) 25 | # if any(v > CNT[t] for t, v in used.items()): 26 | # break 27 | # else: 28 | # res += int(id) 29 | 30 | res += math.prod(maxx.values()) 31 | 32 | prints(res) 33 | -------------------------------------------------------------------------------- /aoc2023/03.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | :- dynamic grid/2, symb_neighbours/2. 3 | 4 | neighbours(Start-Len-Y, SX-SY, C) :- 5 | abs(Y - SY) #=< 1, SX #>= Start, SX #=< Start+Len+1, 6 | label([SX, SY]), grid(SX-SY, C). 7 | 8 | main :- 9 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 10 | foldl([L, Y, NY]>>( 11 | NY is Y + 1, 12 | forall((string_code(X, L, C), code_type(C, punct), C \= 0'.), assertz(grid(X-Y, C))) 13 | ), Lines, 0, _), 14 | compile_predicates([grid/2]), 15 | foldl([L, Y, NY]>>( 16 | NY is Y + 1, 17 | re_foldl([re_match{0:Start-Len}, _, _]>>( 18 | sub_string(L, Start, Len, _, Sub), number_string(N, Sub), 19 | forall(neighbours(Start-Len-Y, SX-SY, _), assertz(symb_neighbours(SX-SY, Y-Start-N))) 20 | ), "\\d+", L, _, _, [capture_type(range)]) 21 | ), Lines, 0, _), 22 | aggregate(sum(N), Y-Start, P^symb_neighbours(P, Y-Start-N), Part1), 23 | format("Part 1: ~d~n", [Part1]), 24 | 25 | aggregate_all(sum(Prod), (grid(X-Y, 0'*), findall(N, symb_neighbours(X-Y, _-_-N), [A, B]), Prod is A*B), Part2), 26 | format("Part 2: ~d~n", [Part2]). 27 | -------------------------------------------------------------------------------- /aoc2023/03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 9/61 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | res = 0 10 | 11 | L = lines() 12 | 13 | NUMS = {} 14 | IDX = 0 15 | 16 | for y, l in enumerate(L): 17 | W = len(l) 18 | i = 0 19 | num = 0 20 | def fix(): 21 | global res, IDX 22 | IDX += 1 23 | j = i-1 24 | added = False 25 | while j >= 0: 26 | if not l[j].isdigit(): 27 | break 28 | NUMS[(j, y)] = (num, IDX) 29 | if not added: 30 | for dx, dy in OCTDIR: 31 | x, ny = j + dx, y + dy 32 | if 0 <= x < W and 0 <= ny < len(L): 33 | c = L[ny][x] 34 | if not c.isdigit() and c != ".": 35 | res += num 36 | added = True 37 | j -= 1 38 | while i < W: 39 | if l[i].isdigit(): 40 | num = num * 10 + int(l[i]) 41 | else: 42 | if num: 43 | fix() 44 | num = 0 45 | i += 1 46 | if num: 47 | fix() 48 | 49 | res = 0 50 | print(NUMS) 51 | 52 | for y, l in enumerate(L): 53 | for x, c in enumerate(l): 54 | if c == "*": 55 | nums = set() 56 | for dx, dy in OCTDIR: 57 | nx, ny = x + dx, y + dy 58 | v = NUMS.get((nx, ny)) 59 | if v is not None: 60 | nums.add(v) 61 | 62 | if len(nums) == 2: 63 | a, _ = nums.pop() 64 | b, _ = nums.pop() 65 | res += a * b 66 | 67 | prints(res) 68 | -------------------------------------------------------------------------------- /aoc2023/04.pl: -------------------------------------------------------------------------------- 1 | :- dynamic card/2. 2 | :- table count(_,-). 3 | 4 | get_numbers(L, Ns) :- re_foldl([re_match{0:S}, [S|T], T]>>true, "\\d+", L, Ns, [], [capture_type(term)]). 5 | 6 | main :- 7 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 8 | forall(member(L, Lines), ( 9 | split_string(L, "|", " ", Parts), 10 | maplist(get_numbers, Parts, [[ID | Win], Mine]), 11 | aggregate_all(count, (member(N, Mine), memberchk(N, Win)), R), 12 | assertz(card(ID, R)) 13 | )), 14 | 15 | aggregate_all(sum(2^(R-1)), (card(_, R), R > 0), Part1), 16 | aggregate_all(sum(C), count(ID, C), Part2), 17 | format("Part 1: ~d~nPart 2: ~d~n", [Part1, Part2]). 18 | 19 | count(ID, Res) :- 20 | card(ID, R), Lo is ID + 1, Hi is ID + R, 21 | aggregate_all(sum(C), (C is 1; between(Lo, Hi, N), count(N, C)), Res). 22 | -------------------------------------------------------------------------------- /aoc2023/04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 4/702 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | res = 0 10 | 11 | ls = lines() 12 | N = len(ls) 13 | 14 | cards = [] 15 | for id, l in enumerate(ls, start=1): 16 | _, rest = l.split(": ") 17 | winning, mine = rest.split(" | ") 18 | winning = set(ints(winning)) 19 | r = 0 20 | for x in ints(mine): 21 | if x in winning: 22 | if r == 0: 23 | r = 1 24 | else: 25 | r += 1 26 | # r *= 2 27 | cards.append((id, r)) 28 | 29 | copies = [Counter() for _ in range(N)] 30 | for i in range(N): 31 | copies[i][cards[i][0]] += 1 32 | 33 | 34 | for i, (id, r) in enumerate(cards): 35 | if not r: 36 | continue 37 | 38 | print(id, copies[i]) 39 | if r: 40 | win = 0 41 | for _, cnt in copies[i].items(): 42 | win += cnt 43 | 44 | 45 | for j in range(id+1, id+r+1): 46 | print(id, r, j) 47 | # print(j, N, win) 48 | copies[j-1][j] += win 49 | 50 | 51 | 52 | prints(sum(d for c in copies for d in c.values())) 53 | 54 | # prints(res) 55 | -------------------------------------------------------------------------------- /aoc2023/04_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("_post.py", ".in")) 7 | 8 | decrement = Counter[int]() 9 | p1 = p2 = prefix_sum = 0 10 | 11 | for id, l in enumerate(lines(), start=1): 12 | prefix_sum -= decrement[id] 13 | copies = prefix_sum+1 14 | p2 += copies 15 | 16 | winning, mine = map(ints, l.split(": ")[1].split(" | ")) 17 | winning = set(winning) 18 | won = sum(x in winning for x in mine) 19 | prefix_sum += copies 20 | decrement[id+won+1] += copies 21 | 22 | if won: p1 += 2**(won-1) 23 | 24 | print(f"Part 1: {p1}\nPart 2: {p2}") 25 | -------------------------------------------------------------------------------- /aoc2023/05.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)), use_module(library(prolog_stack)). 2 | 3 | get_numbers(L, Ns) :- re_foldl([re_match{0:S}, [S|T], T]>>true, "\\d+", L, Ns, [], [capture_type(term)]). 4 | 5 | main :- 6 | read_string(user_input, _, S), re_split("\n\n", S, [SeedsS | Blocks]), 7 | get_numbers(SeedsS, Seeds), 8 | convlist([Block, Map]>>( 9 | split_string(Block, "\n", "\n", [_ | Lines]), Lines \= [], 10 | maplist([L, [SourceStart, SourceEnd, Shift]]>>( 11 | get_numbers(L, [DestStart, SourceStart, Length]), 12 | SourceEnd is SourceStart + Length, 13 | Shift is DestStart - SourceStart 14 | ), Lines, Map) 15 | ), Blocks, Maps), 16 | !, 17 | solve(Maps, Seeds, Part1), 18 | format("Part 1: ~d\n", [Part1]), 19 | 20 | range_pairs(Seeds, SeedRanges), 21 | solve(Maps, SeedRanges, Part2), 22 | format("Part 2: ~d\n", [Part2]). 23 | 24 | constrain(Ranges, In, Out) :- 25 | foldl({In, Out}/[[SourceStart, SourceEnd, Shift], AIn, AOut]>>( 26 | End is SourceEnd-1, 27 | (In in SourceStart..End) #<==> B, 28 | AOut #= (AIn * (1 - B)), 29 | B #==> (Out #= In + Shift) 30 | ), Ranges, 1, AllFail), 31 | AllFail #==> (Out #= In). 32 | 33 | solve(Maps, Domains, Out) :- 34 | domain_union(Initial, Domains), 35 | scanl(constrain, Maps, Initial, Vars), 36 | Vars ins 0..10000000000, 37 | last(Vars, Out), 38 | once(labeling([min(Out), bisect], Vars)). 39 | 40 | domain_union(V, [D|Ds]) :- 41 | foldl([Dom, DIn, DOut]>>(DOut = DIn \/ Dom), Ds, D, DUnion), 42 | V in DUnion. 43 | 44 | range_pairs([], []). 45 | range_pairs([Seed, Length | Tail], [Seed..End | Pairs]) :- 46 | End is Seed + Length - 1, range_pairs(Tail, Pairs). 47 | -------------------------------------------------------------------------------- /aoc2023/05_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("_post.py", ".in")) 7 | 8 | seeds, *blocks = sys.stdin.read().split("\n\n") 9 | seeds = list(ints(seeds)) 10 | 11 | maps = [] 12 | for block in blocks: 13 | ls = lines(block)[1:] 14 | maps.append([tuple(ints(l)) for l in ls]) 15 | 16 | for i, intervals in enumerate(( 17 | [(seed, seed+1) for seed in seeds], 18 | [(seed, seed+len) for seed, len in tile(seeds, 2)], 19 | ), start=1): 20 | for mapping_ranges in maps: 21 | next_intervals = [] 22 | 23 | for l, r in intervals: 24 | for dest_start, source_start, length in mapping_ranges: 25 | if (cut := cut_interval(l, r, source_start, source_start + length)): 26 | shift = dest_start - source_start 27 | cl, (ml, mr), cr = cut 28 | next_intervals.append((ml + shift, mr + shift)) 29 | intervals.extend(i for i in (cl, cr) if i) 30 | break 31 | else: 32 | next_intervals.append((l, r)) 33 | 34 | intervals = merge_intervals(next_intervals) 35 | 36 | print(f"Part {i}: {min(l for l, _ in intervals)}") 37 | -------------------------------------------------------------------------------- /aoc2023/05_z3.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import z4 as z3 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("_z3.py", ".in")) 8 | 9 | seeds, *blocks = sys.stdin.read().split("\n\n") 10 | seeds = list(ints(seeds)) 11 | 12 | maps = [] 13 | for block in blocks: 14 | ls = lines(block)[1:] 15 | maps.append([tuple(ints(l)) for l in ls]) 16 | 17 | for i, intervals in enumerate(( 18 | [(seed, seed+1) for seed in seeds], 19 | [(seed, seed+len) for seed, len in tile(seeds, 2)], 20 | ), start=1): 21 | solver = z3.Optimize() 22 | prev = z3.Int("seed_0") 23 | solver.add(z3.Or(*[z3.And(l <= prev, prev < r) for l, r in intervals])) 24 | for j, mapping_ranges in enumerate(maps, start=1): 25 | next = z3.Int(f"seed_{j}") 26 | els = next == prev 27 | 28 | for dest_start, source_start, length in mapping_ranges: 29 | shift = dest_start - source_start 30 | els = z3.If( 31 | z3.And(source_start <= prev, prev < source_start + length), 32 | next == prev + shift, 33 | els 34 | ) 35 | 36 | solver.add(els) 37 | prev = next 38 | 39 | solver.minimize(prev) 40 | assert solver.check() == z3.sat 41 | print(f"Part {i}: {solver.model().eval(prev)}") 42 | -------------------------------------------------------------------------------- /aoc2023/06.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | 3 | main :- 4 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 5 | maplist({Lines}/[Parse, Res]>>(maplist(Parse, Lines, Input), solve(Input, Res)), [ 6 | [Line, Ns]>>re_foldl([_{0:N}, [N|T], T]>>true, "\\d+"/t, Line, Ns, [], []), 7 | [Line, [V]]>>re_foldl([_{0:N}, A, B]>>(B is A*10+N), "\\d"/t, Line, 0, V, []) 8 | ], Ans), 9 | format("Part 1: ~d~nPart 2: ~d~n", Ans). 10 | 11 | solve([[], []], 1). 12 | solve([[T|Ts], [D|Ds]], Res) :- 13 | P #< T, P * (T - P) #> D, fd_size(P, W), 14 | solve([Ts, Ds], Res1), 15 | Res is Res1 * W. 16 | -------------------------------------------------------------------------------- /aoc2023/06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | times = list(ints(input())) 9 | distances = list(ints(input())) 10 | times = [int("".join(map(str, times)))] 11 | distances = [int("".join(map(str, distances)))] 12 | N = len(times) 13 | 14 | res = 1 15 | 16 | for time, dist in zip(times, distances): 17 | d = 0 18 | won = 0 19 | for hold in range(time+1): 20 | if (time - hold) * hold > dist: 21 | won += 1 22 | # print(hold, move, dist) 23 | d += hold+1 24 | print(won) 25 | res *= won 26 | 27 | 28 | 29 | prints(res) 30 | -------------------------------------------------------------------------------- /aoc2023/06_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("_post.py", ".in")) 7 | 8 | T, D = (list(ints(input())) for _ in range(2)) 9 | for part in range(1, 3): 10 | if part == 2: 11 | T = [int("".join(map(str, T)))] 12 | D = [int("".join(map(str, D)))] 13 | 14 | res = 1 15 | for time, dist in zip(T, D): 16 | hi = time // 2 17 | assert hi * (time-hi) > dist 18 | p = binary_search(lambda x: x * (time-x) > dist, 1, hi) 19 | res *= time - 2*p + 1 20 | 21 | print(f"Part {part}: {res}") 22 | -------------------------------------------------------------------------------- /aoc2023/07.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 3 | maplist([Line, Codes-Bid]>>( 4 | split_string(Line, " ", "", [CodesS, BidS]), 5 | string_codes(CodesS, Codes), number_string(Bid, BidS) 6 | ), Lines, Cards), !, 7 | 8 | solve(Cards, part1, Part1), 9 | solve(Cards, part2, Part2), 10 | format("Part 1: ~d~nPart 2: ~d~n", [Part1, Part2]). 11 | 12 | strength(part2, 0'J, 0) :- !. 13 | strength(_, Card, Strength) :- string_code(Strength, "23456789TJQKA", Card). 14 | 15 | type(part2, [0-Jokers | Tail], Type) :- 16 | Jokers < 5 -> 17 | aggregate(max(Cnt, S), member(S-Cnt, Tail), max(Cnt, S)), 18 | selectchk(S-Cnt, Tail, S-NCnt, Clumped), 19 | NCnt is Cnt + Jokers, 20 | type(part1, Clumped, Type). 21 | 22 | type(_, Clumped, Type) :- aggregate_all(sum(V*V), member(_-V, Clumped), Type). 23 | 24 | solve(Cards, Part, Res) :- 25 | maplist({Part}/[Cs-Bid, [Typ | Strengths]-Bid]>>( 26 | maplist(strength(Part), Cs, Strengths), 27 | msort(Strengths, Sorted), clumped(Sorted, Clumped), 28 | type(Part, Clumped, Typ) 29 | ), Cards, Transformed), 30 | 31 | sort(Transformed, Sorted), 32 | foldl([_-Bid, Id-Acc, NId-R]>>( 33 | NId is Id + 1, 34 | R is Acc + Bid * NId 35 | ), Sorted, 0-0, _-Res). 36 | 37 | 38 | -------------------------------------------------------------------------------- /aoc2023/07.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 49/85 3 | 4 | from functools import cmp_to_key 5 | from util import * 6 | 7 | if len(sys.argv) == 1: 8 | sys.stdin = open(__file__.replace("py", "in")) 9 | 10 | res = 0 11 | 12 | cards = [l.split(" ") for l in lines()] 13 | 14 | from string import digits 15 | STRENGTH = digits[2:] + "AKQJT"[::-1] 16 | STRENGTH = "J" + digits[2:] + "AKQT"[::-1] 17 | print(STRENGTH) 18 | 19 | 20 | @lru_cache(maxsize=None) 21 | def typ(card): 22 | C = Counter(card) 23 | if len(C) == 1: 24 | return 10 25 | elif len(C) == 2: 26 | return 9 if max(C.values()) == 4 else 8 27 | 28 | m = max(C.values()) 29 | if m == 3: 30 | return 7 31 | 32 | if sum(1 for cnt in C.values() if cnt == 2) == 2: 33 | return 6 34 | 35 | if m == 2: 36 | return 5 37 | return 4 38 | 39 | 40 | def cmp(a, b): 41 | a, ca, _ = a 42 | b, cb, _ = b 43 | ta, tb = map(typ, (a, b)) 44 | if ta != tb: 45 | return ta - tb 46 | 47 | for c1, c2 in zip(ca, cb): 48 | if c1 != c2: 49 | return STRENGTH.index(c1) - STRENGTH.index(c2) 50 | return 0 51 | assert False 52 | 53 | def trans(card): 54 | card, bid = card 55 | 56 | poss = [] 57 | for c in card: 58 | if c == "J": 59 | poss.append(list(STRENGTH)) 60 | else: 61 | poss.append([c]) 62 | 63 | return max((("".join(x), card, bid) for x in product(*poss)), key=cmp_to_key(cmp)) 64 | 65 | return card, bid 66 | 67 | cards = list(map(trans, cards)) 68 | 69 | cards.sort(key=cmp_to_key(cmp)) 70 | # print(cards) 71 | 72 | for i, (_, _, bid) in enumerate(cards, start=1): 73 | res += i * int(bid) 74 | 75 | prints(res) 76 | -------------------------------------------------------------------------------- /aoc2023/08.pl: -------------------------------------------------------------------------------- 1 | :- dynamic adj/3. 2 | 3 | main :- 4 | read_line_to_codes(user_input, Instructions), 5 | read_string(user_input, _, S), 6 | re_foldl([re_match{0:_, 1:From, 2:L, 3:R}, _, _]>>( 7 | assertz(adj(From, 0'L, L)), assertz(adj(From, 0'R, R)) 8 | ), "(\\w+) = \\((\\w+), (\\w+)\\)", S, _, _, []), 9 | compile_predicates([adj/3]), 10 | append(Instructions, Tail, Queue), 11 | path_length(Queue, Tail, "AAA", "ZZZ", Part1), 12 | format("Part 1: ~d~n", [Part1]), 13 | 14 | findall(N, (adj(Cur, 0'L, _), string_code(3, Cur, 0'A), path_length(Queue, Tail, Cur, "Z", N)), Ls), 15 | foldl([X, Y, Z]>>(Z is lcm(X, Y)), Ls, 1, Part2), 16 | format("Part 2: ~d~n", [Part2]). 17 | 18 | path_length(_, _, Cur, End, 0) :- sub_string(Cur, _, _, 0, End), !. 19 | path_length([I|Queue], [I|Tail], Cur, End, N) :- 20 | adj(Cur, I, Next), 21 | path_length(Queue, Tail, Next, End, NX), 22 | N is NX + 1. 23 | -------------------------------------------------------------------------------- /aoc2023/08.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 53/4 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | ins, adjs = sys.stdin.read().split("\n\n") 10 | 11 | ADJ = {} 12 | for l in lines(adjs): 13 | k, r = l.split(" = ") 14 | l, r = r.split(", ") 15 | ADJ[k] = (l[1:], r[:-1]) 16 | print(k, ADJ[k]) 17 | 18 | 19 | res = 0 20 | 21 | def path(start): 22 | r = 0 23 | cur = start 24 | for action in cycle(ins): 25 | if cur.endswith("Z"): 26 | break 27 | 28 | r += 1 29 | cur = ADJ[cur][action == "R"] 30 | return r 31 | 32 | ans = [path(start) for start in ADJ if start.endswith("A")] 33 | prints(math.lcm(*ans)) 34 | # 35 | # cur = "AAA" 36 | # for action in cycle(ins): 37 | # if cur == "ZZZ": 38 | # break 39 | # 40 | # res += 1 41 | # cur = ADJ[cur][action == "R"] 42 | # 43 | # prints(res) 44 | -------------------------------------------------------------------------------- /aoc2023/09.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 3 | maplist([Line, Nums]>>(split_string(Line, " ", "", Split), maplist(number_string, Nums, Split)), Lines, Input), 4 | 5 | aggregate_all(r(sum(Next), sum(Prev)), ( 6 | member(Nums, Input), solve(Nums, Next), reverse(Nums, Rev), solve(Rev, Prev) 7 | ), r(Part1, Part2)), 8 | format("Part 1: ~d~nPart 2: ~d~n", [Part1, Part2]). 9 | 10 | differences([A, B | Tail], [Diff | Diffs]) :- Diff is B - A, differences([B | Tail], Diffs). 11 | differences([_], []). 12 | 13 | solve([0], 0). 14 | solve(Nums, Next) :- 15 | differences(Nums, Diffs), last(Nums, Last), 16 | solve(Diffs, N2), Next is Last + N2. 17 | 18 | -------------------------------------------------------------------------------- /aoc2023/09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 116/75 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | res = 0 10 | 11 | def f(ls): 12 | diffs = [ls[i] - ls[i-1] for i in range(1, len(ls))] 13 | if set(diffs) == {0}: 14 | return 0 15 | 16 | return diffs[0] - f(diffs) 17 | 18 | 19 | for l in lines(): 20 | ls = list(ints(l)) 21 | v = f(ls) 22 | print(v) 23 | res += ls[0] - v 24 | 25 | prints(res) 26 | -------------------------------------------------------------------------------- /aoc2023/10.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | :- dynamic grid/2. 3 | 4 | main :- 5 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 6 | forall((nth1(Y, Lines, Line), string_code(X, Line, C)), assertz(grid(X-Y, C))), 7 | compile_predicates([grid/2]), !, 8 | 9 | grid(Start, 0'S), 10 | loop(Start, [], Loop), 11 | length(Loop, LoopLen), 12 | Part1 is LoopLen // 2, 13 | format("Part 1: ~d~n", [Part1]), 14 | 15 | aggregate_all(sum(X1 * Y2 - X2 * Y1), nextto(X1-Y1, X2-Y2, [Start|Loop]), SArea), 16 | Part2 is abs(SArea) // 2 - Part1 + 1, 17 | format("Part 1: ~d~n", [Part2]). 18 | 19 | conn(0'-, -1, 0). conn(0'-, 1, 0). 20 | conn(0'|, 0, -1). conn(0'|, 0, 1). 21 | conn(0'J, 0, -1). conn(0'J, -1, 0). 22 | conn(0'L, 0, -1). conn(0'L, 1, 0). 23 | conn(0'F, 0, 1). conn(0'F, 1, 0). 24 | conn(0'7, 0, 1). conn(0'7, -1, 0). 25 | conn(0'S, DX, DY) :- 26 | grid(X-Y, 0'S), 27 | DX #= NX-X, DY #= NY-Y, 28 | abs(DX) + abs(DY) #= 1, 29 | label([NX, NY]), 30 | adj(NX-NY, X-Y). 31 | 32 | adj(P, NX-NY) :- 33 | P = X-Y, 34 | grid(P, C), conn(C, DX, DY), 35 | NX is X+DX, NY is Y+DY. 36 | 37 | loop(Cur, Loop, Loop) :- grid(Cur, 0'S), Loop \= [], !. 38 | loop(Cur, Stack, NLoop) :- 39 | (Stack = [] -> true; Stack = [Prev|_], dif(Prev, Next)), 40 | adj(Cur, Next), loop(Next, [Cur|Stack], NLoop). 41 | -------------------------------------------------------------------------------- /aoc2023/10_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("_post.py", ".in")) 7 | 8 | 9 | ADJ = { 10 | "|": [Point.of(0, 1), Point.of(0, -1)], 11 | "-": [Point.of(1, 0), Point.of(-1, 0)], 12 | "F": [Point.of(0, 1), Point.of(1, 0)], 13 | "J": [Point.of(0, -1), Point.of(-1, 0)], 14 | "L": [Point.of(1, 0), Point.of(0, -1)], 15 | "7": [Point.of(0, 1), Point.of(-1, 0)] 16 | } 17 | 18 | G = [list(l.replace("O", ".").replace("I", ".")) for l in lines()] 19 | for y, l in enumerate(G): 20 | for x, c in enumerate(l): 21 | if c == "S": 22 | start = Point.of(x, y) 23 | break 24 | 25 | start_conn = [] 26 | for diff in map(Point, DIR): 27 | n = start + diff 28 | if -diff in ADJ.get(G[n.y][n.x], []): 29 | start_conn.append(diff) 30 | 31 | start_sym = next(k for k, aa in ADJ.items() if set(start_conn) == set(aa)) 32 | assert start_sym == "J" 33 | 34 | G[start.y][start.x] = start_sym 35 | 36 | V = {start} 37 | Q = [start] 38 | 39 | for p in Q: 40 | for diff in ADJ[G[p.y][p.x]]: 41 | n = p + diff 42 | if n not in V: 43 | V.add(n) 44 | Q.append(n) 45 | break 46 | 47 | print(f"Part 1: {len(Q)//2}") 48 | # Shoelace formula 49 | area = abs(sum(Q[i-1].cross(p) for i, p in enumerate(Q))) 50 | # Pick's theorem 51 | print(f"Part 2: {(area - len(Q) + 2) // 2}") 52 | -------------------------------------------------------------------------------- /aoc2023/11.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | 3 | main :- 4 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 5 | maplist(string_codes, Lines, LineCodes), 6 | 7 | solve(LineCodes, 2, Part1), 8 | solve(LineCodes, 1000000, Part2), 9 | format("Part 1: ~d~nPart 2: ~d~n", [Part1, Part2]). 10 | 11 | solve(L, Mul, R) :- 12 | one_way(L, Mul, 0, 0, R1), 13 | transpose(L, L2), 14 | one_way(L2, Mul, 0, 0, R2), 15 | R is R1 + R2. 16 | 17 | one_way([], _, _, _, 0). 18 | one_way([Line | Tail], Mul, Gal, Sum, R) :- 19 | aggregate_all(count, member(0'#, Line), Count), 20 | NGal is Gal + Count, 21 | (Count =:= 0 -> NSum is Sum + NGal * Mul; NSum is Sum + NGal), 22 | one_way(Tail, Mul, NGal, NSum, NR), 23 | R is NR + Count * Sum. 24 | 25 | -------------------------------------------------------------------------------- /aoc2023/11.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 95/51 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | res = 0 10 | 11 | galaxies = [] 12 | 13 | G = [l for l in lines()] 14 | extra_row = [set(l) == {"."} for l in G] 15 | extra_col = [set(G[y][x] for y in range(len(G))) == {"."} for x in range(len(G[0]))] 16 | print(extra_col) 17 | 18 | y = 0 19 | x = 0 20 | ex = 0 21 | ey = 0 22 | for l in G: 23 | x = ex = 0 24 | for c in l: 25 | if c == "#": 26 | p = Point.of(x+ex, y+ey) 27 | for o in galaxies: 28 | res += (p - o).manh_dist() 29 | galaxies.append(p) 30 | x += 1 31 | if x < len(extra_col) and extra_col[x]: 32 | ex += 1 * 1000000-1 33 | 34 | y += 1 35 | if y < len(extra_row) and extra_row[y]: 36 | ey += 1 * 1000000-1 37 | 38 | prints(res) 39 | -------------------------------------------------------------------------------- /aoc2023/12.pl: -------------------------------------------------------------------------------- 1 | :- table dp(_,_,sum). 2 | 3 | main :- 4 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 5 | maplist([Line, L-Cs]>>( 6 | split_string(Line, " ", "", [Left, CSS]), string_codes(Left, L), 7 | split_string(CSS, ",", "", CsL), maplist(number_string, Cs, CsL) 8 | ), Lines, Input), 9 | 10 | aggregate_all(sum(R), (member(L-Cs, Input), solve(L, Cs, R)), Part1), 11 | format("Part 1: ~d~n", [Part1]), 12 | 13 | aggregate_all(sum(R), ( 14 | member(L-Cs, Input), 15 | append([L, [0'?], L, [0'?], L, [0'?], L, [0'?], L], NewL), 16 | append([Cs, Cs, Cs, Cs, Cs], NewCs), 17 | solve(NewL, NewCs, R) 18 | ), Part2), 19 | format("Part 2: ~d~n", [Part2]). 20 | 21 | solve(L, Cs, R) :- append(L, [0'.], I), dp(I, Cs, R). 22 | 23 | dp([], [], 1). 24 | dp([], [_|_], 0). 25 | dp([A|L], Cs, R) :- A \= 0'#, dp(L, Cs, R). 26 | dp([A|L], [C|Cs], R) :- A \= 0'., 27 | length(Head, C), 28 | append(Head, [CT|Tail], [A|L]), 29 | CT \= 0'#, \+ member(0'., Head), 30 | dp(Tail, Cs, R). 31 | -------------------------------------------------------------------------------- /aoc2023/12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 103/19 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | res = 0 10 | 11 | for l in lines(): 12 | l, counts = l.split() 13 | counts = list(ints(counts)) 14 | counts *= 5 15 | l = "?".join(l for _ in range(5)) 16 | N = len(l) 17 | M = len(counts) 18 | 19 | @lru_cache(maxsize=None) 20 | def dp(i, j): 21 | if i == N: 22 | return int(j == M) 23 | 24 | r = 0 25 | if l[i] in ".?": 26 | r += dp(i+1, j) 27 | 28 | if l[i] in "#?": 29 | if j < M: 30 | c = counts[j] 31 | if i + c <= N and "." not in set(l[i:i+c]) and (i + c == N or l[i+c] != "#"): 32 | r += dp(min(i+c+1, N), j+1) 33 | return r 34 | 35 | 36 | 37 | 38 | 39 | res += dp(0, 0) 40 | continue 41 | 42 | poss = [] 43 | for c in l: 44 | if c == "?": 45 | poss.append(".#") 46 | else: 47 | poss.append(c) 48 | 49 | 50 | for aa in product(*poss): 51 | if Counter(aa)["#"] != sum(counts): 52 | continue 53 | i = 0 54 | ok = True 55 | for count in counts: 56 | while i < N and aa[i] == ".": 57 | i += 1 58 | 59 | if i == N: 60 | ok = False 61 | break 62 | 63 | for x in range(count): 64 | if i == N or aa[i] != "#": 65 | ok = False 66 | break 67 | 68 | i += 1 69 | 70 | if i < N and aa[i] != ".": 71 | ok = False 72 | i += 1 73 | 74 | if not ok: 75 | break 76 | res += ok 77 | 78 | 79 | prints(res) 80 | -------------------------------------------------------------------------------- /aoc2023/13.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | 3 | main :- 4 | read_string(user_input, _, S), re_split("\n\n", S, Blocks), 5 | convlist([Block, Codes]>>( 6 | split_string(Block, "\n", "\n", Lines), 7 | Lines \= [""], 8 | maplist(string_codes, Lines, Codes) 9 | ), Blocks, Input), !, 10 | 11 | aggregate_all(sum(R), (member(Mir, Input), solve(Mir, 0, R)), Part1), 12 | aggregate_all(sum(R), (member(Mir, Input), solve(Mir, 1, R)), Part2), 13 | format("Part 1: ~d~nPart 2: ~d~n", [Part1, Part2]). 14 | 15 | solve(Input, Diffs, Result) :- 16 | (find_reflection([], Input, Diffs, Y) -> Result is Y * 100; 17 | transpose(Input, Tr), find_reflection([], Tr, Diffs, Result)). 18 | 19 | find_reflection(L, R, Diffs, Y) :- 20 | L \= [], R \= [], 21 | aggregate_all(count, ( 22 | nth0(I, L, A), nth0(I, R, B), 23 | nth0(J, A, C1), nth0(J, B, C2), 24 | C1 \= C2 25 | ), Diffs), 26 | length(L, Y), !. 27 | 28 | find_reflection(L, [H|T], Diffs, Y) :- 29 | find_reflection([H|L], T, Diffs, Y). 30 | -------------------------------------------------------------------------------- /aoc2023/13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 3/11 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | pats = sys.stdin.read().split("\n\n") 10 | 11 | res = 0 12 | 13 | for pat in pats: 14 | pat = lines(pat) 15 | opat = pat 16 | 17 | mul = 100 18 | prev = None 19 | for _ in range(2): 20 | H = len(pat) 21 | for y in range(1, H): 22 | a = pat[:y] 23 | b = pat[y:] 24 | if all(x == y for x, y in zip(a[::-1], b)): 25 | # res += mul * y 26 | assert prev is None 27 | prev = mul, y 28 | # nwork.add((y, mul)) 29 | break 30 | 31 | 32 | mul = 1 33 | pat = rotate(pat, 3) 34 | 35 | pat = opat 36 | mul = 100 37 | prev = None 38 | for _ in range(2): 39 | H = len(pat) 40 | for y in range(1, H): 41 | a = pat[:y] 42 | b = pat[y:] 43 | wrongs = 0 44 | for x, aaa in zip(a[::-1], b): 45 | for z, w in zip(x, aaa): 46 | wrongs += z != w 47 | if wrongs == 1: 48 | res += mul * y 49 | assert prev is None 50 | prev = mul, y 51 | # nwork.add((y, mul)) 52 | break 53 | 54 | 55 | mul = 1 56 | pat = rotate(pat, 3) 57 | 58 | 59 | prints(res) 60 | -------------------------------------------------------------------------------- /aoc2023/14.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)), use_module(library(prolog_stack)). 2 | :- table cycle(_,-), process(_,_) as subsumptive. 3 | 4 | main :- 5 | % open("14.in", read, Stream), 6 | Stream = user_input, 7 | read_string(Stream, _, S), split_string(S, "\n", "\n", Lines), 8 | maplist(string_codes, Lines, Codes), 9 | rotate(Codes, Input, ccw), 10 | 11 | part1(Input, Part1), 12 | format("Part 1: ~d~n", [Part1]), 13 | 14 | part2(Input, Part2), 15 | format("Part 2: ~d~n", [Part2]). 16 | 17 | rotate(Matrix, Rotated, Dir) :- 18 | transpose(Matrix, Transposed), 19 | (Dir = ccw -> reverse(Transposed, Rotated); 20 | maplist(reverse, Transposed, Rotated)). 21 | 22 | part1(Input, Res) :- tilt(Input, Tilted), load(Tilted, Res). 23 | 24 | tilt(Input, Tilted) :- maplist(tilt_row, Input, Tilted). 25 | tilt_row(Row, Tilted) :- tilt_row(0, Row, Tilted). 26 | tilt_row(Free, [0'#|Row], Tilted) :- 27 | make_dots(Free, Dots), tilt_row(0, Row, Tail), 28 | append([Dots, `#`, Tail], Tilted). 29 | tilt_row(Free, [0'O|Row], [0'O|Tail]) :- tilt_row(Free, Row, Tail). 30 | tilt_row(Free, [0'.|Row], Tilted) :- NFree is Free+1, tilt_row(NFree, Row, Tilted). 31 | tilt_row(Free, [], Dots) :- make_dots(Free, Dots). 32 | 33 | make_dots(N, Dots) :- length(Dots, N), maplist(=(0'.), Dots). 34 | 35 | load(Rows, Load) :- 36 | aggregate_all(sum(Y), (member(Row, Rows), reverse(Row, Rev), nth1(Y, Rev, 0'O)), Load). 37 | 38 | cycle(Input, Output) :- cycle(Input, Output, 4). 39 | cycle(Input, Input, 0). 40 | cycle(Input, Output, N) :- 41 | N > 0, N1 is N-1, 42 | tilt(Input, Tilted), 43 | rotate(Tilted, Rotated, cw), 44 | cycle(Rotated, Output, N1). 45 | 46 | :- dynamic(in/1). 47 | 48 | process(0, Input) :- in(Input). 49 | process(N, Output) :- 50 | N > 0, N1 is N-1, 51 | process(N1, Processed), 52 | cycle(Processed, Output). 53 | 54 | part2(Input, Res) :- 55 | assertz(in(Input)), 56 | 57 | NumCycles is 1000000000, 58 | between(1, NumCycles, D1), 59 | process(D1, Head), 60 | MaxD2 is D1-1, between(1, MaxD2, D2), 61 | process(D2, Head), 62 | Steps is D2 + (NumCycles-D2) mod (D1-D2), 63 | process(Steps, Final), 64 | load(Final, Res). 65 | -------------------------------------------------------------------------------- /aoc2023/14.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 311/26 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | G = [list(l) for l in lines()] 10 | 11 | res = 0 12 | 13 | seen = {} 14 | def key(G): 15 | return "".join("".join(l) for l in G) 16 | 17 | def k2(G): 18 | return tuple(tuple(l) for l in G) 19 | 20 | dist = 0 21 | A = [k2(G)] 22 | while True: 23 | W = len(G[0]) 24 | H = len(G) 25 | dist += 1 26 | for _ in range(4): 27 | for x in range(W): 28 | # rocks = sum(G[y][x] == "O" for y in range(len(G))) 29 | # 30 | for y in range(H): 31 | if G[y][x] == "O": 32 | while y-1 >= 0 and G[y-1][x] == ".": 33 | G[y][x], G[y-1][x] = G[y-1][x], G[y][x] 34 | y -= 1 35 | # res += y+1 36 | 37 | G = rotate(G, -1) 38 | 39 | k = key(G) 40 | if k in seen: 41 | break 42 | 43 | seen[k] = dist 44 | A.append(k2(G)) 45 | # for y in range(len(G)-1, -1, -1): 46 | # if G[y][x] != "#" and rocks: 47 | # rocks -= 1 48 | # res += y+1 49 | 50 | ndist = dist - seen[k] 51 | G = A[seen[k] + (1000000000 - seen[k]) % ndist] 52 | 53 | H = len(G) 54 | W = len(G[0]) 55 | for x in range(W): 56 | for y in range(H): 57 | if G[y][x] == "O": 58 | res += H-y 59 | 60 | prints(res) 61 | -------------------------------------------------------------------------------- /aoc2023/15.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_line_to_string(user_input, S), split_string(S, ",", "", Commands), 3 | maplist(hash, Commands, Hashes), sum_list(Hashes, Part1), 4 | 5 | length(Buckets, 256), maplist(=([]), Buckets), 6 | foldl(apply_command, Commands, Buckets, NBuckets), 7 | aggregate_all(sum(B*I*L), ( 8 | nth1(B, NBuckets, Bucket), nth1(I, Bucket, _-L) 9 | ), Part2), 10 | format("Part 1: ~d~nPart 2: ~d~n", [Part1, Part2]). 11 | 12 | hash(S, H) :- 13 | string_codes(S, Codes), 14 | foldl([X, Y, Z]>>(Z is (X + Y) * 17 mod 256), Codes, 0, H). 15 | 16 | apply_command(Command, Buckets, NBuckets) :- 17 | (string_concat(Label, "-", Command) -> L is -1; 18 | split_string(Command, "=", "", [Label, LS]), 19 | number_string(L, LS)), 20 | hash(Label, B), 21 | nth0(B, Buckets, Bucket, Rest), 22 | (L =:= -1 -> 23 | (selectchk(Label-_, Bucket, NBucket) -> true; NBucket = Bucket); 24 | (selectchk(Label-_, Bucket, Label-L, NBucket) -> true; 25 | append(Bucket, [Label-L], NBucket))), 26 | nth0(B, NBuckets, NBucket, Rest). 27 | -------------------------------------------------------------------------------- /aoc2023/15.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 27/209 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | res = 0 10 | 11 | boxes = [[] for _ in range(256)] 12 | lab_strength = {} 13 | 14 | for seq in input().split(","): 15 | if "-" in seq: 16 | assert seq[-1] == "-" 17 | op = "-" 18 | seq = seq[:-1] 19 | else: 20 | assert "=" in seq 21 | seq, fl = seq.split("=") 22 | op = "=" 23 | fl = int(fl) 24 | 25 | h = 0 26 | for c in seq: 27 | h = (h + ord(c)) * 17 % 256 28 | # res += h 29 | 30 | if op == "-": 31 | s = lab_strength.get(seq) 32 | if seq in boxes[h]: 33 | boxes[h].remove(seq) 34 | else: 35 | lab_strength[seq] = fl 36 | for i, oseq in enumerate(boxes[h]): 37 | if oseq == seq: 38 | boxes[h][i] = seq 39 | break 40 | else: 41 | boxes[h].append(seq) 42 | 43 | for bi, b in enumerate(boxes, 1): 44 | if b: 45 | print(bi, b) 46 | 47 | 48 | 49 | for bi, b in enumerate(boxes, 1): 50 | for li, seq in enumerate(b, 1): 51 | res += bi * li * lab_strength[seq] 52 | # lab_to_box[seq] = (bi, li) 53 | 54 | 55 | 56 | prints(res) 57 | -------------------------------------------------------------------------------- /aoc2023/16.pl: -------------------------------------------------------------------------------- 1 | :- dynamic grid/3. 2 | 3 | main :- 4 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 5 | forall((nth1(Y, Lines, Line), string_code(X, Line, C)), assertz(grid(X, Y, C))), 6 | compile_predicates([grid/3]), 7 | 8 | count_reachable(1-1, 0, Part1), 9 | format("Part 1: ~d~n", [Part1]), 10 | 11 | aggregate_all(max(X), grid(X, _, _), D), 12 | aggregate_all(max(Count), ( 13 | startdir(X-Y, D, Dir), 14 | between(1, D, X), between(1, D, Y), 15 | count_reachable(X-Y, Dir, Count) 16 | ), Part2), 17 | format("Part 2: ~d~n", [Part2]). 18 | 19 | startdir(1-_, _, 0). startdir(_-1, _, 3). 20 | startdir(D-_, D, 2). startdir(_-D, D, 1). 21 | 22 | count_reachable(X-Y, Dir, Count) :- 23 | empty_nb_set(Visited), 24 | reachable(X-Y, Dir, Visited), 25 | aggregate_all(count, P, gen_nb_set(Visited, P-_), Count). 26 | 27 | reachable(P, Dir, Vis) :- 28 | add_nb_set(P-Dir, Vis, New), 29 | (New == false -> true; 30 | forall(move(P, Dir, NP, NDir), reachable(NP, NDir, Vis))). 31 | 32 | dxy(D, P) :- nth0(D, [1-0, 0-(-1), -1-0, 0-1], P). 33 | 34 | move(X-Y, Dir, NX-NY, NDir) :- 35 | grid(X, Y, C), 36 | (string_code(I, "\\/", C) -> (Dir mod 2 =:= I mod 2 -> DD is 1; DD is -1); 37 | string_code(I, "-|", C), Dir mod 2 =:= I mod 2 -> (DD is 1; DD is -1); 38 | DD is 0), 39 | NDir is (Dir + DD) mod 4, 40 | dxy(NDir, Dx-Dy), 41 | NX is X + Dx, NY is Y + Dy, 42 | grid(NX, NY, _). 43 | -------------------------------------------------------------------------------- /aoc2023/16.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 109/116 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | G = lines() 10 | AA = { 11 | "|": [(0, -1), (0, 1)], 12 | "-": [(1, 0), (-1, 0)], 13 | } 14 | 15 | res = 0 16 | DI = DIR_NORTHNEG 17 | start = Point.of(0, 0) 18 | 19 | W, H = len(G[0]), len(G) 20 | def count(start, sd): 21 | V = {(start, sd)} 22 | Q = [(start, sd)] 23 | 24 | def nxt(np, nd): 25 | if (np, nd) in V: 26 | return 27 | V.add((np, nd)) 28 | Q.append((np, nd)) 29 | 30 | for p, d in Q: 31 | di = DI[d] 32 | np = p + di 33 | if not 0 <= np.x < W or not 0 <= np.y < H: 34 | continue 35 | 36 | c = G[np.y][np.x] 37 | if c in AA: 38 | adjs = AA[c] 39 | if di not in AA[c]: 40 | nxt(np, (d + 1) % 4) 41 | nxt(np, (d - 1) % 4) 42 | else: 43 | nxt(np, d) 44 | elif c == ".": 45 | nxt(np, d) 46 | elif c == "/": 47 | if d == 0: 48 | nxt(np, (d + 1) % 4) 49 | elif d == 1: 50 | nxt(np, (d - 1) % 4) 51 | elif d == 2: 52 | nxt(np, (d + 1) % 4) 53 | elif d == 3: 54 | nxt(np, (d - 1) % 4) 55 | elif c == "\\": 56 | if d == 0: 57 | nxt(np, (d - 1) % 4) 58 | elif d == 1: 59 | nxt(np, (d + 1) % 4) 60 | elif d == 2: 61 | nxt(np, (d - 1) % 4) 62 | elif d == 3: 63 | nxt(np, (d + 1) % 4) 64 | else: 65 | assert False 66 | 67 | ps = set(p for p, d in V) 68 | # print_coords(ps, ".") 69 | return len(ps)-1 70 | 71 | # print(count(Point.of(3, -1), 3)) 72 | # exit() 73 | 74 | best = 0 75 | for y in range(len(G)): 76 | best = max(best, count(Point.of(-1, y), 0)) 77 | best = max(best, count(Point.of(W, y), 2)) 78 | 79 | for x in range(W): 80 | best = max(best, count(Point.of(x, -1), 3)) 81 | best = max(best, count(Point.of(x, H), 1)) 82 | 83 | # prints(count(Point.of(-1, 0), 0)) 84 | prints(best) 85 | -------------------------------------------------------------------------------- /aoc2023/17.pl: -------------------------------------------------------------------------------- 1 | :- dynamic grid/3. 2 | :- table path(_,_,_,_,min). 3 | 4 | main :- 5 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 6 | forall((nth1(Y, Lines, Line), string_code(X, Line, C), V is C - 0'0), 7 | assertz(grid(X, Y, V))), 8 | compile_predicates([grid/3]), 9 | 10 | % Assert that the grid is a square 11 | aggregate_all(r(max(X), max(Y)), grid(X, Y, _), r(W, W)), 12 | aggregate_all(min(C), path(W, W, _, 1-3, C), Part1), 13 | aggregate_all(min(C), path(W, W, _, 4-10, C), Part2), 14 | format("Part 1: ~d~nPart 2: ~d~n", [Part1, Part2]). 15 | 16 | path(1, 1, _, _, 0). 17 | path(X, Y, St, Lo-Hi, R) :- 18 | (Sign = 1; Sign = -1), 19 | (St = 0; St = 1), DX is St * Sign, DY is (1-St) * Sign, 20 | between(Lo, Hi, D), 21 | NX is X + DX * D, NY is Y + DY * D, 22 | grid(NX, NY, _), NSt is 1-St, Lim is D-1, 23 | path(NX, NY, NSt, Lo-Hi, R1), 24 | aggregate_all(sum(V), ( 25 | between(0, Lim, T), 26 | TX is X + DX * T, TY is Y + DY * T, 27 | grid(TX, TY, V) 28 | ), R2), 29 | R is R1 + R2. 30 | -------------------------------------------------------------------------------- /aoc2023/17.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 5/3 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | G = [list(map(int, l)) for l in lines()] 10 | 11 | res = 0 12 | 13 | def adj(n): 14 | p, s = n 15 | di = Point.of(1, 0) 16 | if s: 17 | di = di.perp() 18 | 19 | for dd in (-1, 1): 20 | cost = 0 21 | np = p 22 | for d in range(10): 23 | np = np + di * dd 24 | if 0 <= np.x < len(G[0]) and 0 <= np.y < len(G): 25 | cost += G[np.y][np.x] 26 | if d >= 3: 27 | yield ((np, not s), cost) 28 | else: 29 | break 30 | 31 | D, _ = dijkstra(adj, (Point.of(0, 0), False), (Point.of(0, 0), True)) 32 | W, H = len(G[0]), len(G) 33 | p = Point.of(W-1, H-1) 34 | prints(min(D[(p, s)] for s in (False, True))) 35 | # prints(res) 36 | -------------------------------------------------------------------------------- /aoc2023/18.pl: -------------------------------------------------------------------------------- 1 | main :- 2 | read_string(user_input, _, S), split_string(S, "\n", "\n", Lines), 3 | 4 | maplist([Line, D-Steps]>>( 5 | split_string(Line, " ", "", [D, StepsStr, _]), 6 | number_string(Steps, StepsStr) 7 | ), Lines, Ins1), 8 | solve(Ins1, Part1), 9 | 10 | maplist([Line, D-Steps]>>( 11 | re_matchsub("#(\\w{5})(\\w)", Line, _{0:_, 1:StepsStr, 2:DS}, []), 12 | number_string(DSI, DS), sub_string("RDLU", DSI, 1, _, D), 13 | string_concat("0x", StepsStr, StepsHex), number_string(Steps, StepsHex) 14 | ), Lines, Ins2), 15 | solve(Ins2, Part2), 16 | format("Part 1: ~d~nPart 2: ~d~n", [Part1, Part2]). 17 | 18 | dx(C, D) :- string_code(I, "RULD", C), nth1(I, [1, 0, -1, 0], D). 19 | dy(C, D) :- string_code(I, "RULD", C), nth1(I, [0, 1, 0, -1], D). 20 | 21 | solve(Instructions, Result) :- 22 | foldl([D-Steps, X-Y-Area-Side, NX-NY-NArea-NSide]>>( 23 | dx(D, Dx), dy(D, Dy), 24 | NX is X + Dx * Steps, NY is Y + Dy * Steps, 25 | NSide is Side + Steps, NArea is Area + X * NY - Y * NX 26 | ), Instructions, 0-0-0-0, _-_-Area-Side), 27 | Int is abs(Area) // 2 - Side // 2 + 1, 28 | Result is Int + Side. 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /aoc2023/18.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("py", "in")) 7 | 8 | res = 0 9 | P = Point 10 | 11 | c = P.of(0, 0) 12 | D = dict(zip("RULD", DIR_NORTHNEG)) 13 | HD = dict(zip("0123", "RDLU")) 14 | points = [c] 15 | for l in lines(): 16 | d, a, hex = l.split() 17 | a = int(a) 18 | hex = hex[2:-1] 19 | a = int(hex[:5], 16) 20 | d = HD[hex[5]] 21 | 22 | c += P(D[d]) * a 23 | # for _ in range(a): 24 | # c += D[d] 25 | points.append(c) 26 | 27 | # print(points[-1], c, d, a) 28 | print(len(points)) 29 | assert points[0] == points[-1] 30 | 31 | area = abs(sum(points[i-1].cross(points[i]) for i in range(len(points)))) 32 | sidelen = sum((points[i]-points[i-1]).manh_dist() for i in range(len(points))) 33 | interior = area // 2 - sidelen // 2 + 1 34 | prints(sidelen + interior) 35 | exit() 36 | 37 | sy = min(p.y for p in points) 38 | ey = max(p.y for p in points) 39 | for y in range(sy, ey+1): 40 | # ps = [p.x for p in points if p.y == y] 41 | ps = [] 42 | for p1, p2 in windows(points, 2): 43 | if p1.y == p2.y == y: 44 | ps.append((min(p1.x, p2.x), max(p1.x, p2.x))) 45 | else: 46 | my, may = min(p1.y, p2.y), max(p1.y, p2.y) 47 | if my <= y < may: 48 | ps.append((p1.x, p1.x)) 49 | 50 | ps = merge_intervals(ps) 51 | flag = False 52 | last = -1 53 | contr = 0 54 | for a, b in ps: 55 | if flag: 56 | contr += b - last + 1 57 | else: 58 | contr += b - a + 1 59 | last = b+1 60 | flag = not flag 61 | # res += p2 - p1+1 62 | res += contr 63 | print(y, contr, ps) 64 | # print_coords(points) 65 | prints(res) 66 | exit() 67 | 68 | # print(points[0], points[1], points[-1], points[-2]) 69 | # exit() 70 | # for dp in OCTDIR: 71 | # p = points[0] + dp 72 | # if p in points: 73 | # continue 74 | # 75 | # Q = [p] 76 | # V = set(Q) | set(points) 77 | # ok = True 78 | # for i in Q: 79 | # if len(V) >= 10 ** 6: 80 | # break 81 | # for j in map(P, neighbours(*i, dirs=DIR)): 82 | # if j not in V: 83 | # V.add(j) 84 | # Q.append(j) 85 | # else: 86 | # prints(len(V)) 87 | # break 88 | 89 | -------------------------------------------------------------------------------- /aoc2023/19.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(dcg/basics)), use_module(library(clpfd)). 2 | 3 | list([H|T], Element, Sep) --> call(Element, H), list_(T, Element, Sep). 4 | list_([H|T], Element, Sep) --> Sep, call(Element, H), list_(T, Element, Sep). 5 | list_([], _, _) --> []. 6 | 7 | workflow(Name-Rules) --> string_without("{\n", NameC), 8 | "{", list(Rules, rule, ","), "}", { string_codes(Name, NameC) }. 9 | rule([Key,Op,Val,To]) --> [C, COp], { member(COp, `<>`) }, integer(Val), ":", 10 | string(ToC), { maplist(char_code, [Key, Op], [C, COp]), string_codes(To, ToC) }. 11 | rule([To]) --> string(ToC), { string_codes(To, ToC) }. 12 | 13 | part(Part) --> "{", list(Part, pval, ","), "}". 14 | pval(Key-Val) --> [C, 0'=], integer(Val), { char_code(Key, C) }. 15 | 16 | parse(Workflows, Parts) --> list(Workflows, workflow, "\n"), "\n\n", list(Parts, part, "\n"), "\n". 17 | 18 | score1(Part, Score) :- aggregate_all(sum(V), member(_-V, Part), Score). 19 | 20 | process(_, "A", Part, ScoreFun, Score) :- call(ScoreFun, Part, Score). 21 | process(_, "R", _, _, 0). 22 | process(Workflows, Name, Part, ScoreFun, Res) :- 23 | memberchk(Name-Rules, Workflows), 24 | process_rules(Workflows, Rules, Part, ScoreFun, Res). 25 | 26 | process_rules(Workflows, [[To]|_], Part, ScoreFun, Res) :- 27 | process(Workflows, To, Part, ScoreFun, Res). 28 | process_rules(Workflows, [[Key, Op, Val, To]|Tail], Part, ScoreFun, Res) :- 29 | memberchk(Key-CV, Part), 30 | (Op = < -> CV #< Val #<==> B; CV #> Val #<==> B), 31 | aggregate_all(sum(R), ( 32 | (B #= 1, process(Workflows, To, Part, ScoreFun, R)); 33 | (B #= 0, process_rules(Workflows, Tail, Part, ScoreFun, R)) 34 | ), Res). 35 | 36 | score2(Part, Score) :- foldl([_-V,A,B]>>(fd_size(V, S), B is A * S), Part, 1, Score). 37 | 38 | main :- 39 | phrase_from_stream(parse(Workflows, Parts), user_input), 40 | aggregate_all(sum(Score), ( 41 | member(Part, Parts), 42 | process(Workflows, "in", Part, score1, Score) 43 | ), Part1), 44 | format("Part 1: ~d~n", [Part1]), 45 | 46 | length(Vs, 4), Vs ins 1..4000, 47 | pairs_keys_values(Part, [x,m,a,s], Vs), 48 | process(Workflows, "in", Part, score2, Part2), 49 | format("Part 2: ~d~n", [Part2]). 50 | 51 | 52 | -------------------------------------------------------------------------------- /aoc2023/19.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 11/1 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | ws, psb = sys.stdin.read().split("\n\n") 10 | 11 | workflows = {} 12 | for line in lines(ws): 13 | name, rest = line.split("{") 14 | rest = rest[:-1] 15 | ps = rest.split(",") 16 | workflows[name] = [p.split(":") for p in ps] 17 | # print(name, workflows[name]) 18 | 19 | res = 0 20 | 21 | def process(wn, part): 22 | if wn == "A": 23 | return True 24 | elif wn == "R": 25 | return False 26 | rule = workflows[wn] 27 | for r in rule: 28 | if len(r) == 1: 29 | return process(r[0], part) 30 | 31 | if eval(r[0], locals=part): 32 | return process(r[1], part) 33 | assert False 34 | 35 | for part in lines(psb): 36 | part = part[1:-1].split(",") 37 | d = {} 38 | for p in part: 39 | k, v = p.split("=") 40 | d[k] = int(v) 41 | 42 | if process("in", d): 43 | res += sum(d.values()) 44 | # print(d) 45 | 46 | def process(wn, part): 47 | if wn == "A": 48 | return math.prod(b - a + 1 for a, b in part.values()) if all(a <= b for a, b in part.values()) else 0 49 | return True 50 | elif wn == "R": 51 | return 0 52 | rule = workflows[wn] 53 | curr = 0 54 | for r in rule: 55 | if len(r) == 1: 56 | curr += process(r[0], part) 57 | continue 58 | 59 | cond, res = r 60 | op = ">" if ">" in cond else "<" 61 | a, b = cond.split(op) 62 | lo, hi = part[a] 63 | p2 = dict(part) 64 | if op == ">": 65 | p2[a] = (max(lo, int(b) + 1), hi) 66 | curr += process(res, p2) 67 | part[a] = (lo, min(hi, int(b))) 68 | else: 69 | p2[a] = (lo, min(hi, int(b) - 1)) 70 | curr += process(res, p2) 71 | part[a] = (max(lo, int(b)), hi) 72 | 73 | # if eval(r[0], locals=part): 74 | # return process(r[1], part) 75 | return curr 76 | assert False 77 | 78 | prints(process("in", {k: (1, 4000) for k in "xmas"})) 79 | -------------------------------------------------------------------------------- /aoc2023/20.pl: -------------------------------------------------------------------------------- 1 | :- [library(dcg/basics), library(dcg/high_order)]. 2 | :- [library(chr), library(aggregate), library(apply), library(apply_macros), library(yall)]. 3 | :- dynamic adj/2. 4 | 5 | str(S) --> string(Cs), { string_codes(S, Cs) }. 6 | module(mod(Name, K, Adj)) --> 7 | ([K], { memberchk(K, `%&`) }; {K = 0'.}), str(Name), " -> ", sequence(str, ", ", Adj), "\n". 8 | 9 | :- chr_constraint counter(+, +), pulse(+, +). 10 | 11 | counter(P, C), pulse(P, _) <=> NC is C+1, counter(P, NC). 12 | 13 | init_state(_, 0'%, fflop(0)). 14 | init_state(Name, 0'&, conj(Prev)) :- findall(P, (adj(P, Adj), memberchk(Name, Adj)), Prev). 15 | init_state(_, 0'., normal). 16 | 17 | main :- 18 | open("20.in", read, Stream), 19 | phrase_from_stream(sequence(module, Mods), Stream), !, 20 | forall(member(mod(Name, _, Adj), Mods), assertz(adj(Name, Adj))), 21 | compile_predicates([adj/2]), 22 | 23 | maplist([mod(Name, K, _), Name-Spec]>>once(init_state(Name, K, Spec)), Mods, Pairs), 24 | ht_pairs(State, Pairs), 25 | 26 | ($(part1(State)), fail; true), 27 | profile(part2(State)). 28 | 29 | part1(State) => 30 | counter(0, 0), counter(1, 0), 31 | foreach(between(1, 1000, _), press(State)), 32 | findall(C, current_chr_constraint(counter(_, C)), [L, H]), 33 | Part1 is L * H, 34 | format("Part 1: ~d~n", [Part1]). 35 | 36 | press(State) :- 37 | foldl({State}/[pulse(From, P, To), QT, NQT]>>( 38 | pulse(P, From), 39 | ht_put(State, To, NS, normal, OS), 40 | (process(OS, From, P, NS, NP) -> 41 | findall(pulse(To, NP, NTo), (adj(To, L), member(NTo, L)), QT, NQT) 42 | ; QT = NQT, OS = NS) 43 | ), [pulse("button", 0, "broadcaster")|Tail], Tail, []), !. 44 | 45 | process(normal, _, P, normal, P). 46 | process(fflop(P), _, 0, fflop(NP), NP) :- NP is 1-P. 47 | process(conj(Lows), From, P, conj(NLows), NP) :- 48 | (P =:= 1 -> subtract(Lows, [From], NLows); union(Lows, [From], NLows)), 49 | (NLows == [] -> NP is 0; NP is 1). 50 | 51 | :- chr_constraint presses(+natural), first_high(+, -). 52 | 53 | presses(X), pulse(_, "button") <=> Y is X+1, presses(Y). 54 | presses(C) \ first_high(X, Y), pulse(1, X) <=> Y = C. 55 | pulse(_, _) <=> true. 56 | 57 | part2(State) :- 58 | presses(0), 59 | findall(P, (adj(PP, ["rx"]), adj(P, Adj), memberchk(PP, Adj)), Prev), 60 | maplist(first_high, Prev, Hs), solve(State, Hs). 61 | 62 | solve(State, Hs) :- maplist(number, Hs) -> 63 | foldl([H, A, R]>>(R is H * A), Hs, 1, Part2), 64 | format("Part 2: ~d~n", [Part2]); 65 | press(State), solve(State, Hs). 66 | -------------------------------------------------------------------------------- /aoc2023/21.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | :- table reach(_,_,_,min), dxy/2. 3 | :- dynamic grid/2. 4 | 5 | main :- 6 | open("21.in", read, Stream), 7 | read_string(Stream, _, S), split_string(S, "\n", "\n", Lines), 8 | forall((nth1(Y, Lines, Line), string_code(X, Line, C), C \= 0'#), assertz(grid(X, Y))), 9 | compile_predicates([grid/2]), 10 | 11 | aggregate_all(max(W), grid(W, W), W), nb_setval(dim, W), 12 | 13 | count_reachable(64, Part1), 14 | format("Part 1: ~d~n", [Part1]), 15 | 16 | findall(R, ( 17 | between(0, 2, T), MD is W // 2 + W * T, 18 | count_reachable(MD, R), 19 | writeln(T-R) 20 | ), Xs), 21 | % Xs = [3682,32768,90820], 22 | 23 | foreach(nth0(X, Xs, V), (A * X^2 + B * X + C #= V)), 24 | [A, B, C] ins 0..1000000, 25 | once(label([A, B, C])), 26 | 27 | Steps is 26501365 // W, 28 | Part2 is A * Steps^2 + B * Steps + C, 29 | format("Part 2: ~d~n", [Part2]). 30 | 31 | dxy(X, Y) :- abs(X) + abs(Y) #= 1, label([X, Y]). 32 | 33 | count_reachable(Hi, R) :- 34 | abolish_table_subgoals(reach/4), 35 | aggregate_all(count, (reach(_, _, Hi, D), D mod 2 =:= Hi mod 2), R). 36 | 37 | reach(X, X, _, 0) :- nb_getval(dim, W), X is (W+1) // 2. 38 | reach(X, Y, Hi, D) :- nb_getval(dim, W), 39 | reach(A, B, Hi, D1), D1 < Hi, 40 | dxy(DX, DY), X is A + DX, Y is B + DY, 41 | MX is (X-1) mod W + 1, MY is (Y-1) mod W + 1, 42 | grid(MX, MY), D is D1+1. 43 | -------------------------------------------------------------------------------- /aoc2023/21_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | 3 | from util import * 4 | 5 | if len(sys.argv) == 1: 6 | sys.stdin = open(__file__.replace("_post.py", ".in")) 7 | 8 | G = lines() 9 | 10 | W, H = len(G[0]), len(G) 11 | assert W == H 12 | start = next((x, y) for y, l in enumerate(G) for x, c in enumerate(l) if c == "S") 13 | 14 | get_adj = lambda G: grid_adj_2d(W, H, pred=lambda _, p: G[p[1]][p[0]] != "#") 15 | print(f"""Part 1: { 16 | sum(d <= 64 and d % 2 == 0 for d in bfs(get_adj(G), start)[0].values()) 17 | }""") 18 | 19 | steps = 26501365 20 | LIM = ceildiv(steps, W) 21 | res = 0 22 | 23 | for mirrored, G in enumerate((G, [l[::-1] for l in G])): 24 | adj = get_adj(G) 25 | distances = {p: list(bfs(adj, p)[0].values()) for p in product((0, W // 2, W-1), repeat=2)} 26 | maxdist = max(max(d) for d in distances.values()) 27 | 28 | @cache 29 | def reaches(p: tuple[int, int], steps: int) -> int: 30 | return sum(d <= steps and d % 2 == steps % 2 for d in distances[p]) 31 | 32 | @cache 33 | def by_parity(p: tuple[int, int], parity: int) -> int: 34 | return sum(d % 2 == parity for d in distances[p]) 35 | 36 | def tile_distance(diff: int) -> int: 37 | if not diff: return 0 38 | return W * diff - sign(diff) * (W // 2) 39 | 40 | X = 0 41 | for row in range(-LIM, LIM+1): 42 | row_dist = tile_distance(row) 43 | too_far = lambda x: abs(row_dist) + tile_distance(x) > steps 44 | 45 | while not too_far(X): X += 1 46 | while X >= 1 and too_far(X-1): X -= 1 47 | 48 | x = X-1 49 | while x >= mirrored: 50 | x_dist = tile_distance(x) 51 | remaining_steps = steps - abs(row_dist) - x_dist 52 | p = ((start[0] + x_dist) % W, (start[1] + row_dist) % H) 53 | 54 | if remaining_steps >= maxdist: 55 | # We can reach all plots in the tile (with the correct parity) 56 | if x > 2: 57 | jump = (x - 1) // 2 58 | res += len(distances[p]) * jump 59 | x -= jump * 2 60 | else: 61 | res += by_parity(p, remaining_steps % 2) 62 | x -= 1 63 | else: 64 | res += reaches(p, remaining_steps) 65 | x -= 1 66 | 67 | 68 | prints(f"Part 2: {res}") 69 | -------------------------------------------------------------------------------- /aoc2023/22.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(dcg/basics)), use_module(library(dcg/high_order)). 2 | :- dynamic edge/2, pt/4. 3 | 4 | brick(Brick) --> sequence(sequence(integer, ","), "~", [L, H]), "\n", 5 | { pairs_keys_values(Brick, L, H) }. 6 | 7 | main :- 8 | phrase_from_stream(sequence(brick, Bricks), user_input), 9 | predsort(cmp, Bricks, Sorted), 10 | forall(nth0(I, Sorted, B), ( 11 | (aggregate_all(max(Z), (area(B, X, Y), pt(X, Y, Z, _)), NZ) -> 12 | forall(distinct(J, (area(B, X, Y), pt(X, Y, NZ, J))), assertz(edge(I, J))); 13 | NZ = 0), 14 | [_, _, ZL-ZH] = B, 15 | Z is NZ + 1 + ZH - ZL, 16 | forall(area(B, X, Y), assertz(pt(X, Y, Z, I))) 17 | )), 18 | 19 | aggregate_all(count, ( 20 | nth0(I, Sorted, _), \+ (edge(J, I), dif(I, K), \+ edge(J, K)) 21 | ), Part1), 22 | format("Part 1: ~d~n", [Part1]), 23 | 24 | aggregate_all(sum(Size-1), ( 25 | nth0(I, Sorted, _), 26 | ht_new(V), solve([I], V), ht_size(V, Size) 27 | ), Part2), 28 | format("Part 2: ~d~n", [Part2]). 29 | 30 | get_z([_,_,Z-_], Z). 31 | cmp(R, A, B) :- get_z(A, X), get_z(B, Y), (X =< Y -> R = <; R = >). 32 | 33 | area([XL-XH, YL-YH, _], X, Y) :- between(XL, XH, X), between(YL, YH, Y). 34 | 35 | solve([], _) :- !. 36 | solve([I|Tail], Deleted) :- 37 | ht_put(Deleted, I, 0), 38 | findall(J, (edge(J, I), forall(edge(J, K), ht_get(Deleted, K, 0))), Q, Tail), 39 | solve(Q, Deleted). 40 | -------------------------------------------------------------------------------- /aoc2023/22.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 74/71 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | P = Point 10 | 11 | res = 0 12 | 13 | bricks = [] 14 | 15 | for l in lines(): 16 | a, b = l.split("~") 17 | a, b = map(lambda s: P.of(*map(int, s.split(","))), [a, b]) 18 | for x, y in zip(a, b): 19 | assert x <= y 20 | bricks.append((a, b)) 21 | 22 | def key(b): 23 | a, b = b 24 | return a.z 25 | 26 | bricks.sort(key=key) 27 | 28 | X = defaultdict(dict) 29 | 30 | # supports = Counter() 31 | ban = set() 32 | 33 | order = sorted(range(len(bricks)), key=lambda i: bricks[i][0].z) 34 | 35 | adj = defaultdict(set) 36 | belows = dict() 37 | 38 | for i in order: 39 | s = chr(ord("A") + i) 40 | br = bricks[i] 41 | a, b = br 42 | z = a.z-1 43 | while z >= 1: 44 | below = set() 45 | for x in range(a.x, b.x+1): 46 | for y in range(a.y, b.y+1): 47 | if (x, y) in X[z]: 48 | below.add(X[z][(x, y)]) 49 | # if (x, y) in Y[z]: 50 | # below.add(Y[z][(x, y)]) 51 | 52 | if not below: 53 | z -= 1 54 | else: 55 | # print(s, "by", below) 56 | belows[i] = below 57 | for j in below: 58 | adj[j].add(i) 59 | # if len(below) == 1: 60 | # for j in below: 61 | # adj[j].add(i) 62 | # ban |= below 63 | # for i in below: 64 | # supports[i] += 1 65 | break 66 | else: 67 | belows[i] = set() 68 | 69 | z += 1 70 | # print(s, a, b, z) 71 | 72 | for x in range(a.x, b.x+1): 73 | for y in range(a.y, b.y+1): 74 | for nz in range(z, z+b.z-a.z+1): 75 | X[nz][(x, y)] = i 76 | 77 | cp = {i: set(b) for i, b in belows.items()} 78 | 79 | for i in range(len(bricks)): 80 | Q = [i] 81 | for x in Q: 82 | for j in adj[x]: 83 | belows[j].remove(x) 84 | if not belows[j]: 85 | Q.append(j) 86 | 87 | res += len(Q)-1 88 | belows = {i: set(b) for i, b in cp.items()} 89 | # bfs(adj, i)[0]) 90 | # print(i, adj[i]) 91 | 92 | 93 | prints(res) 94 | exit() 95 | 96 | 97 | print(ban) 98 | prints(len(bricks) - len(ban)) 99 | -------------------------------------------------------------------------------- /aoc2023/23.pl: -------------------------------------------------------------------------------- 1 | :- dynamic grid/3, edge/3, dfs/3. 2 | 3 | % Essentially just a translation of the Python solution to Prolog. 🤔 4 | 5 | main :- 6 | read_string(user_input, _, S), 7 | forall(( 8 | split_string(S, "\n", "\n", Lines), 9 | nth1(Y, Lines, Line), string_code(X, Line, C), 10 | C \= 0'# 11 | ), assertz(grid(X, Y, C))), 12 | compile_predicates([grid/3]), 13 | 14 | aggregate_all(r(max(X), max(Y)), grid(X, Y, _), r(W, H)), 15 | findall(X-Y, ( 16 | grid(X, Y, 0'.), 17 | aggregate_all(count, adj(X-Y, _), C), C >= 3 18 | ), Rest), 19 | Waypoints = [2-1, W-H | Rest], 20 | 21 | forall(( 22 | nth0(I, Waypoints, P), adj(P, NP), 23 | dif(P, NP2), once(adj(NP, NP2)), 24 | find_path(NP, P, Waypoints, R-P2) 25 | ), (nth0(J, Waypoints, P2), assertz(edge(I, J, R)))), 26 | once(edge(X, 1, D)), asserta((dfs(X, _, D) :- !)), 27 | compile_predicates([dfs/3]), 28 | 29 | dfs(0, 0, Part1), 30 | format("Part 1: ~d~n", [Part1]), 31 | 32 | findall(edge(B, A, C), edge(A, B, C), REdges), 33 | maplist(assertz, REdges), 34 | compile_predicates([edge/3]), 35 | dfs(0, 0, Part2), 36 | format("Part 2: ~d~n", [Part2]). 37 | 38 | adj(X-Y, NX-NY) :- 39 | grid(X, Y, C), dxy(C, DX, DY), 40 | NX is X+DX, NY is Y+DY, grid(NX, NY, _). 41 | 42 | find_path(P, _, WP, 1-P) :- memberchk(P, WP), !. 43 | find_path(P, PP, WP, R-RP) :- 44 | adj(P, NP), NP \= PP, find_path(NP, P, WP, R0-RP), R is R0+1. 45 | 46 | dxy(0'., X, Y) :- dif(C, 0'.), dxy(C, X, Y). 47 | dxy(C, X, Y) :- member(C-X-Y, [0'>-1-0, 0'v-0-1, 0'<-(-1)-0, 0'^-0-(-1)]). 48 | 49 | dfs(P, Vis, Res) :- 50 | getbit(Vis, P) =:= 0, NVis is Vis \/ (1<^ 2 31 | ] 32 | jun_index = {p: i for i, p in enumerate(junctions)} 33 | N = len(junctions) 34 | 35 | def find_junction(cur, prev): 36 | dist = 1 37 | while (j := jun_index.get(cur, -1)) == -1: 38 | dist += 1 39 | cur, prev = next(nxt for nxt in neighbours(*cur, V=V) if nxt != prev), cur 40 | return j, dist 41 | 42 | adj = [[find_junction(np, p) for np in neighbours(*p, V=V)] for p in junctions] 43 | (endi, endd), = adj[1] 44 | 45 | def longest_path_2(i, bs): 46 | if i == endi: return endd 47 | return max((w+longest_path_2(j, bs|(1<>( 6 | re_foldl([re_match{0:N}, [N|Ns], Ns]>>true, "-?\\d+"/t, L, [X,Y,Z|V], [], []), 7 | pairs_keys_values(Stone, [X,Y,Z], V) 8 | ), Lines, Stones), 9 | 10 | aggregate_all(count, ( 11 | maplist([St,R]>>append(R, [_], St), Stones, Stones2D), 12 | { T1 >= 0, T2 >= 0 }, 13 | append(L, [A|_], Stones2D), member(B, L), 14 | maplist({T1,T2}/[P1-V1, P2-V2]>>{ 15 | P =:= P1 + V1 * T1, 16 | P =:= P2 + V2 * T2, 17 | 200000000000000 =< P, P =< 400000000000000 18 | % 7 =< P, P =< 27 19 | }, A, B) 20 | ), Part1), 21 | format("Part 1: ~d~n", Part1), 22 | 23 | Params = [X-XV, Y-YV, Z-ZV], 24 | [XV,YV,ZV] ins -3..300, 25 | X #>= 0, Y #>= 0, Z #>= 0, 26 | maplist({Params}/[St, T]>>( 27 | T #> 0, 28 | maplist({T}/[P1-V1, P2-V2]>>( 29 | P1 #= P2 + (V2 - V1) * T 30 | % P1 + V1 * T #= P2 + V2 * T 31 | ), St, Params) 32 | ), Stones, Times), 33 | once(label([X,Y,Z,XV,YV,ZV|Times])), 34 | Part2 is X+Y+Z, 35 | format("Part 2: ~d~n", Part2). 36 | -------------------------------------------------------------------------------- /aoc2023/24.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 194/8 3 | import z4 as z3 4 | 5 | from util import * 6 | 7 | if len(sys.argv) == 1: 8 | sys.stdin = open(__file__.replace("py", "in")) 9 | 10 | P = Point 11 | stones = [] 12 | for l in lines(): 13 | p, v = l.split(" @ ") 14 | p, v = [P.of(*map(int, l.split(","))) for l in (p, v)] 15 | stones.append((p, v)) 16 | # print(stones) 17 | 18 | res = 0 19 | LOW = 200000000000000 20 | HI = 400000000000000 21 | # LOW = 7 22 | # HI = 27 23 | 24 | s = z3.Solver() 25 | P = z3.Reals("x1, y1, z1") 26 | V = z3.Reals("xv1, yv1, zv1") 27 | times = z3.RealVector("t", len(stones)) 28 | for t, (p1, v1) in zip(times, stones): 29 | s.add(t >= 0) 30 | for i in range(3): 31 | s.add(P[i] + t * V[i] == p1[i] + t * v1[i]) 32 | 33 | r = s.check() 34 | assert r == z3.sat 35 | m = s.model() 36 | print(sum(m[p].as_long() for p in P)) 37 | exit() 38 | 39 | for i, (p1, v1) in enumerate(stones): 40 | print(f"{i/len(stones):.2%}") 41 | s = z3.Solver() 42 | t1, t2 = z3.Reals("t1, t2") 43 | s.add(t1 >= 0, t2 >= 0) 44 | for p2, v2 in stones[:i]: 45 | s.push() 46 | for j in range(2): 47 | x1 = p1[j] + t1 * v1[j] 48 | s.add(LOW <= x1, x1 <= HI) 49 | s.add(x1 == p2[j] + t2 * v2[j]) 50 | 51 | r = s.check() 52 | res += r == z3.sat 53 | s.pop() 54 | # print(r) 55 | # print(p1, p2, r, s.sexpr()) 56 | # res += 1 57 | 58 | 59 | 60 | 61 | prints(res) 62 | -------------------------------------------------------------------------------- /aoc2023/24_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | import z4 as z3 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("_post.py", ".in")) 8 | 9 | stones = [(Point(vs[:3]), Point(vs[3:])) for l in lines() for vs in [[*ints(l)]]] 10 | 11 | res = 0 12 | LO = 200000000000000 13 | HI = 400000000000000 14 | 15 | if False: 16 | s = z3.Solver() 17 | t1, t2 = z3.Reals("t1 t2") 18 | s.add(t1 >= 0, t2 >= 0) 19 | for i, (p1, v1) in enumerate(stones): 20 | if i % 3 == 0: print(f"{i/len(stones):.0%}") 21 | xs = [p1[j] + t1 * v1[j] for j in range(2)] 22 | s.push() 23 | for v in xs: s.add(LO <= v, v <= HI) 24 | 25 | for p2, v2 in stones[:i]: 26 | s.push() 27 | for j, v in enumerate(xs): 28 | s.add(v == p2[j] + t2 * v2[j]) 29 | 30 | res += s.check() == z3.sat 31 | s.pop() 32 | 33 | s.pop() 34 | else: 35 | def lineInter(s1: Point, e1: Point, s2: Point, e2: Point): 36 | d = (e1 - s1).cross(e2 - s2) 37 | if d == 0: return (-(s1.cross2(e1, s2) == 0), Point.of(0, 0)) 38 | p = s2.cross2(e1, e2) 39 | q = s2.cross2(e2, s1) 40 | return (1, (s1 * p + e1 * q) / d) 41 | 42 | for i, (p1, v1) in enumerate(stones): 43 | for p2, v2 in stones[:i]: 44 | r, p = lineInter(p1, p1 + v1, p2, p2 + v2) 45 | assert r != -1 46 | if r == 1: 47 | works = LO <= p.x <= HI and LO <= p.y <= HI and \ 48 | all(sign(v) == sign(d) for v, d in zip(v1.c[:2], p - p1)) and \ 49 | all(sign(v) == sign(d) for v, d in zip(v2.c[:2], p - p2)) 50 | res += works 51 | 52 | 53 | print(f"Part 1: {res}") 54 | 55 | s = z3.Solver() 56 | P = z3.RealVector("p", 3) 57 | V = z3.RealVector("v", 3) 58 | times = z3.RealVector("t", len(stones)) 59 | for t, (p, v) in zip(times, stones): 60 | s.add(t >= 0) 61 | for i in range(3): 62 | s.add(P[i] + t * V[i] == p[i] + t * v[i]) 63 | 64 | assert s.check() == z3.sat 65 | print(f"Part 2: {s.model().eval(sum(P)).as_long()}") 66 | -------------------------------------------------------------------------------- /aoc2023/25.pl: -------------------------------------------------------------------------------- 1 | % Straightforward (but unsatisfyingly procedural) implementation of Karger's algorithm. 2 | % The union find datastructure uses Prolog's internal unification machinery, which 3 | % is efficient but quite hacky. 4 | 5 | main :- 6 | read_string(user_input, _, S), 7 | findall(X-Y, ( 8 | split_string(S, "\n", "\n", Lines), member(Line, Lines), 9 | split_string(Line, " ", ":", Ns), maplist(atom_string, [X|Ys], Ns), 10 | member(Y, Ys) 11 | ), Edges), 12 | setof(X, Y^(member(X-Y, Edges); member(Y-X, Edges)), Nodes), 13 | length(Goals, 4), maplist(=(solve(Edges, Nodes, Ans)), Goals), 14 | first_solution(Ans, Goals, []), writeln(Ans). 15 | 16 | solve(Edges, Nodes, Ans) => 17 | length(Nodes, N), % set_random(seed(0)), 18 | pairs_keys_values(Pairs, Nodes, VNodes), dict_pairs(D, _, Pairs), 19 | maplist({D}/[A-B, X-Y]>>(dict_create(S, _, [A-X, B-Y]), S :< D), Edges, VEdges), 20 | repeat, % set-up done, try contracting in random orders 21 | random_permutation(VEdges, Perm), contract(Perm, N), 22 | aggregate_all(count, (member(A-B, Perm), A \== B), 3) -> 23 | aggregate_all(count, ([A|_] = VNodes, member(B, VNodes), A == B), C), 24 | Ans is C * (N-C). 25 | 26 | contract(_, 2) => !. 27 | contract([X-Y|Tail], N) => 28 | (X == Y -> NN = N; NN is N-1, X = Y), 29 | contract(Tail, NN). 30 | -------------------------------------------------------------------------------- /aoc2023/25.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 20 3 | 4 | from util import * 5 | 6 | if len(sys.argv) == 1: 7 | sys.stdin = open(__file__.replace("py", "in")) 8 | 9 | res = 0 10 | 11 | adj = defaultdict(set) 12 | 13 | for l in lines(): 14 | a, r = l.split(": ") 15 | for b in r.split(): 16 | adj[a].add(b) 17 | adj[b].add(a) 18 | 19 | import sys 20 | from collections import defaultdict 21 | sys.setrecursionlimit(1<<30) 22 | 23 | def dinics(graph, s, t): 24 | assert s != t 25 | N = len(graph) 26 | cap = [defaultdict(int, adj) for adj in graph] 27 | 28 | def augment(i, of): 29 | if i == t: return of 30 | used, v = 0, valid[i] 31 | while v and used < of: 32 | j = v.pop(); c = cap[i][j] 33 | r = augment(j, min(of - used, c)) 34 | used += r 35 | cap[i][j] -= r 36 | cap[j][i] += r 37 | if 0 < r < c: v.append(j) 38 | return used 39 | 40 | flow = 0 41 | while True: 42 | valid = [[] for _ in range(N)] 43 | level = [-1] * N 44 | level[s], Q = 0, [s] 45 | for i in Q: 46 | if i == t: break 47 | for j, c in cap[i].items(): 48 | if level[j] == -1 and c: 49 | level[j] = level[i] + 1 50 | Q.append(j) 51 | if level[i] + 1 == level[j] and c: 52 | valid[i].append(j) 53 | else: return flow, cap 54 | flow += augment(s, float('inf')) 55 | 56 | all = set(adj) 57 | for v in adj.values(): 58 | all |= v 59 | 60 | idx = {a: i for i, a in enumerate(all)} 61 | G = [defaultdict(int) for _ in range(len(all))] 62 | for a, v in adj.items(): 63 | for b in v: 64 | G[idx[a]][idx[b]] = 1 65 | 66 | for a in all: 67 | for b in all: 68 | if a == b: continue 69 | 70 | flow, cap = dinics(G, idx[a], idx[b]) 71 | if flow == 3: 72 | Q = [idx[a]] 73 | seen = set(Q) 74 | for i in Q: 75 | for j, v in cap[i].items(): 76 | if v > 0 and j not in seen: 77 | seen.add(j) 78 | Q.append(j) 79 | 80 | prints(len(Q) * (len(all) - len(Q))) 81 | exit() 82 | break 83 | else: 84 | print("AAA") 85 | 86 | prints(res) 87 | -------------------------------------------------------------------------------- /aoc2023/README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2023 2 | 3 | I use Python ([PyPy](https://pypy.org/)) to compete for global leaderboard positions. 4 | 5 | The committed Python code is what I end up with after solving the 2nd part of each puzzle. 6 | 7 | It's probably not pretty... 8 | 9 | #### Prolog 10 | 11 | I also write some solutions in [SWI Prolog 9.1](https://www.swi-prolog.org/) for fun (not for the leaderboard). 12 | 13 | They are run from the command line like so: 14 | ```bash 15 | swipl -O -t halt -g main 04.pl < 04.in 16 | ``` 17 | -------------------------------------------------------------------------------- /aoc2023/get_input.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | """ 4 | Fetch AoC input as files. 5 | By Asger Hautop Drewsen: https://github.com/Tyilo 6 | """ 7 | 8 | import sys 9 | from pathlib import Path 10 | from typing import Optional 11 | 12 | import requests 13 | 14 | YEAR = 2023 15 | URL_PREFIX = f"https://adventofcode.com/{YEAR}" 16 | 17 | 18 | def validate_session(session): 19 | test_url = f"{URL_PREFIX}/settings" 20 | r = session.get(test_url) 21 | return r.status_code == 200 and r.url == test_url 22 | 23 | 24 | session_cookie: Optional[str] 25 | try: 26 | with open(".session", "r") as f: 27 | session_cookie = f.read().strip() 28 | except FileNotFoundError: 29 | session_cookie = None 30 | 31 | while True: 32 | if not session_cookie: 33 | session_cookie = input("Session cookie value: ").strip() 34 | with open(".session", "w") as f: 35 | f.write(session_cookie) 36 | 37 | session = requests.Session() 38 | session.headers["User-Agent"] = "get_input.py @ github.com/BarrensZeppelin/adventofcode" 39 | session.cookies.set("session", session_cookie, domain=".adventofcode.com", path="/") 40 | if validate_session(session): 41 | break 42 | 43 | print("That session cookie doesn't seem to work. Try again.") 44 | session_cookie = None 45 | 46 | 47 | for i in range(1, 26): 48 | path = Path(f"{i:02}.in") 49 | 50 | if path.exists(): 51 | continue 52 | 53 | r = session.get(f"{URL_PREFIX}/day/{i}/input") 54 | if r.ok: 55 | with path.open("wb") as fb: 56 | fb.write(r.content) 57 | print(f"Downloaded {path.name}") 58 | else: 59 | if r.status_code == 404: 60 | print(f"Day {i} not released yet") 61 | break 62 | else: 63 | sys.exit(f"Got unknown status code: {r.status_code}\n{r.text.strip()}") 64 | -------------------------------------------------------------------------------- /aoc2024/01.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 332/311 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | a, b = [], [] 12 | for l in lines(): 13 | x, y = ints(l) 14 | a.append(x) 15 | b.append(y) 16 | 17 | # C1 = Counter(a) 18 | C2 = Counter(b) 19 | 20 | # for x, y in zip(sorted(a), sorted(b)): 21 | # res += abs(y - x) 22 | 23 | for x in a: 24 | res += x * C2[x] 25 | 26 | 27 | # print(res) 28 | submit(res) 29 | -------------------------------------------------------------------------------- /aoc2024/02.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 50/115 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | """ 10 | L = sys.stdin.read().split("\n\n") 11 | 12 | tile, rotate, Point, sign 13 | """ 14 | 15 | res = 0 16 | 17 | def safe(X: list[int]): 18 | if sorted(X) == X or sorted(X, reverse=True) == X: 19 | for x, y in zip(X, X[1:]): 20 | if not 1 <= abs(x - y) <= 3: 21 | return False 22 | else: 23 | print(X) 24 | return True 25 | 26 | 27 | for l in lines(): 28 | X = ints(l) 29 | if safe(X) or any(safe(X[:i] + X[i + 1:]) for i in range(len(X))): 30 | res += 1 31 | 32 | submit(res) 33 | -------------------------------------------------------------------------------- /aoc2024/03.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 358/109 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | S = sys.stdin.read() 12 | en = True 13 | for i, c in enumerate(S): 14 | if S[i:].startswith("don't()"): 15 | en = False 16 | elif S[i:].startswith("do()"): 17 | en = True 18 | elif en and (match := re.match(r"mul\((\d+),(\d+)\)", S[i:])): 19 | a, b = map(int, match.groups()) 20 | res += a * b 21 | 22 | submit(res) 23 | -------------------------------------------------------------------------------- /aoc2024/04.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 103/43 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | """ 10 | L = sys.stdin.read().split("\n\n") 11 | 12 | tile, rotate, Point, sign 13 | """ 14 | 15 | res = 0 16 | 17 | G = lines() 18 | H = len(G) 19 | W = len(G[0]) 20 | 21 | for y in range(H): 22 | for x in range(W): 23 | if G[y][x] != "A" or y == 0 or y == H - 1 or x == 0 or x == W - 1: 24 | continue 25 | 26 | if {G[y-1][x-1], G[y+1][x+1]} == {"M", "S"} and {G[y-1][x+1], G[y+1][x-1]} == set("SM"): 27 | res += 1 28 | 29 | 30 | # for dx, dy in OCTDIR: 31 | # for i, c in enumerate("XMAS"): 32 | # nx = x + dx * i 33 | # ny = y + dy * i 34 | # if nx < 0 or nx >= W or ny < 0 or ny >= H or G[ny][nx] != c: 35 | # break 36 | # else: 37 | # res += 1 38 | 39 | submit(res) 40 | -------------------------------------------------------------------------------- /aoc2024/05.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 375/220 3 | from __future__ import annotations 4 | 5 | from functools import cmp_to_key 6 | 7 | from util import * 8 | 9 | replace_stdin() 10 | 11 | res = 0 12 | 13 | 14 | A, B = sys.stdin.read().split("\n\n") 15 | 16 | adj = defaultdict(list) 17 | for l in lines(A): 18 | a, b = ints(l) 19 | adj[a].append(b) 20 | 21 | # order, x = topsort(adj) 22 | # assert not x 23 | # 24 | # no = {k: i for i, k in enumerate(order)} 25 | 26 | 27 | def key(a, b): 28 | if b in adj[a]: 29 | return -1 30 | elif a in adj[b]: 31 | return 1 32 | else: 33 | return 0 34 | 35 | 36 | for l in lines(B): 37 | l = ints(l) 38 | for a, b in zip(l, l[1:]): 39 | if a in adj[b]: 40 | y = sorted(l, key=cmp_to_key(key)) 41 | res += y[len(y) // 2] 42 | break 43 | # else: 44 | # res += l[len(l)//2] 45 | 46 | 47 | submit(res) 48 | -------------------------------------------------------------------------------- /aoc2024/06.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 631/211 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | G = lines() 12 | H = len(G) 13 | W = len(G[0]) 14 | for y, l in enumerate(G): 15 | for x, c in enumerate(l): 16 | if c not in ".#": 17 | p = Point.of(x, y) 18 | print(p, x, y, c) 19 | 20 | G = [list(l) for l in G] 21 | 22 | class Done(Exception): 23 | pass 24 | 25 | def nxt(s): 26 | p, d = s 27 | np = p + DIR_NORTHNEG[d] 28 | if not 0 <= np.x < W or not 0 <= np.y < H: 29 | raise Done 30 | if G[np.y][np.x] == "#": 31 | d = (d - 1) % 4 32 | else: 33 | p = np 34 | 35 | return p, d 36 | 37 | for y, l in enumerate(G): 38 | for x, c in enumerate(l): 39 | if c in "^#": 40 | continue 41 | 42 | G[y][x] = "#" 43 | fg = fungraph((p, 1), nxt) 44 | try: 45 | fg[1<<30] 46 | res += 1 47 | except Done: 48 | pass 49 | G[y][x] = "." 50 | 51 | 52 | # d = 1 53 | # vis = {p} 54 | # while True: 55 | # print(p) 56 | # np = p + DIR_NORTHNEG[d] 57 | # if not 0 <= np.x < W or not 0 <= np.y < H: 58 | # break 59 | # if G[np.y][np.x] == "#": 60 | # d = (d - 1) % 4 61 | # else: 62 | # p = np 63 | # vis.add(p) 64 | 65 | submit(res) 66 | -------------------------------------------------------------------------------- /aoc2024/07.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 100/41 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | for l in lines(): 12 | b, *vs = ints(l) 13 | 14 | def f(v, i): 15 | if i == len(vs): 16 | return v == b 17 | 18 | return f(v + vs[i], i + 1) or f(v * vs[i], i + 1) or f(int(str(v) + str(vs[i])), i + 1) 19 | 20 | if f(vs[0], 1): 21 | res += b 22 | 23 | 24 | submit(res) 25 | -------------------------------------------------------------------------------- /aoc2024/08.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 61/31 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | G = lines() 12 | D = defaultdict(list) 13 | for y, l in enumerate(G): 14 | for x, c in enumerate(l): 15 | D[c].append(Point.of(x, y)) 16 | 17 | W = len(G[0]) 18 | H = len(G) 19 | 20 | uniq = set() 21 | for c, l in D.items(): 22 | if c == '.': 23 | continue 24 | for i, p1 in enumerate(l): 25 | for p2 in l[i + 1:]: 26 | d = p2 - p1 27 | from math import gcd 28 | g = gcd(d.x, d.y) 29 | d.x //= g 30 | d.y //= g 31 | 32 | for i in (-1, 1): 33 | pp = p1 34 | while True: 35 | if not 0 <= pp.x < W or not 0 <= pp.y < H: 36 | break 37 | uniq.add(pp) 38 | pp += d * i 39 | 40 | # for x in (p1 - d, p2 + d): 41 | # if 0 <= x.x < W and 0 <= x.y < H: 42 | # uniq.add(x) 43 | 44 | res = len(uniq) 45 | 46 | submit(res) 47 | -------------------------------------------------------------------------------- /aoc2024/09.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 286/243 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | L = list(map(int, input())) 12 | R = [-1] * sum(L) 13 | i = 0 14 | L2 = [] 15 | for id, d in enumerate(L): 16 | if id % 2 == 0: 17 | L2.append((id // 2, d)) 18 | else: 19 | L2.append((-1, d)) 20 | 21 | moved = set() 22 | for i in reversed(range(len(L2))): 23 | id, d = L2[i] 24 | if id == -1: 25 | continue 26 | 27 | if id in moved: 28 | continue 29 | 30 | moved.add(id) 31 | 32 | for j, (id2, d2) in enumerate(L2): 33 | if id2 != -1: 34 | continue 35 | if j >= i: 36 | break 37 | 38 | if d <= d2: 39 | L2[i] = (-1, d) 40 | L2[j] = (id, d) 41 | if d < d2: 42 | L2[j+1:j+1] = [(-1, d2-d)] 43 | break 44 | 45 | print(L2) 46 | # R = [] 47 | # for i, d in enumerate(L): 48 | # if i % 2 == 0: 49 | # R.extend([i//2] * d) 50 | # else: 51 | # while d: 52 | # j = len(L) - 1 53 | # if i == j: 54 | # break 55 | # if j % 2 == 1: 56 | # L.pop() 57 | # continue 58 | # d2 = L[j] 59 | # u = min(d, d2) 60 | # d -= u 61 | # L[j] -= u 62 | # R.extend([j//2] * u) 63 | # if not L[j]: 64 | # L.pop() 65 | # 66 | # print(R) 67 | i = 0 68 | for id, d in L2: 69 | if id == -1: 70 | i += d 71 | continue 72 | 73 | print(id, d, i) 74 | for j in range(i, i+d): 75 | res = res + j * id 76 | 77 | i += d 78 | 79 | submit(res) 80 | -------------------------------------------------------------------------------- /aoc2024/09_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | from __future__ import annotations 3 | 4 | from itertools import chain, islice 5 | 6 | from util import * 7 | 8 | replace_stdin() 9 | 10 | res = 0 11 | L = [*map(int, input())] 12 | N = sum(L) 13 | files, free = [], [] 14 | i = 0 15 | for j, d in enumerate(L): 16 | if j % 2: 17 | free.append((i, d)) 18 | else: 19 | files.append((i, j // 2, d)) 20 | i += d 21 | 22 | 23 | def checksum(R): 24 | return sum(i * id for i, id in enumerate(R) if id > 0) 25 | 26 | 27 | def part1(): 28 | f = (p for j, d in free for p in range(j, j + d) if p < i) 29 | R = [-1] * N 30 | for i, id, d in reversed(files): 31 | for p in islice(chain(f, range(i, i + d)), d): 32 | R[p] = id 33 | 34 | return R 35 | 36 | 37 | def part2(): 38 | R = [-1] * N 39 | for i, id, d in reversed(files): 40 | for fi, (j, d2) in enumerate(free): 41 | if j >= i: 42 | R[i : i + d] = [id] * d 43 | break 44 | if d2 >= d: 45 | R[j : j + d] = [id] * d 46 | free[fi] = (j + d, d2 - d) 47 | break 48 | return R 49 | 50 | 51 | print(f"Part 1: {checksum(part1())}") 52 | print(f"Part 2: {checksum(part2())}") 53 | -------------------------------------------------------------------------------- /aoc2024/10.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 129/87 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | G = Grid(lines()) 12 | 13 | adj = G.adj(pred=lambda a, b: int(G.at(b)) == int(G.at(a)) + 1) 14 | 15 | for sp in G.rev["0"]: 16 | def f(p): 17 | if G.at(p) == "9": 18 | return 1 19 | return sum(f(q) for q in adj[p]) 20 | 21 | res += f(sp) 22 | 23 | # d, _, _ = bfs(adj, sp) 24 | # res += sum(G.at(p) == "9" for p in d) 25 | 26 | 27 | submit(res) 28 | -------------------------------------------------------------------------------- /aoc2024/11.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 199/71 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | @cache 12 | def f(c, i): 13 | if i == 0: 14 | return 1 15 | Y = [] 16 | if c == 0: 17 | Y.append(1) 18 | elif len(str(c)) % 2 == 0: 19 | h = len(str(c)) // 2 20 | Y.append(int(str(c)[:h])) 21 | Y.append(int(str(c)[h:])) 22 | else: 23 | Y.append(c*2024) 24 | return sum(f(y, i-1) for y in Y) 25 | 26 | X = ints() 27 | # for _ in range(75): 28 | # Y = [] 29 | # for c in X: 30 | # n = 0 31 | # if c == 0: 32 | # Y.append(1) 33 | # elif len(str(c)) % 2 == 0: 34 | # h = len(str(c)) // 2 35 | # Y.append(int(str(c)[:h])) 36 | # Y.append(int(str(c)[h:])) 37 | # else: 38 | # Y.append(c*2024) 39 | # 40 | # X = Y 41 | 42 | 43 | res = sum(f(x, 75) for x in X) 44 | submit(res) 45 | -------------------------------------------------------------------------------- /aoc2024/12.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 164/27 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | G = Grid(lines()) 12 | adj = G.adj(pred=lambda x, y: G(x) == G(y)) 13 | 14 | vis = set() 15 | for y, l in enumerate(G): 16 | for x, c in enumerate(l): 17 | p = Point.of(x, y) 18 | if p in vis: 19 | continue 20 | 21 | D, Q, _ = bfs(adj, p) 22 | perim = 0 23 | p2 = set() 24 | for p in Q: 25 | p2.update(((p + d), d) for d in DIR if (p + d) not in D) 26 | perim += sum((p + d) not in D for d in DIR) 27 | vis.update(Q) 28 | # res += perim * len(Q) 29 | 30 | sides = 0 31 | for pp in list(p2): 32 | if pp not in p2: 33 | continue 34 | 35 | p, d = pp 36 | sides += 1 37 | N = [p] 38 | for p in N: 39 | for nd in DIR: 40 | np = p + nd 41 | npp = (np, d) 42 | if npp in p2: 43 | p2.remove(npp) 44 | N.append(np) 45 | 46 | res += sides * len(Q) 47 | 48 | 49 | 50 | submit(res) 51 | -------------------------------------------------------------------------------- /aoc2024/13.pl: -------------------------------------------------------------------------------- 1 | :- use_module([library(clpq), library(yall), library(apply_macros)]). 2 | 3 | main :- 4 | read_string(user_input, _, S), re_split("\n\n", S, Sections), 5 | maplist([S, L]>>re_foldl([_{0:N}, [N|Ns], Ns]>>true, "\\d+"/t, S, L, [], []), Sections, Inputs), 6 | maplist({Inputs}/[X, Ans]>> 7 | aggregate_all(sum(3 * A + B), ( 8 | member([Ax, Ay, Bx, By, Px, Py], Inputs), { 9 | A >= 0, B >= 0, 10 | Px + X = Ax * A + Bx * B, 11 | Py + X = Ay * A + By * B 12 | }, integer(A), integer(B) 13 | ), Ans), [0, 10^13], Ans), 14 | format("Part 1: ~d~nPart 2: ~d~n", Ans). 15 | -------------------------------------------------------------------------------- /aoc2024/13.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 149/29 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | import z4 as z3 12 | 13 | s = z3.Optimize() 14 | L = sys.stdin.read().split("\n\n") 15 | P = z3.IntVector("P", 2) 16 | s.add(P[0] >= 0) 17 | s.add(P[1] >= 0) 18 | for sc in L: 19 | a, b, p = map(ints, lines(sc)) 20 | assert len(a) == 2 == len(b) == len(p) 21 | p[0] += 10000000000000 22 | p[1] += 10000000000000 23 | 24 | s.push() 25 | s.add(P[0] * a[0] + P[1] * b[0] == p[0]) 26 | s.add(P[0] * a[1] + P[1] * b[1] == p[1]) 27 | s.minimize(P[0] * 3 + P[1]) 28 | 29 | if s.check() == z3.sat: 30 | m = s.model() 31 | res += m.eval(P[0] * 3 + P[1]).as_long() 32 | 33 | s.pop() 34 | 35 | 36 | submit(res) 37 | -------------------------------------------------------------------------------- /aoc2024/14.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 450/1325 3 | from __future__ import annotations 4 | 5 | import z4 as z3 6 | from util import * 7 | 8 | replace_stdin() 9 | 10 | 11 | res = 0 12 | 13 | W, H = 101, 103 14 | # W, H = 11, 7 15 | C = Counter() 16 | R = [] 17 | for l in lines(): 18 | px, py, vx, vy = ints(l) 19 | p = Point([px, py]) 20 | v = Point([vx, vy]) 21 | R.append((p, v)) 22 | 23 | # def p_at(t: int): 24 | # D = [p + v*t for p, v in R] 25 | # for p in D: 26 | # p.x %= W 27 | # p.y %= H 28 | # 29 | # print() 30 | # print(t) 31 | # print_coords([(p.x, p.y) for p in D], ".") 32 | 33 | 34 | # s = z3.Solver() 35 | # T = z3.Int("T") 36 | # s.add(T >= 0, T < 10**12) 37 | # for i, (p1, v1) in enumerate(R): 38 | # for j, (p2, v2) in enumerate(R): 39 | # if j >= i: 40 | # break 41 | # 42 | # print(i, j) 43 | # for p3, v3 in R[:j]: 44 | # with s: 45 | # s.add((p1.x + v1.x * T) % W + 1 == (p2.x + v2.x * T) % W) 46 | # s.add((p1.y + v1.y * T) % H + 1 == (p2.y + v2.y * T) % H) 47 | # s.add((p2.x + v2.x * T) % W + 1 == (p3.x + v3.x * T) % W) 48 | # s.add((p2.y + v2.y * T) % H + 1 == (p3.y + v3.y * T) % H) 49 | # 50 | # if s.check() == z3.sat: 51 | # t = s.model()[T].as_long() 52 | # p_at(t) 53 | 54 | 55 | for i in range(10**8): 56 | for j, (p, v) in enumerate(R): 57 | p += v 58 | p.x %= W 59 | p.y %= H 60 | R[j] = (p, v) 61 | 62 | 63 | V = {p for p, v in R} 64 | # adj = {p: [p2 for p2 in p.neigh(OCTDIR) if p2 in V] for p in V} 65 | # adj2 = {p: [p2 for p2 in V if (p - p2).manh_dist() < 5] for p in V} 66 | # comp = conn_components(adj2) 67 | if len(V) == len(R): 68 | print(i+1) 69 | print_coords([(p.x, p.y) for p, v in R], ".") 70 | break 71 | 72 | exit() 73 | C2 = Counter() 74 | h, w = H//2, W//2 75 | for p, c in C.items(): 76 | if p.x == w or p.y == h: 77 | continue 78 | C2[(p.x//(w+1), p.y//(h+1))] += c 79 | 80 | print_coords({p: str(v) for p, v in C.items()}, ".") 81 | print(C, C2) 82 | 83 | res = 1 84 | for c in C2.values(): 85 | res *= c 86 | 87 | submit(res) 88 | -------------------------------------------------------------------------------- /aoc2024/15_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | from __future__ import annotations 3 | 4 | from util import * 5 | 6 | replace_stdin() 7 | 8 | D = dict(zip(">^ int: 15 | G = Grid(M) 16 | sp = G.rev["@"][0] 17 | 18 | for d in map(D.__getitem__, I): 19 | 20 | def adj(p: Point[int]): 21 | if (c := G(p)) in "O[]": 22 | yield p + d 23 | if c == "[": 24 | yield p + (1, 0) 25 | if c == "]": 26 | yield p - (1, 0) 27 | 28 | push, _, _ = bfs(adj, sp + d) 29 | if any(G(p) == "#" for p in push): 30 | continue 31 | 32 | push = {p: G(p) for p in push if G(p) in "O[]"} 33 | for p in push: 34 | G[p.y][p.x] = "." 35 | for p, c in push.items(): 36 | p += d 37 | G[p.y][p.x] = c 38 | 39 | sp += d 40 | 41 | return sum(p.x + 100 * p.y for p in G.points() if G(p) in "O[") 42 | 43 | 44 | print(f"Part 1: {solve(G[:])}") 45 | 46 | replace = dict(zip("#O.@", "## [] .. @.".split(), strict=True)) 47 | print(f"Part 2: {solve([''.join(replace[c] for c in l) for l in G])}") 48 | -------------------------------------------------------------------------------- /aoc2024/16.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 161/39 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | G = Grid(lines()) 12 | 13 | s = G.rev["S"][0] 14 | e = G.rev["E"][0] 15 | 16 | def adj(p): 17 | p, d = p 18 | 19 | np = p + DIR_NORTHNEG[d] 20 | if G(np) in ".E": 21 | yield (np, d), 1 22 | 23 | yield (p, (d + 1) % 4), 1000 24 | yield (p, (d - 1) % 4), 1000 25 | # for i in range(4): 26 | # d = (d + 1) % 4 27 | # yield 28 | 29 | dists, prevs = dijkstra(adj, (s, 0)) 30 | 31 | res = min(d for p, d in dists.items() if p[0] == e) 32 | md = res 33 | 34 | _, Q, _ = bfs(prevs, *((e, i) for i in range(4))) 35 | 36 | res = len({p for p, _ in Q}) 37 | 38 | submit(res) 39 | -------------------------------------------------------------------------------- /aoc2024/17.pl: -------------------------------------------------------------------------------- 1 | :- use_module(library(clpfd)). 2 | :- dynamic prog/2. 3 | 4 | eval(I, R, O) :- 5 | number(I), J is I+1, K is I+2, 6 | (prog(I, P), prog(J, Q) -> inst(P, K, Q, R, O); O = []). 7 | 8 | combo(X, _, X) :- X < 4, !. 9 | combo(4, r(A, _, _), A). 10 | combo(5, r(_, B, _), B). 11 | combo(6, r(_, _, C), C). 12 | 13 | inst(0, I, Q, R, O) => 14 | R = r(A, B, C), 15 | combo(Q, R, K), NA #= A >> K, eval(I, r(NA, B, C), O). 16 | inst(1, I, Q, r(A, B, C), O) => NB #= B xor Q, eval(I, r(A, NB, C), O). 17 | inst(2, I, Q, R, O) => 18 | R = r(A, _, C), 19 | combo(Q, R, K), NB #= K mod 8, eval(I, r(A, NB, C), O). 20 | inst(3, I, Q, R, O) => 21 | R = r(A, _, _), 22 | (A #= 0, eval(I, R, O) ; A #> 0, eval(Q, R, O)). 23 | inst(4, I, _, r(A, B, C), O) => NB #= B xor C, eval(I, r(A, NB, C), O). 24 | inst(5, I, Q, R, O) => combo(Q, R, K), J #= K mod 8, O = [J|Tl], eval(I, R, Tl). 25 | inst(6, I, Q, R, O) => 26 | R = r(A, _, C), 27 | combo(Q, R, K), NB #= A >> K, eval(I, r(A, NB, C), O). 28 | inst(7, I, Q, R, O) => 29 | R = r(A, B, _), 30 | combo(Q, R, K), NC #= A >> K, eval(I, r(A, B, NC), O). 31 | 32 | main :- 33 | read_string(user_input, _, S), 34 | re_foldl([_{0:N}, [N|Ns], Ns]>>true, "\\d+"/t, S, [A, B, C | Prog], [], []), 35 | foreach(nth0(I, Prog, V), assertz(prog(I, V))), 36 | eval(0, r(A, B, C), O), 37 | MA #>= 0, 38 | eval(0, r(MA, 0, 0), Prog), 39 | labeling([min(MA), bisect], [MA]), 40 | format("Part 1: ~w~nPart 2: ~d~n", [O, MA]). 41 | -------------------------------------------------------------------------------- /aoc2024/17_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | from __future__ import annotations 3 | 4 | import z4 as z3 5 | 6 | from util import * 7 | 8 | replace_stdin() 9 | 10 | res = 0 11 | 12 | A, B, C, *P = ints() 13 | s = z3.Solver() 14 | init_A = z3.BitVec("IA", 64) 15 | s.add(init_A >= 0) 16 | 17 | 18 | def check(): 19 | r = s.check() 20 | assert r != z3.unknown, r 21 | return r == z3.sat 22 | 23 | 24 | def g(v: int): 25 | with s: 26 | s.add(init_A <= v) 27 | return check() 28 | 29 | 30 | def f(ip: int, j: int, A, B, C): 31 | while 0 <= ip < len(P): 32 | p, op = P[ip : ip + 2] 33 | cop = (0, 1, 2, 3, A, B, C, 0)[op] 34 | 35 | match p: 36 | case 0: A >>= cop 37 | case 1: B ^= op 38 | case 2: B = cop % 8 39 | case 4: B ^= C 40 | case 6: B = A >> cop 41 | case 7: C = A >> cop 42 | 43 | case 3: 44 | with s: 45 | s.add(A != 0) 46 | if check(): 47 | f(op, j, A, B, C) 48 | 49 | with s: 50 | s.add(A == 0) 51 | if check(): 52 | f(ip + 2, j, A, B, C) 53 | 54 | return 55 | 56 | case 5: 57 | if j < len(P): 58 | with s: 59 | s.add(cop % 8 == P[j]) 60 | if check(): 61 | f(ip + 2, j + 1, A, B, C) 62 | 63 | return 64 | 65 | ip += 2 66 | 67 | # Solver + binary search turns out to be several times faster than Optimize w. minimize 68 | prints(binary_search(g, 0)) 69 | sys.exit(0) 70 | 71 | 72 | f(0, 0, init_A, 0, 0) 73 | -------------------------------------------------------------------------------- /aoc2024/18.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 246/298 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | W, H = 71, 71 12 | ps = [] 13 | for l in lines(): 14 | a, b = ints(l) 15 | ps.append(Point.of(a, b)) 16 | 17 | def blocked(i): 18 | G = Grid.empty(W, H, ".") 19 | for p in ps[:i]: 20 | G.set(p, "#") 21 | adj = G.adj(pred=lambda x, y: G(x) != "#" and G(y) != "#") 22 | dists, _, _ = bfs(lambda p: adj.get(p, []), Point.of(0, 0)) 23 | return Point((W-1, H-1)) not in dists 24 | 25 | res = binary_search(blocked, 0, len(ps)+1)-1 26 | print(res, ps[res]) 27 | res = ",".join(map(str, ps[res])) 28 | submit(res) 29 | -------------------------------------------------------------------------------- /aoc2024/18_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | from __future__ import annotations 3 | 4 | from util import * 5 | 6 | replace_stdin() 7 | 8 | res = 0 9 | 10 | W, H = 71, 71 11 | P = Point 12 | ps = [P(ints(l)) for l in lines()] 13 | G = Grid.empty(W, H, ".") 14 | first = set(ps[:1024]) 15 | sp, ep = P.of(0, 0), P.of(W - 1, H - 1) 16 | D = bfs(G.adj(pred=lambda x, y: not first & {x, y}), sp)[0] 17 | print(f"Part 1: {D[ep]}") 18 | 19 | uf = UF[P[int]]() 20 | sps = set(ps) 21 | for p in chain(set(G.points()) - sps, reversed(ps)): 22 | sps.discard(p) 23 | for d in p.neigh(): 24 | if G.inbounds(d) and d not in sps: 25 | uf.join(p, d) 26 | 27 | if uf.find(sp) == uf.find(ep): 28 | print(f"Part 2: {','.join(map(str, p))}") 29 | break 30 | -------------------------------------------------------------------------------- /aoc2024/19.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 120/64 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | A, B = sys.stdin.read().split("\n\n") 12 | pats = A.split(", ") 13 | 14 | for l in lines(B): 15 | print(l, pats) 16 | @cache 17 | def f(i: int): 18 | if i == len(l): 19 | return 1 20 | 21 | r = 0 22 | for p in pats: 23 | if l[i:i+len(p)] == p and f(i+len(p)): 24 | r += f(i+len(p)) 25 | 26 | return r 27 | 28 | res += f(0) 29 | 30 | 31 | submit(res) 32 | -------------------------------------------------------------------------------- /aoc2024/20.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 116/81 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | G = Grid(lines()) 12 | sp, ep = G.crev["S"][0], G.crev["E"][0] 13 | 14 | adj = G.adj(pred=lambda x, y: "#" not in (G(x), G(y))) 15 | SD = bfs(adj, sp)[0] 16 | ED = bfs(adj, ep)[0] 17 | 18 | fdist = SD[ep] 19 | 20 | C = Counter() 21 | nonw = [p for p in G.points() if G(p) != "#"] 22 | for p in G.crev["."] + [sp, ep]: 23 | for p2 in nonw: 24 | d2 = (p - p2).manh_dist() 25 | if d2 > 20: 26 | continue 27 | nd = SD[p] + d2 + ED[p2] 28 | # print(p, nd, p2) 29 | saved = fdist - nd 30 | if saved > 0: 31 | C[saved] += 1 32 | 33 | if saved >= 100: 34 | res += 1 35 | 36 | print(sorted(C.items())) 37 | 38 | submit(res) 39 | -------------------------------------------------------------------------------- /aoc2024/21_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | from __future__ import annotations 3 | 4 | from util import * 5 | 6 | replace_stdin() 7 | 8 | Gs = [Grid(s.split(",")) for s in ("789,456,123, 0A", " ^A,")] 9 | 10 | part1 = part2 = 0 11 | 12 | 13 | @cache 14 | def sequence_cost(s: str, i: int, gi: int = 0) -> int: 15 | if i == 0: 16 | return len(s) 17 | 18 | G = Gs[gi] 19 | sp = p = G.crev["A"][0] 20 | cost = 0 21 | for c in s: 22 | d = G.crev[c][0] - p 23 | xs = (">" if d.x > 0 else "<") * abs(d.x) 24 | ys = ("v" if d.y > 0 else "^") * abs(d.y) 25 | 26 | opts: list[str] = [] 27 | if G(p + (d.x, 0)) != " ": 28 | opts.append(xs + ys + "A") 29 | if G(p + (0, d.y)) != " ": 30 | opts.append(ys + xs + "A") 31 | 32 | cost += min(sequence_cost(s, i - 1, 1) for s in opts) 33 | p += d 34 | assert p == sp 35 | return cost 36 | 37 | 38 | for l in lines(): 39 | code = int(l[:-1]) 40 | part1 += sequence_cost(l, 3) * code 41 | part2 += sequence_cost(l, 26) * code 42 | 43 | 44 | print(f"Part 1: {part1}\nPart 2: {part2}") 45 | -------------------------------------------------------------------------------- /aoc2024/22.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 130/290 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | def mix(x, y): 12 | return x ^ y 13 | 14 | def prune(x): 15 | return x % 16777216 16 | 17 | M = 16777216 18 | c = Counter() 19 | for l in lines(): 20 | ix = x = int(l) 21 | # x = 123 22 | xs = [x] 23 | for _ in range(2000): 24 | x = mix(x, x*64) % M 25 | x = mix(x, x//32) % M 26 | x = mix(x, x*2048) % M 27 | xs.append(x) 28 | 29 | # res += x 30 | xs = [x % 10 for x in xs] 31 | # print(xs[:10]) 32 | d = [b - a for a, b in zip(xs, xs[1:])] 33 | # print(xs, d) 34 | b = Counter() 35 | for i, t in enumerate(windows(d, 4)): 36 | t = tuple(t) 37 | p = xs[i + 4] 38 | if t not in b: 39 | b[t] = p 40 | 41 | # print(b[(0, 0, 0, 8)]) 42 | for t, p in b.items(): 43 | c[t] += p 44 | assert len(d) == 2000 45 | 46 | res = max(c.values()) 47 | # res = max(c.items(), key=lambda t: t[1]) 48 | 49 | submit(res) 50 | -------------------------------------------------------------------------------- /aoc2024/22_post.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | from __future__ import annotations 3 | 4 | from itertools import accumulate, pairwise 5 | 6 | from util import * 7 | 8 | replace_stdin() 9 | 10 | M = 16777216 11 | 12 | 13 | def f(x: int, _) -> int: 14 | x ^= x * 64 15 | x %= M 16 | x ^= x // 32 17 | x %= M 18 | x ^= x * 2048 19 | return x % M 20 | 21 | 22 | p1 = 0 23 | c = Counter[int]() 24 | for x in ints(): 25 | xs = list(accumulate(range(2000), f, initial=x)) 26 | p1 += xs[-1] 27 | prices = [x % 10 for x in xs] 28 | diffs = [b - a for a, b in pairwise(prices)] 29 | c.update({tuple(s): p for s, p in zip(windows(diffs, 4)[::-1], prices[::-1], strict=False)}) 30 | 31 | print(f"Part 1: {p1}\nPart 2: {max(c.values())}") 32 | -------------------------------------------------------------------------------- /aoc2024/23.pl: -------------------------------------------------------------------------------- 1 | :- dynamic edge/2. 2 | 3 | main :- 4 | read_string(user_input, _, Input), split_string(Input, "\n", "\n", Lines), 5 | forall(member(Line, Lines), ( 6 | split_string(Line, "-", "", [A, B]), 7 | assertz(edge(A, B)), assertz(edge(B, A)) 8 | )), 9 | aggregate_all(count, ( 10 | edge(A, B), edge(A, C), edge(B, C), 11 | once((member(S, [A, B, C]), string_code(1, S, 0't))) 12 | ), Count), 13 | P1 is Count // 6, 14 | format("Part 1: ~d\n", [P1]). 15 | -------------------------------------------------------------------------------- /aoc2024/23.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 219/229 3 | from __future__ import annotations 4 | 5 | import networkx as nx 6 | from util import * 7 | 8 | replace_stdin() 9 | 10 | res = 0 11 | 12 | e = [l.split("-") for l in lines()] 13 | adj = make_adj(e, both=True) 14 | 15 | V = set() 16 | for a in adj: 17 | for b, c in combinations(adj[a], 2): 18 | if b in adj[c]: 19 | t = tuple(sorted([a, b, c])) 20 | if t not in V: 21 | V.add(t) 22 | if any(x[0] == "t" for x in t): 23 | res += 1 24 | 25 | 26 | G = nx.Graph() 27 | for a, b in e: 28 | G.add_edge(a, b) 29 | G.add_edge(b, a) 30 | 31 | best = max(nx.find_cliques(G), key=len) 32 | 33 | submit(",".join(sorted(best))) 34 | 35 | 36 | # uf = UF() 37 | # for a, b in e: 38 | # uf.join(a, b) 39 | # 40 | # for s in uf.sets(): 41 | # print(s) 42 | # if len(s) == 3 and any(x[0] == "t" for x in s): 43 | # res += 1 44 | 45 | 46 | 47 | # submit(res) 48 | -------------------------------------------------------------------------------- /aoc2024/25.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | # 91/74 3 | from __future__ import annotations 4 | 5 | from util import * 6 | 7 | replace_stdin() 8 | 9 | res = 0 10 | 11 | keys = [] 12 | locks = [] 13 | L = sys.stdin.read().split("\n\n") 14 | for l in L: 15 | l = lines(l) 16 | key = l[0].count("#") == 5 17 | if not key: 18 | l.reverse() 19 | 20 | hs = [] 21 | for x in range(5): 22 | y = 0 23 | while y < len(l) and l[y][x] == "#": 24 | y += 1 25 | hs.append(y) 26 | 27 | (keys if key else locks).append(hs) 28 | 29 | 30 | print(keys, locks) 31 | for k in keys: 32 | for l in locks: 33 | if all(a + b <= 7 for a, b in zip(k, l)): 34 | res += 1 35 | 36 | submit(res) 37 | -------------------------------------------------------------------------------- /aoc2024/template.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env pypy3 2 | from __future__ import annotations 3 | 4 | from util import * 5 | 6 | replace_stdin() 7 | 8 | """ 9 | L = sys.stdin.read().split("\n\n") 10 | 11 | Grid, tile, rotate, Point, sign 12 | """ 13 | 14 | res = 0 15 | 16 | for l in lines(): 17 | pass 18 | 19 | 20 | submit(res) 21 | --------------------------------------------------------------------------------