├── .gitignore ├── README.md ├── day_01 ├── day_01_p1.py └── day_01_p2.py ├── day_02 ├── day_02_p1.py └── day_02_p2.py ├── day_03 ├── day_03_p1.py └── day_03_p2.py ├── day_04 ├── day_04_p1.py └── day_04_p2.py ├── day_05 ├── day_05_p1.py └── day_05_p2.py ├── day_06 ├── day_06_p1.py └── day_06_p2.py ├── day_07 ├── day_07_p1.py └── day_07_p2.py ├── day_08 ├── day_08_p1.py └── day_08_p2.py ├── day_09 ├── day_09_p1.py └── day_09_p2.py ├── day_10 ├── day_10_p1.py └── day_10_p2.py ├── day_11 ├── day_11_p1.py └── day_11_p2.py ├── day_12 ├── day_12_p1.py └── day_12_p2.py ├── day_13 ├── day_13_p1.py └── day_13_p2.py ├── day_14 ├── day_14_p1.py └── day_14_p2.py ├── day_15 ├── day_15_p1.py └── day_15_p2.py ├── day_16 ├── day_16 │ ├── day_16.cpp │ ├── day_16_p1 copy.py │ └── day_16_p2.cpp ├── day_16_p1.py └── day_16_p2.py ├── day_18 ├── day_18_p1.py └── day_18_p2.py ├── day_20 ├── day_20_p1.py └── day_20_p2.py ├── day_21 ├── day_21_p1.py └── day_21_p2.py ├── day_22 ├── day_22_p1.py └── day_22_p2.py ├── day_23 ├── day_23_p1.py └── day_23_p2.py ├── day_24 ├── day_24_p1.py └── day_24_p2.py ├── day_25 ├── day_25_p1.py └── day_25_p2.py └── new_day.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.in 2 | .vscode/ 3 | *.exe 4 | *.out 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Advent of Code 2022 Solutions 2 | 3 | This repository contains my solutions to [Advent of Code 2022](https://adventofcode.com/2022)! 4 | 5 | I'm making videos that explain my solutions to the puzzles! Watch my playlist here: https://www.youtube.com/playlist?list=PLsqh-jhhTL2-cJiIRlju1sQj0i_Sk9zmd 6 | 7 | 8 | ## Structure 9 | 10 | Days' code are contained in `/day_xx` and source files are named `day_xx_px`. 11 | 12 | 13 | ## Requirements 14 | 15 | I'm experimenting around with being more efficient this year, so I'm going to use the [`advent-of-code-data`](https://github.com/wimglenn/advent-of-code-data) library by [Wim Glenn](https://github.com/wimglenn) to streamline the data retrieval and submission process. Therefore, if you'd like to run my code, install `advent-of-code-data` with 16 | ``` 17 | pip install advent-of-code-data 18 | ``` 19 | first. 20 | -------------------------------------------------------------------------------- /day_01/day_01_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_01.in") as fin: 2 | raw_data = fin.read().strip() 3 | parts = raw_data.split("\n\n") 4 | 5 | 6 | loads = [] 7 | for part in parts: 8 | foods = map(int, part.split()) 9 | loads.append(sum(foods)) 10 | 11 | 12 | print(max(loads)) 13 | -------------------------------------------------------------------------------- /day_01/day_01_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_01.in") as fin: 2 | raw_data = fin.read().strip() 3 | parts = raw_data.split("\n\n") 4 | 5 | 6 | loads = [] 7 | for part in parts: 8 | foods = map(int, part.split()) 9 | loads.append(sum(foods)) 10 | 11 | 12 | print(sum(sorted(loads)[-3:])) 13 | -------------------------------------------------------------------------------- /day_02/day_02_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_02.in") as fin: 2 | lines = fin.read().strip().split("\n") 3 | 4 | 5 | play_map = {"A": 0, "B": 1, "C": 2, 6 | "X": 0, "Y": 1, "Z": 2} 7 | 8 | score_key = [1, 2, 3] 9 | 10 | score = 0 11 | for line in lines: 12 | opp, me = [play_map[i] for i in line.split()] 13 | 14 | # Win 15 | if (me - opp) % 3 == 1: 16 | score += 6 17 | elif (me == opp): 18 | score += 3 19 | 20 | score += score_key[me] 21 | 22 | 23 | print(score) 24 | -------------------------------------------------------------------------------- /day_02/day_02_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_02.in") as fin: 2 | lines = fin.read().strip().split("\n") 3 | 4 | 5 | # X, Y, Z values represent offset in what to play 6 | play_map = {"A": 0, "B": 1, "C": 2, 7 | "X": -1, "Y": 0, "Z": 1} 8 | 9 | score_key = [1, 2, 3] 10 | 11 | score = 0 12 | for line in lines: 13 | opp, result = [play_map[i] for i in line.split()] 14 | 15 | score += (result + 1) * 3 16 | score += score_key[(opp + result) % 3] 17 | 18 | 19 | print(score) 20 | -------------------------------------------------------------------------------- /day_03/day_03_p1.py: -------------------------------------------------------------------------------- 1 | from string import ascii_lowercase, ascii_uppercase 2 | 3 | key = ascii_lowercase + ascii_uppercase 4 | print(key) 5 | 6 | with open("./day_03.in") as fin: 7 | data = fin.read().strip() 8 | 9 | 10 | ans = 0 11 | 12 | lines = data.split("\n") 13 | for line in lines: 14 | n = len(line) 15 | a = line[:(n//2)] # First compartment 16 | b = line[(n//2):] # Second compartment 17 | 18 | for i, c in enumerate(key): 19 | if c in a and c in b: 20 | ans += key.index(c) + 1 21 | 22 | print(ans) 23 | -------------------------------------------------------------------------------- /day_03/day_03_p2.py: -------------------------------------------------------------------------------- 1 | from string import ascii_lowercase, ascii_uppercase 2 | 3 | key = ascii_lowercase + ascii_uppercase 4 | 5 | with open("./day_03.in") as fin: 6 | data = fin.read().strip() 7 | 8 | 9 | ans = 0 10 | 11 | lines = data.split("\n") 12 | for i in range(0, len(lines), 3): 13 | a = lines[i:(i + 3)] # The group of three Elves' rucksacks 14 | 15 | for i, c in enumerate(key): 16 | if all([c in li for li in a]): 17 | ans += key.index(c) + 1 18 | 19 | print(ans) 20 | -------------------------------------------------------------------------------- /day_04/day_04_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_04.in") as fin: 2 | lines = fin.read().strip().split() 3 | 4 | 5 | ans = 0 6 | for line in lines: 7 | elves = line.split(",") 8 | ranges = [list(map(int, elf.split("-"))) for elf in elves] 9 | 10 | a, b = ranges[0] 11 | c, d = ranges[1] 12 | 13 | if a <= c and b >= d or a >= c and b <= d: 14 | ans += 1 15 | 16 | 17 | print(ans) 18 | -------------------------------------------------------------------------------- /day_04/day_04_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_04.in") as fin: 2 | lines = fin.read().strip().split() 3 | 4 | 5 | ans = 0 6 | for line in lines: 7 | elves = line.split(",") 8 | ranges = [list(map(int, elf.split("-"))) for elf in elves] 9 | 10 | a, b = ranges[0] 11 | c, d = ranges[1] 12 | 13 | if not (b < c or a > d): 14 | ans += 1 15 | 16 | 17 | print(ans) 18 | -------------------------------------------------------------------------------- /day_05/day_05_p1.py: -------------------------------------------------------------------------------- 1 | N = 9 2 | drawing_lines = 8 3 | 4 | with open("./day_05.in") as fin: 5 | parts = fin.read()[:-1].split("\n\n") 6 | drawing = parts[0].split("\n") 7 | stacks = [[] for _ in range(N)] 8 | 9 | for i in range(drawing_lines): 10 | line = drawing[i] 11 | crates = line[1::4] 12 | for s in range(len(crates)): 13 | if crates[s] != " ": 14 | stacks[s].append(crates[s]) 15 | 16 | # Reverse all stacks 17 | stacks = [stack[::-1] for stack in stacks] 18 | 19 | # Move things around 20 | for line in parts[1].split("\n"): 21 | tokens = line.split(" ") 22 | n, src, dst = map(int, [tokens[1], tokens[3], tokens[5]]) 23 | src -= 1 24 | dst -= 1 25 | 26 | for _ in range(n): 27 | pop = stacks[src].pop() 28 | stacks[dst].append(pop) 29 | 30 | 31 | tops = [stack[-1] for stack in stacks] 32 | print("".join(tops)) 33 | -------------------------------------------------------------------------------- /day_05/day_05_p2.py: -------------------------------------------------------------------------------- 1 | N = 9 2 | drawing_lines = 8 3 | 4 | with open("./day_05.in") as fin: 5 | parts = fin.read()[:-1].split("\n\n") 6 | drawing = parts[0].split("\n") 7 | stacks = [[] for _ in range(N)] 8 | 9 | for i in range(drawing_lines): 10 | line = drawing[i] 11 | crates = line[1::4] 12 | for s in range(len(crates)): 13 | if crates[s] != " ": 14 | stacks[s].append(crates[s]) 15 | 16 | # Reverse all stacks 17 | stacks = [stack[::-1] for stack in stacks] 18 | 19 | 20 | # Move things around 21 | for line in parts[1].split("\n"): 22 | tokens = line.split(" ") 23 | n, src, dst = map(int, [tokens[1], tokens[3], tokens[5]]) 24 | src -= 1 25 | dst -= 1 26 | 27 | stacks[dst].extend(stacks[src][-n:]) 28 | stacks[src] = stacks[src][:-n] 29 | 30 | 31 | tops = [stack[-1] for stack in stacks] 32 | print("".join(tops)) 33 | -------------------------------------------------------------------------------- /day_06/day_06_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_06.in") as fin: 2 | data = fin.read().strip() 3 | 4 | i = 0 5 | while True: 6 | s = data[i:(i+4)] 7 | if len(set(list(s))) == 4: 8 | print(i + 4) 9 | break 10 | 11 | i += 1 12 | -------------------------------------------------------------------------------- /day_06/day_06_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_06.in") as fin: 2 | data = fin.read().strip() 3 | 4 | i = 0 5 | while True: 6 | s = data[i:(i+14)] 7 | if len(set(list(s))) == 14: 8 | print(i + 14) 9 | break 10 | 11 | i += 1 12 | -------------------------------------------------------------------------------- /day_07/day_07_p1.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from functools import lru_cache 3 | from pprint import pprint 4 | 5 | with open("./day_07.in") as fin: 6 | blocks = ("\n" + fin.read().strip()).split("\n$ ")[1:] 7 | 8 | 9 | path = [] 10 | 11 | dir_sizes = defaultdict(int) 12 | children = defaultdict(list) 13 | 14 | 15 | def parse(block): 16 | lines = block.split("\n") 17 | command = lines[0] 18 | outputs = lines[1:] 19 | 20 | parts = command.split(" ") 21 | op = parts[0] 22 | if op == "cd": 23 | if parts[1] == "..": 24 | path.pop() 25 | else: 26 | path.append(parts[1]) 27 | 28 | return 29 | 30 | abspath = "/".join(path) 31 | assert op == "ls" 32 | 33 | sizes = [] 34 | for line in outputs: 35 | if not line.startswith("dir"): 36 | sizes.append(int(line.split(" ")[0])) 37 | else: 38 | dir_name = line.split(" ")[1] 39 | children[abspath].append(f"{abspath}/{dir_name}") 40 | 41 | dir_sizes[abspath] = sum(sizes) 42 | 43 | 44 | for block in blocks: 45 | parse(block) 46 | 47 | 48 | # Do DFS 49 | @lru_cache(None) # Cache may not be strictly necessary 50 | def dfs(abspath): 51 | size = dir_sizes[abspath] 52 | for child in children[abspath]: 53 | size += dfs(child) 54 | return size 55 | 56 | 57 | ans = 0 58 | for abspath in dir_sizes: 59 | if dfs(abspath) <= 100000: 60 | ans += dfs(abspath) 61 | 62 | print(ans) 63 | -------------------------------------------------------------------------------- /day_07/day_07_p2.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from functools import lru_cache 3 | 4 | with open("./day_07.in") as fin: 5 | blocks = ("\n" + fin.read().strip()).split("\n$ ")[1:] 6 | 7 | 8 | path = [] 9 | 10 | dir_sizes = defaultdict(int) 11 | children = defaultdict(list) 12 | visited = set() 13 | 14 | 15 | def parse(block): 16 | lines = block.split("\n") 17 | command = lines[0] 18 | outputs = lines[1:] 19 | 20 | parts = command.split(" ") 21 | op = parts[0] 22 | if op == "cd": 23 | if parts[1] == "..": 24 | path.pop() 25 | else: 26 | path.append(parts[1]) 27 | 28 | return 29 | 30 | abspath = "/".join(path) 31 | 32 | assert op == "ls" 33 | 34 | sizes = [] 35 | for line in outputs: 36 | if not line.startswith("dir"): 37 | sizes.append(int(line.split(" ")[0])) 38 | else: 39 | dir_name = line.split(" ")[1] 40 | children[abspath].append(f"{abspath}/{dir_name}") 41 | 42 | dir_sizes[abspath] = sum(sizes) 43 | 44 | 45 | for block in blocks: 46 | parse(block) 47 | 48 | 49 | # Do DFS 50 | @lru_cache(None) 51 | def dfs(abspath): 52 | size = dir_sizes[abspath] 53 | for child in children[abspath]: 54 | size += dfs(child) 55 | return size 56 | 57 | 58 | unused = 70000000 - dfs("/") 59 | required = 30000000 - unused 60 | 61 | ans = 1 << 60 62 | for abspath in dir_sizes: 63 | size = dfs(abspath) 64 | if size >= required: 65 | ans = min(ans, size) 66 | 67 | print(ans) 68 | -------------------------------------------------------------------------------- /day_08/day_08_p1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | with open("./day_08.in", "r") as fin: 4 | lines = fin.read().strip().split() 5 | 6 | print(lines) 7 | grid = [list(map(int, list(line))) for line in lines] 8 | 9 | n = len(grid) 10 | m = len(grid[0]) 11 | 12 | grid = np.array(grid) 13 | 14 | ans = 0 15 | for i in range(n): 16 | for j in range(m): 17 | h = grid[i, j] 18 | 19 | if j == 0 or np.amax(grid[i, :j]) < h: 20 | ans += 1 21 | elif j == m - 1 or np.amax(grid[i, (j+1):]) < h: 22 | ans += 1 23 | elif i == 0 or np.amax(grid[:i, j]) < h: 24 | ans += 1 25 | elif i == n - 1 or np.amax(grid[(i+1):, j]) < h: 26 | ans += 1 27 | 28 | print(ans) 29 | -------------------------------------------------------------------------------- /day_08/day_08_p2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | with open("./day_08.in", "r") as fin: 4 | lines = fin.read().strip().split() 5 | 6 | grid = [list(map(int, list(line))) for line in lines] 7 | 8 | n = len(grid) 9 | m = len(grid[0]) 10 | 11 | grid = np.array(grid) 12 | 13 | dd = [[0, 1], [0, -1], [1, 0], [-1, 0]] 14 | 15 | ans = 0 16 | for i in range(n): 17 | for j in range(m): 18 | h = grid[i, j] 19 | score = 1 20 | 21 | # Scan in 4 directions 22 | for di, dj in dd: 23 | ii, jj = i + di, j + dj 24 | dist = 0 25 | 26 | while (0 <= ii < n and 0 <= jj < m) and grid[ii, jj] < h: 27 | dist += 1 28 | ii += di 29 | jj += dj 30 | 31 | if (0 <= ii < n and 0 <= jj < m) and grid[ii, jj] >= h: 32 | dist += 1 33 | 34 | score *= dist 35 | 36 | ans = max(ans, score) 37 | 38 | 39 | print(ans) 40 | -------------------------------------------------------------------------------- /day_09/day_09_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_09.in") as fin: 2 | lines = fin.read().strip().split("\n") 3 | 4 | 5 | hx, hy = 0, 0 6 | tx, ty = 0, 0 7 | 8 | 9 | def touching(x1, y1, x2, y2): 10 | return abs(x1 - x2) <= 1 and abs(y1 - y2) <= 1 11 | 12 | 13 | def move(dx, dy): 14 | global hx, hy, tx, ty 15 | 16 | hx += dx 17 | hy += dy 18 | 19 | if not touching(hx, hy, tx, ty): 20 | sign_x = 0 if hx == tx else (hx - tx) / abs(hx - tx) 21 | sign_y = 0 if hy == ty else (hy - ty) / abs(hy - ty) 22 | 23 | tx += sign_x 24 | ty += sign_y 25 | 26 | 27 | dd = { 28 | "R": [1, 0], 29 | "U": [0, 1], 30 | "L": [-1, 0], 31 | "D": [0, -1] 32 | } 33 | 34 | tail_visited = set() 35 | tail_visited.add((tx, ty)) 36 | 37 | for line in lines: 38 | op, amount = line.split(" ") 39 | amount = int(amount) 40 | dx, dy = dd[op] 41 | 42 | for _ in range(amount): 43 | move(dx, dy) 44 | tail_visited.add((tx, ty)) 45 | 46 | print(len(tail_visited)) 47 | -------------------------------------------------------------------------------- /day_09/day_09_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_09.in") as fin: 2 | lines = fin.read().strip().split("\n") 3 | 4 | 5 | knots = [[0, 0] for _ in range(10)] 6 | 7 | 8 | def touching(x1, y1, x2, y2): 9 | return abs(x1 - x2) <= 1 and abs(y1 - y2) <= 1 10 | 11 | 12 | def move(dx, dy): 13 | global knots 14 | knots[0][0] += dx 15 | knots[0][1] += dy 16 | 17 | for i in range(1, 10): 18 | hx, hy = knots[i - 1] 19 | tx, ty = knots[i] 20 | 21 | if not touching(hx, hy, tx, ty): 22 | sign_x = 0 if hx == tx else (hx - tx) / abs(hx - tx) 23 | sign_y = 0 if hy == ty else (hy - ty) / abs(hy - ty) 24 | 25 | tx += sign_x 26 | ty += sign_y 27 | 28 | knots[i] = [tx, ty] 29 | 30 | 31 | dd = { 32 | "R": [1, 0], 33 | "U": [0, 1], 34 | "L": [-1, 0], 35 | "D": [0, -1] 36 | } 37 | 38 | tail_visited = set() 39 | tail_visited.add(tuple(knots[-1])) 40 | 41 | for line in lines: 42 | op, amount = line.split(" ") 43 | amount = int(amount) 44 | dx, dy = dd[op] 45 | 46 | for _ in range(amount): 47 | move(dx, dy) 48 | tail_visited.add(tuple(knots[-1])) 49 | 50 | print(len(tail_visited)) 51 | -------------------------------------------------------------------------------- /day_10/day_10_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_10.in") as fin: 2 | lines = fin.read().strip().split("\n") 3 | 4 | X = 1 5 | op = 0 6 | 7 | ans = 0 8 | interesting = [20, 60, 100, 140, 180, 220] 9 | 10 | for line in lines: 11 | parts = line.split(" ") 12 | 13 | if parts[0] == "noop": 14 | op += 1 15 | 16 | if op in interesting: 17 | ans += op * X 18 | 19 | elif parts[0] == "addx": 20 | V = int(parts[1]) 21 | X += V 22 | 23 | op += 1 24 | 25 | if op in interesting: 26 | ans += op * (X - V) 27 | 28 | op += 1 29 | 30 | if op in interesting: 31 | ans += op * (X - V) 32 | 33 | 34 | print(ans) 35 | -------------------------------------------------------------------------------- /day_10/day_10_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_10.in") as fin: 2 | lines = fin.read().strip().split("\n") 3 | 4 | cur_X = 1 5 | op = 0 6 | ans = 0 7 | 8 | row = 0 9 | col = 0 10 | 11 | X = [1] * 241 12 | 13 | for line in lines: 14 | parts = line.split(" ") 15 | 16 | if parts[0] == "noop": 17 | op += 1 18 | X[op] = cur_X 19 | 20 | elif parts[0] == "addx": 21 | V = int(parts[1]) 22 | 23 | X[op + 1] = cur_X 24 | cur_X += V 25 | 26 | op += 2 27 | X[op] = cur_X 28 | 29 | 30 | # Ranges from [1, 39] 31 | ans = [[None] * 40 for _ in range(6)] 32 | 33 | for row in range(6): 34 | for col in range(40): 35 | counter = row * 40 + col + 1 36 | if abs(X[counter - 1] - (col)) <= 1: 37 | ans[row][col] = "##" 38 | else: 39 | ans[row][col] = " " 40 | 41 | 42 | for row in ans: 43 | print("".join(row)) 44 | -------------------------------------------------------------------------------- /day_11/day_11_p1.py: -------------------------------------------------------------------------------- 1 | from pprint import pprint 2 | 3 | with open("./day_11.in") as fin: 4 | raw_data = fin.read().strip() 5 | monkey_parts = raw_data.split("\n\n") 6 | 7 | monkeys = [] 8 | 9 | 10 | def expr(operation, x): 11 | left, op, right = operation 12 | 13 | assert left == "old" # Assumption 14 | 15 | if op == "+": 16 | return x + int(right) 17 | else: 18 | if right == "old": 19 | return x * x 20 | else: 21 | return x * int(right) 22 | 23 | 24 | class Monkey: 25 | def __init__(self, items, operation, test): 26 | self.items = items 27 | self.operation = operation 28 | self.test = test 29 | self.inspections = 0 30 | 31 | def __str__(self): 32 | return f"{self.items}, {self.operation}, {self.test}" 33 | 34 | 35 | for i, monkey_part in enumerate(monkey_parts): 36 | lines = monkey_part.split("\n") 37 | items = list(map(int, lines[1][2:].split(" ", 2)[2].split(", "))) 38 | operation = lines[2][2:].split(" ", 3)[3].split(" ") 39 | 40 | # Parse the test 41 | mod = int(lines[3][2:].split(" ")[-1]) 42 | if_true = int(lines[4][4:].split(" ")[-1]) 43 | if_false = int(lines[5][4:].split(" ")[-1]) 44 | 45 | # Package it together 46 | monkeys.append(Monkey( 47 | items, 48 | operation, 49 | [mod, if_true, if_false] 50 | )) 51 | 52 | 53 | # Do the rounds 54 | N = len(monkeys) 55 | 56 | for round in range(20): 57 | for i in range(N): 58 | monkey = monkeys[i] 59 | for item in monkey.items: 60 | # item is an int, representing worry level 61 | item = expr(monkey.operation, item) 62 | item //= 3 63 | 64 | mod, if_true, if_false = monkey.test 65 | if item % mod == 0: 66 | monkeys[if_true].items.append(item) 67 | else: 68 | monkeys[if_false].items.append(item) 69 | 70 | monkey.inspections += 1 71 | 72 | # Empty the list of items 73 | monkey.items = [] 74 | 75 | 76 | # Are we done 77 | amounts = [m.inspections for m in monkeys] 78 | sorted_amts = sorted(amounts) 79 | print(sorted_amts[-1] * sorted_amts[-2]) 80 | -------------------------------------------------------------------------------- /day_11/day_11_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_11.in") as fin: 2 | raw_data = fin.read().strip() 3 | monkey_parts = raw_data.split("\n\n") 4 | 5 | monkeys = [] 6 | 7 | 8 | class Monkey: 9 | def __init__(self, items, operation, test): 10 | self.items = items 11 | self.operation = operation 12 | self.test = test 13 | self.inspections = 0 14 | 15 | def __str__(self): 16 | return f"{self.items}, {self.operation}, {self.test}" 17 | 18 | 19 | for i, monkey_part in enumerate(monkey_parts): 20 | lines = monkey_part.split("\n") 21 | items = list(map(int, lines[1][2:].split(" ", 2)[2].split(", "))) 22 | operation = lines[2][2:].split(" ", 3)[3].split(" ") 23 | 24 | # Parse the test 25 | mod = int(lines[3][2:].split(" ")[-1]) 26 | if_true = int(lines[4][4:].split(" ")[-1]) 27 | if_false = int(lines[5][4:].split(" ")[-1]) 28 | 29 | # Package it together 30 | monkeys.append(Monkey( 31 | items, 32 | operation, 33 | [mod, if_true, if_false] 34 | )) 35 | 36 | 37 | big_mod = 1 38 | for monkey in monkeys: 39 | big_mod *= monkey.test[0] 40 | 41 | 42 | def expr(operation, x): 43 | left, op, right = operation 44 | assert left == "old" # Assumption 45 | 46 | if op == "+": 47 | ans = x + int(right) 48 | else: 49 | if right == "old": 50 | ans = x * x 51 | else: 52 | ans = x * int(right) 53 | 54 | return ans % big_mod 55 | 56 | 57 | # Do the rounds 58 | N = len(monkeys) 59 | 60 | for round in range(10000): 61 | for i in range(N): 62 | monkey = monkeys[i] 63 | for item in monkey.items: 64 | # item is an int, representing worry level 65 | item = expr(monkey.operation, item) 66 | 67 | mod, if_true, if_false = monkey.test 68 | 69 | if item % mod == 0: 70 | monkeys[if_true].items.append(item) 71 | else: 72 | monkeys[if_false].items.append(item) 73 | 74 | monkey.inspections += 1 75 | 76 | # Empty the list of items 77 | monkey.items = [] 78 | 79 | 80 | # Are we done 81 | amounts = [m.inspections for m in monkeys] 82 | print(amounts) 83 | 84 | sorted_amts = sorted(amounts) 85 | print(sorted_amts[-1] * sorted_amts[-2]) 86 | -------------------------------------------------------------------------------- /day_12/day_12_p1.py: -------------------------------------------------------------------------------- 1 | from string import ascii_lowercase 2 | from heapq import heappop, heappush 3 | 4 | with open("./day_12.in") as fin: 5 | lines = fin.read().strip().split() 6 | 7 | grid = [list(line) for line in lines] 8 | n = len(grid) 9 | m = len(grid[0]) 10 | 11 | for i in range(n): 12 | for j in range(m): 13 | char = grid[i][j] 14 | if char == "S": 15 | start = i, j 16 | if char == "E": 17 | end = i, j 18 | 19 | 20 | def height(s): 21 | if s in ascii_lowercase: 22 | return ascii_lowercase.index(s) 23 | if s == "S": 24 | return 0 25 | if s == "E": 26 | return 25 27 | 28 | 29 | # Determine neighbors 30 | def neighbors(i, j): 31 | for di, dj in [[1, 0], [-1, 0], [0, 1], [0, -1]]: 32 | ii = i + di 33 | jj = j + dj 34 | 35 | if not (0 <= ii < n and 0 <= jj < m): 36 | continue 37 | 38 | if height(grid[ii][jj]) <= height(grid[i][j]) + 1: 39 | yield ii, jj 40 | 41 | 42 | # Dijkstra's 43 | visited = [[False] * m for _ in range(n)] 44 | heap = [(0, start[0], start[1])] 45 | 46 | while True: 47 | steps, i, j = heappop(heap) 48 | 49 | if visited[i][j]: 50 | continue 51 | visited[i][j] = True 52 | 53 | if (i, j) == end: 54 | print(steps) 55 | break 56 | 57 | for ii, jj in neighbors(i, j): 58 | heappush(heap, (steps + 1, ii, jj)) 59 | -------------------------------------------------------------------------------- /day_12/day_12_p2.py: -------------------------------------------------------------------------------- 1 | from string import ascii_lowercase 2 | from heapq import heappop, heappush 3 | 4 | with open("./day_12.in") as fin: 5 | lines = fin.read().strip().split() 6 | 7 | grid = [list(line) for line in lines] 8 | n = len(grid) 9 | m = len(grid[0]) 10 | 11 | for i in range(n): 12 | for j in range(m): 13 | char = grid[i][j] 14 | if char == "S": 15 | start = i, j 16 | if char == "E": 17 | end = i, j 18 | 19 | 20 | def height(s): 21 | if s in ascii_lowercase: 22 | return ascii_lowercase.index(s) 23 | if s == "S": 24 | return 0 25 | if s == "E": 26 | return 25 27 | 28 | 29 | # Determine neighbors 30 | def neighbors(i, j): 31 | for di, dj in [[1, 0], [-1, 0], [0, 1], [0, -1]]: 32 | ii = i + di 33 | jj = j + dj 34 | 35 | if not (0 <= ii < n and 0 <= jj < m): 36 | continue 37 | 38 | if height(grid[ii][jj]) >= height(grid[i][j]) - 1: 39 | yield ii, jj 40 | 41 | 42 | # Dijkstra's 43 | visited = [[False] * m for _ in range(n)] 44 | heap = [(0, end[0], end[1])] 45 | 46 | while True: 47 | steps, i, j = heappop(heap) 48 | 49 | if visited[i][j]: 50 | continue 51 | visited[i][j] = True 52 | 53 | if height(grid[i][j]) == 0: 54 | print(steps) 55 | break 56 | 57 | for ii, jj in neighbors(i, j): 58 | heappush(heap, (steps + 1, ii, jj)) 59 | -------------------------------------------------------------------------------- /day_13/day_13_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_13.in") as fin: 2 | parts = fin.read().strip().split("\n\n") 3 | 4 | 5 | def compare(a, b): 6 | if isinstance(a, list) and isinstance(b, int): 7 | b = [b] 8 | 9 | if isinstance(a, int) and isinstance(b, list): 10 | a = [a] 11 | 12 | if isinstance(a, int) and isinstance(b, int): 13 | if a < b: 14 | return 1 15 | if a == b: 16 | return 0 17 | return -1 18 | 19 | if isinstance(a, list) and isinstance(b, list): 20 | i = 0 21 | while i < len(a) and i < len(b): 22 | x = compare(a[i], b[i]) 23 | if x == 1: 24 | return 1 25 | if x == -1: 26 | return -1 27 | 28 | i += 1 29 | 30 | if i == len(a): 31 | if len(a) == len(b): 32 | return 0 33 | return 1 # a ended first 34 | 35 | # If i didn't hit the end of a, it hit the end of b first 36 | # This means that b is shorter, which is bad 37 | return -1 38 | 39 | 40 | ans = 0 41 | 42 | for i, block in enumerate(parts): 43 | a, b = map(eval, block.split("\n")) 44 | if compare(a, b) == 1: 45 | ans += i + 1 46 | 47 | print(ans) 48 | -------------------------------------------------------------------------------- /day_13/day_13_p2.py: -------------------------------------------------------------------------------- 1 | from functools import cmp_to_key 2 | 3 | with open("./day_13.in") as fin: 4 | parts = fin.read().strip().replace("\n\n", "\n").split("\n") 5 | 6 | 7 | def compare(a, b): 8 | if isinstance(a, list) and isinstance(b, int): 9 | b = [b] 10 | 11 | if isinstance(a, int) and isinstance(b, list): 12 | a = [a] 13 | 14 | if isinstance(a, int) and isinstance(b, int): 15 | if a < b: 16 | return 1 17 | if a == b: 18 | return 0 19 | return -1 20 | 21 | if isinstance(a, list) and isinstance(b, list): 22 | i = 0 23 | while i < len(a) and i < len(b): 24 | x = compare(a[i], b[i]) 25 | if x == 1: 26 | return 1 27 | if x == -1: 28 | return -1 29 | 30 | i += 1 31 | 32 | if i == len(a): 33 | if len(a) == len(b): 34 | return 0 35 | return 1 # a ended first 36 | 37 | # If i didn't hit the end of a, it hit the end of b first 38 | # This means that b is shorter, which is bad 39 | return -1 40 | 41 | 42 | lists = list(map(eval, parts)) 43 | lists.append([[2]]) 44 | lists.append([[6]]) 45 | lists = sorted(lists, key=cmp_to_key(compare), reverse=True) 46 | 47 | 48 | for i, li in enumerate(lists): 49 | if li == [[2]]: 50 | a = i + 1 51 | if li == [[6]]: 52 | b = i + 1 53 | 54 | print(a * b) 55 | -------------------------------------------------------------------------------- /day_14/day_14_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_14.in") as fin: 2 | lines = fin.read().strip().split("\n") 3 | 4 | sand_source = 500, 0 5 | 6 | filled = set() 7 | 8 | for line in lines: 9 | coords = [] 10 | 11 | for str_coord in line.split(" -> "): 12 | x, y = map(int, str_coord.split(",")) 13 | coords.append((x, y)) 14 | 15 | for i in range(1, len(coords)): 16 | cx, cy = coords[i] # cur 17 | px, py = coords[i - 1] 18 | 19 | if cy != py: 20 | assert cx == px 21 | for y in range(min(cy, py), max(cy, py) + 1): 22 | filled.add((cx, y)) 23 | 24 | if cx != px: 25 | assert cy == py 26 | for x in range(min(cx, px), max(cx, px) + 1): 27 | filled.add((x, cy)) 28 | 29 | 30 | max_y = max([coord[1] for coord in filled]) 31 | 32 | # Fill with sand 33 | 34 | 35 | def simulate_sand(): 36 | global filled 37 | x, y = 500, 0 38 | 39 | while y <= max_y: 40 | if (x, y + 1) not in filled: 41 | y += 1 42 | continue 43 | 44 | if (x - 1, y + 1) not in filled: 45 | x -= 1 46 | y += 1 47 | continue 48 | 49 | if (x + 1, y + 1) not in filled: 50 | x += 1 51 | y += 1 52 | continue 53 | 54 | # Everything filled, come to rest 55 | filled.add((x, y)) 56 | return True 57 | 58 | return False 59 | 60 | 61 | ans = 0 62 | 63 | while True: 64 | res = simulate_sand() 65 | 66 | if not res: 67 | break 68 | 69 | ans += 1 70 | 71 | print(ans) 72 | -------------------------------------------------------------------------------- /day_14/day_14_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_14.in") as fin: 2 | lines = fin.read().strip().split("\n") 3 | 4 | sand_source = 500, 0 5 | 6 | filled = set() 7 | for line in lines: 8 | coords = [] 9 | 10 | for str_coord in line.split(" -> "): 11 | x, y = map(int, str_coord.split(",")) 12 | coords.append((x, y)) 13 | 14 | for i in range(1, len(coords)): 15 | cx, cy = coords[i] # cur 16 | px, py = coords[i - 1] 17 | 18 | if cy != py: 19 | assert cx == px 20 | for y in range(min(cy, py), max(cy, py) + 1): 21 | filled.add((cx, y)) 22 | 23 | if cx != px: 24 | assert cy == py 25 | for x in range(min(cx, px), max(cx, px) + 1): 26 | filled.add((x, cy)) 27 | 28 | 29 | max_y = max([coord[1] for coord in filled]) 30 | 31 | # Fill with sand 32 | 33 | 34 | def simulate_sand(): 35 | global filled 36 | x, y = 500, 0 37 | 38 | if (x, y) in filled: 39 | return (x, y) 40 | 41 | while y <= max_y: 42 | if (x, y + 1) not in filled: 43 | y += 1 44 | continue 45 | 46 | if (x - 1, y + 1) not in filled: 47 | x -= 1 48 | y += 1 49 | continue 50 | 51 | if (x + 1, y + 1) not in filled: 52 | x += 1 53 | y += 1 54 | continue 55 | 56 | # Everything filled, come to rest 57 | break 58 | 59 | return (x, y) 60 | 61 | 62 | ans = 0 63 | 64 | while True: 65 | x, y = simulate_sand() 66 | filled.add((x, y)) 67 | ans += 1 68 | 69 | if (x, y) == (500, 0): 70 | break 71 | 72 | print(ans) 73 | -------------------------------------------------------------------------------- /day_15/day_15_p1.py: -------------------------------------------------------------------------------- 1 | from tqdm import tqdm 2 | 3 | with open("./day_15.in") as fin: 4 | lines = fin.read().strip().split("\n") 5 | 6 | 7 | def dist(a, b): 8 | return abs(a[0] - b[0]) + abs(a[1] - b[1]) 9 | 10 | 11 | sensors = [] 12 | beacons = [] 13 | for line in lines: 14 | parts = line.split(" ") 15 | 16 | sx = int(parts[2][2:-1]) 17 | sy = int(parts[3][2:-1]) 18 | bx = int(parts[8][2:-1]) 19 | by = int(parts[9][2:]) 20 | 21 | sensors.append((sx, sy)) 22 | beacons.append((bx, by)) 23 | 24 | 25 | N = len(sensors) 26 | dists = [] 27 | 28 | for i in range(N): 29 | dists.append(dist(sensors[i], beacons[i])) 30 | 31 | Y = 2000000 32 | 33 | intervals = [] 34 | 35 | for i, s in enumerate(sensors): 36 | dx = dists[i] - abs(s[1] - Y) 37 | 38 | if dx <= 0: 39 | continue 40 | 41 | intervals.append((s[0] - dx, s[0] + dx)) 42 | 43 | 44 | # INTERVAL OVERLAP ETC. 45 | allowed_x = [] 46 | for bx, by in beacons: 47 | if by == Y: 48 | allowed_x.append(bx) 49 | 50 | print(allowed_x) 51 | 52 | min_x = min([i[0] for i in intervals]) 53 | max_x = max([i[1] for i in intervals]) 54 | 55 | ans = 0 56 | for x in range(min_x, max_x + 1): 57 | if x in allowed_x: 58 | continue 59 | 60 | for left, right in intervals: 61 | if left <= x <= right: 62 | ans += 1 63 | break 64 | 65 | 66 | print(ans) 67 | -------------------------------------------------------------------------------- /day_15/day_15_p2.py: -------------------------------------------------------------------------------- 1 | from tqdm import tqdm 2 | 3 | with open("./day_15.in") as fin: 4 | lines = fin.read().strip().split("\n") 5 | 6 | 7 | # def dist(x1, y1, x2, y2): 8 | # return abs(x1 - x2) + abs(y1 - y2) 9 | 10 | def dist(a, b): 11 | return abs(a[0] - b[0]) + abs(a[1] - b[1]) 12 | 13 | 14 | sensors = [] 15 | beacons = [] 16 | for line in lines: 17 | parts = line.split(" ") 18 | 19 | sx = int(parts[2][2:-1]) 20 | sy = int(parts[3][2:-1]) 21 | bx = int(parts[8][2:-1]) 22 | by = int(parts[9][2:]) 23 | 24 | sensors.append((sx, sy)) 25 | beacons.append((bx, by)) 26 | 27 | 28 | N = len(sensors) 29 | dists = [] 30 | 31 | for i in range(N): 32 | dists.append(dist(sensors[i], beacons[i])) 33 | 34 | 35 | pos_lines = [] 36 | neg_lines = [] 37 | 38 | for i, s in enumerate(sensors): 39 | d = dists[i] 40 | neg_lines.extend([s[0] + s[1] - d, s[0] + s[1] + d]) 41 | pos_lines.extend([s[0] - s[1] - d, s[0] - s[1] + d]) 42 | 43 | 44 | pos = None 45 | neg = None 46 | 47 | for i in range(2 * N): 48 | for j in range(i + 1, 2 * N): 49 | a, b = pos_lines[i], pos_lines[j] 50 | 51 | if abs(a - b) == 2: 52 | pos = min(a, b) + 1 53 | 54 | a, b = neg_lines[i], neg_lines[j] 55 | 56 | if abs(a - b) == 2: 57 | neg = min(a, b) + 1 58 | 59 | 60 | x, y = (pos + neg) // 2, (neg - pos) // 2 61 | ans = x * 4000000 + y 62 | print(ans) 63 | -------------------------------------------------------------------------------- /day_16/day_16/day_16.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | typedef vector vi; 5 | 6 | void setIO(string s) { 7 | freopen((s + ".in").c_str(), "r", stdin); 8 | freopen((s + ".out").c_str(), "w", stdout); 9 | } 10 | 11 | const int minutes = 30; 12 | 13 | int main() { 14 | setIO("day_16_parsed"); 15 | 16 | int N; 17 | cin >> N; 18 | 19 | // Read flows 20 | vi flow(N); 21 | for (int i = 0; i < N; i++) { 22 | cin >> flow[i]; 23 | } 24 | 25 | // Grab adjacency matrix 26 | vector adj(N, vi(N)); 27 | for (int i = 0; i < N; i++) { 28 | for (int j = 0; j < N; j++) { 29 | cin >> adj[i][j]; 30 | } 31 | } 32 | 33 | // Preprocess state-pressure calculation 34 | // cout << "finished reading input\n"; 35 | 36 | vi get_pressure(1 << N); 37 | for (int state = 0; state < (1 << N); state++) { 38 | int total = 0; 39 | int i = 0; 40 | int temp_state = state; 41 | while (temp_state > 0) { 42 | if (temp_state & 1) { 43 | total += flow[i]; 44 | } 45 | temp_state >>= 1; 46 | i++; 47 | } 48 | get_pressure[state] = total; 49 | } 50 | 51 | // cout << "finished get_pressure preprocessing\n"; 52 | 53 | // DO THE DP 54 | // index by time, node, state 55 | vector> dp(minutes + 1, vector(N, vi(1 << N, -1))); 56 | dp[0][0][0] = 0; 57 | 58 | for (int time = 1; time <= minutes; time++) { 59 | for (int node = 0; node < N; node++) { 60 | for (int state = 0; state < (1 << N); state++) { 61 | // cout << "\ntime=" << time << ", node=" << node << ", state=" << state << "\n"; 62 | 63 | // Turn turning node on 64 | // cout << " trying turning node on\n"; 65 | int a1 = -1; 66 | if (state & (1 << node)) { 67 | int prev = dp[time - 1][node][state ^ (1 << node)]; 68 | if (prev == -1) continue; 69 | a1 = prev + get_pressure[state ^ (1 << node)]; 70 | } 71 | // cout << " produced " << a1 << "\n"; 72 | 73 | // Move to a neighbor 74 | // cout << " trying moving to a neighbor\n"; 75 | int a2 = -1; 76 | for (int other = 0; other < N; other++) { 77 | int d = adj[node][other]; 78 | if (d > time) continue; 79 | 80 | int prev = dp[time - d][other][state]; 81 | if (prev == -1) continue; 82 | 83 | a2 = max(a2, prev + get_pressure[state] * d); 84 | // cout << " neighbor=" << other << ", " << prev << "\n"; 85 | } 86 | // cout << " produced " << a2 << "\n"; 87 | 88 | int ans = max({dp[time - 1][node][state], a1, a2}); 89 | dp[time][node][state] = ans; 90 | 91 | // cout << " FINAL dp value=" << ans << "\n"; 92 | } 93 | } 94 | } 95 | 96 | int ans = 0; 97 | for (int node = 0; node < N; node++) { 98 | for (int state = 0; state < (1 << N); state++) { 99 | // cout << setw(4) << dp[minutes][node][state] << " "; 100 | 101 | ans = max(ans, dp[minutes][node][state]); 102 | } 103 | cout << "\n"; 104 | } 105 | cout << ans << "\n"; 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /day_16/day_16/day_16_p1 copy.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from heapq import heapify, heappop, heappush 3 | from pprint import pprint 4 | from functools import lru_cache 5 | from collections import deque 6 | 7 | with open("../day_16.in") as fin: 8 | lines = fin.read().strip().split("\n") 9 | 10 | 11 | adj = defaultdict(list) 12 | flow = [] 13 | nodes = [] 14 | node_to_idx = {} 15 | 16 | for i, line in enumerate(lines): 17 | parts = line.split(" ", 9) 18 | node = parts[1] 19 | nodes.append(node) 20 | node_to_idx[node] = i 21 | flow.append(int(parts[4][5:-1])) 22 | adj[node].extend(parts[-1].split(", ")) 23 | 24 | 25 | # Run Floyd-Warshall 26 | N = len(nodes) 27 | dist = [[1 << 60] * N for _ in range(N)] 28 | 29 | for node in nodes: 30 | node_idx = node_to_idx[node] 31 | for nbr in adj[node]: 32 | nbr_idx = node_to_idx[nbr] 33 | dist[node_idx][nbr_idx] = 1 34 | 35 | dist[node_idx][node_idx] = 0 36 | 37 | 38 | for k in range(N): 39 | for i in range(N): 40 | for j in range(N): 41 | if dist[i][j] > dist[i][k] + dist[k][j]: 42 | dist[i][j] = dist[i][k] + dist[k][j] 43 | 44 | 45 | # Only a subset of the nodes actually have flow 46 | good_nodes = [] 47 | for i in range(N): 48 | if flow[i] > 0: 49 | good_nodes.append(i) 50 | 51 | 52 | M = len(good_nodes) 53 | 54 | 55 | # Export adjacency matrix 56 | M = len(good_nodes) + 1 57 | 58 | AA = node_to_idx["AA"] 59 | good_nodes.insert(0, AA) 60 | 61 | with open("./day_16_parsed.in", "w") as fout: 62 | fout.write(f"{M}\n") 63 | fout.write(" ".join(map(str, [flow[i] for i in good_nodes])) + "\n") 64 | for i in range(M): 65 | line = "" 66 | for j in range(M): 67 | a, b = good_nodes[i], good_nodes[j] 68 | line += f"{dist[a][b]} " 69 | fout.write(line[:-1] + "\n") 70 | 71 | exit() 72 | 73 | 74 | @lru_cache(None) 75 | def get_pressure(state): 76 | total = 0 77 | i = 0 78 | while state > 0: 79 | if state & 1: 80 | total += flow[good_nodes[i]] 81 | state >>= 1 82 | i += 1 83 | 84 | return total 85 | 86 | 87 | dp = [[[-1] * (1 << M) for _ in range(M)] for _ in range(31)] 88 | 89 | # dp[time][good_node][state] 90 | for time in range(1, 31): 91 | for good_node in range(M): 92 | for state in range(1 << M): 93 | # Alternatives! 94 | a1 = 0 95 | 96 | if not state & (1 << good_node): 97 | a1 = dp[time - 1][good_node][state ^ (1 << good_node)] 98 | 99 | a2 = 0 100 | for other in range(M): 101 | d = dist[good_node][other] 102 | 103 | # Not enough time to get there and do anything 104 | if d > time: 105 | continue 106 | 107 | a2 = max(a2, dp[time - d][other][state]) 108 | 109 | # Status quo, a1, or a2 110 | ans = max(dp[time - 1][good_node][state], a1, a2) 111 | 112 | ans += get_pressure(state) 113 | 114 | dp[time][good_node][state] = ans 115 | 116 | 117 | # Get from AA to any other node 118 | ans = 0 119 | 120 | cost_to_reach = {} 121 | 122 | visited = set() 123 | dq = deque([("AA", 0)]) 124 | 125 | while len(dq) > 0: 126 | node, cost = dq.popleft() 127 | if node in visited: 128 | continue 129 | visited.add(node) 130 | 131 | if flow[node_to_idx[node]] > 0: 132 | cost_to_reach[node] = cost 133 | 134 | for nbr in adj[node]: 135 | dq.append((nbr, cost + 1)) 136 | 137 | for i, good_node in enumerate(good_nodes): 138 | t = cost_to_reach[nodes[good_node]] 139 | 140 | if t >= 30: 141 | continue 142 | 143 | ans = max(ans, dp[30 - t][i][0]) 144 | 145 | 146 | for row in dp[1]: 147 | print(row[:10]) 148 | 149 | print(ans) 150 | -------------------------------------------------------------------------------- /day_16/day_16/day_16_p2.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | typedef vector vi; 5 | 6 | void setIO(string s) { 7 | freopen((s + ".in").c_str(), "r", stdin); 8 | freopen((s + ".out").c_str(), "w", stdout); 9 | } 10 | 11 | const int minutes = 30; 12 | 13 | int main() { 14 | setIO("day_16_parsed"); 15 | 16 | int N; 17 | cin >> N; 18 | 19 | // Read flows 20 | vi flow(N); 21 | for (int i = 0; i < N; i++) { 22 | cin >> flow[i]; 23 | } 24 | 25 | // Grab adjacency matrix 26 | vector adj(N, vi(N)); 27 | for (int i = 0; i < N; i++) { 28 | for (int j = 0; j < N; j++) { 29 | cin >> adj[i][j]; 30 | } 31 | } 32 | 33 | // Preprocess state-pressure calculation 34 | // cout << "finished reading input\n"; 35 | 36 | vi get_pressure(1 << N); 37 | for (int state = 0; state < (1 << N); state++) { 38 | int total = 0; 39 | int i = 0; 40 | int temp_state = state; 41 | while (temp_state > 0) { 42 | if (temp_state & 1) { 43 | total += flow[i]; 44 | } 45 | temp_state >>= 1; 46 | i++; 47 | } 48 | get_pressure[state] = total; 49 | } 50 | 51 | // cout << "finished get_pressure preprocessing\n"; 52 | 53 | // DO THE DP 54 | // index by time, node, state 55 | vector>> dp(minutes + 1, vector>(N, vector(N, vi(1 << N, -1)))); 56 | dp[0][0][0][0] = 0; 57 | 58 | for (int time = 1; time <= minutes; time++) { 59 | for (int node = 0; node < N; node++) { 60 | for (int node2 = 0; node2 < N; node2++) { 61 | for (int state = 0; state < (1 << N); state++) { 62 | } 63 | } 64 | } 65 | } 66 | 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /day_16/day_16_p1.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from heapq import heapify, heappop, heappush 3 | 4 | with open("./day_16.in") as fin: 5 | lines = fin.read().strip().split("\n") 6 | 7 | 8 | adj = defaultdict(list) 9 | flow = defaultdict(int) 10 | nodes = [] 11 | node_to_idx = {} 12 | 13 | for i, line in enumerate(lines): 14 | parts = line.split(" ", 9) 15 | node = parts[1] 16 | nodes.append(node) 17 | node_to_idx[node] = i 18 | flow[node] = int(parts[4][5:-1]) 19 | adj[node].extend(parts[-1].split(", ")) 20 | 21 | 22 | # Visit all 15 nonzero valves in some order 23 | # 2^15 isn't horrible 24 | # approx. 30 * 2^15 25 | 26 | def get_pressure(state): 27 | total = 0 28 | for i in range(len(state)): 29 | if state[i]: 30 | total += flow[nodes[i]] 31 | return total 32 | 33 | 34 | ans = 0 35 | 36 | seen = set() 37 | pq = [(0, "AA", 30, (False,) * 59)] 38 | 39 | count = 0 40 | 41 | # TIME COMPLEXITY 42 | total_states = 30 * 15 * (1 << 15) 43 | 44 | 45 | while len(pq) > 0: 46 | front = heappop(pq) 47 | pressure, node, time, state = front 48 | pressure *= -1 49 | 50 | if (pressure, node, time, state) in seen: 51 | continue 52 | seen.add((pressure, node, time, state)) 53 | 54 | # Done? 55 | if time == 0: 56 | ans = max(ans, pressure) 57 | continue 58 | 59 | idx = node_to_idx[node] 60 | 61 | pressure += get_pressure(state) 62 | 63 | # 1. open the valve 64 | if flow[node] > 0 and not state[idx]: 65 | new_state = list(state) 66 | new_state[idx] = True 67 | new_state = tuple(new_state) 68 | heappush(pq, (-pressure, node, time - 1, new_state)) 69 | 70 | # 2. move to a neighbor 71 | for nbr in adj[node]: 72 | heappush(pq, (-pressure, nbr, time - 1, state)) 73 | 74 | heappush(pq, (-pressure, node, time - 1, state)) 75 | 76 | count += 1 77 | if count % 100000 == 0: 78 | print(f"{str(count).rjust(9)}/{total_states}", end="") 79 | print("\t", f"{round(count/total_states * 100, 3)}%") 80 | 81 | 82 | # That's it 83 | print(ans) 84 | -------------------------------------------------------------------------------- /day_16/day_16_p2.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/womogenes/AoC-2022-Solutions/67d5e59c0c1ace5c1a4a2ad94ffc5b2cd0fcb563/day_16/day_16_p2.py -------------------------------------------------------------------------------- /day_18/day_18_p1.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | with open("./day_18.in") as fin: 4 | lines = fin.read().strip().split("\n") 5 | 6 | filled = set() 7 | for line in lines: 8 | x, y, z = map(int, line.split(",")) 9 | filled.add((x, y, z)) 10 | 11 | 12 | ans = 0 13 | for x, y, z in filled: 14 | covered = 0 15 | 16 | pos = np.array((x, y, z)) 17 | 18 | for coord in range(3): 19 | dpos = np.array([0, 0, 0]) 20 | dpos[coord] = 1 21 | 22 | dneg = np.array([0, 0, 0]) 23 | dneg[coord] = -1 24 | 25 | covered += tuple(pos + dpos) in filled 26 | covered += tuple(pos + dneg) in filled 27 | 28 | ans += 6 - covered 29 | 30 | print(ans) 31 | -------------------------------------------------------------------------------- /day_18/day_18_p2.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | from itertools import product 3 | from functools import lru_cache 4 | from tqdm import tqdm 5 | 6 | with open("./day_18.in") as fin: 7 | lines = fin.read().strip().split("\n") 8 | 9 | 10 | min_coord = 1 << 60 11 | max_coord = -(1 << 60) 12 | 13 | filled = set() 14 | for line in lines: 15 | x, y, z = map(int, line.split(",")) 16 | filled.add((x, y, z)) 17 | 18 | for num in [x, y, z]: 19 | min_coord = min(min_coord, num) 20 | max_coord = max(max_coord, num) 21 | 22 | 23 | # Actually just count if exposed to outside 24 | @lru_cache(None) 25 | def exposed(pos): 26 | # do a DFS 27 | stack = [pos] 28 | seen = set() 29 | 30 | if pos in filled: 31 | return False 32 | 33 | while len(stack) > 0: 34 | pop = stack.pop() 35 | 36 | if pop in filled: 37 | continue 38 | 39 | for coord in range(3): 40 | if not (min_coord <= pop[coord] <= max_coord): 41 | return True 42 | 43 | if pop in seen: 44 | continue 45 | seen.add(pop) 46 | 47 | for coord in range(3): 48 | dpos = np.array([0, 0, 0]) 49 | dpos[coord] = 1 50 | dneg = np.array([0, 0, 0]) 51 | dneg[coord] = -1 52 | 53 | stack.append(tuple(pop + dpos)) 54 | stack.append(tuple(pop + dneg)) 55 | 56 | return False 57 | 58 | 59 | ans = 0 60 | for x, y, z in tqdm(filled): 61 | covered = 0 62 | 63 | pos = np.array((x, y, z)) 64 | 65 | for coord in range(3): 66 | dpos = np.array([0, 0, 0]) 67 | dpos[coord] = 1 68 | 69 | dneg = np.array([0, 0, 0]) 70 | dneg[coord] = -1 71 | 72 | for nbr in [tuple(pos + dpos), tuple(pos + dneg)]: 73 | ans += exposed(nbr) 74 | 75 | 76 | print(ans) 77 | -------------------------------------------------------------------------------- /day_20/day_20_p1.py: -------------------------------------------------------------------------------- 1 | from tqdm import tqdm 2 | 3 | with open("./day_20.in") as fin: 4 | lines = fin.read().strip().split("\n") 5 | nums = list(enumerate(map(int, lines))) 6 | 7 | 8 | n = len(nums) 9 | og = nums.copy() 10 | 11 | 12 | def swap(nums, a, b): 13 | assert (0 <= a < n) and (0 <= b < n) 14 | 15 | nums[a], nums[b] = nums[b], nums[a] 16 | return nums 17 | 18 | 19 | for i, x in tqdm(og): 20 | for idx in range(n): 21 | if nums[idx][0] == i: 22 | break 23 | 24 | assert nums[idx][1] == x 25 | 26 | if x < 0: 27 | cur = idx 28 | for _ in range(-x): 29 | nums = swap(nums, cur, (cur - 1) % n) 30 | cur = (cur - 1) % n 31 | 32 | continue 33 | 34 | if x > 0: 35 | cur = idx 36 | for _ in range(x): 37 | nums = swap(nums, cur, (cur + 1) % n) 38 | cur = (cur + 1) % n 39 | 40 | 41 | coords = [1000, 2000, 3000] 42 | 43 | ans = 0 44 | for zero_idx in range(n): 45 | if nums[zero_idx][1] == 0: 46 | break 47 | 48 | for c in coords: 49 | ans += nums[(zero_idx + c) % n][1] 50 | 51 | print(ans) 52 | -------------------------------------------------------------------------------- /day_20/day_20_p2.py: -------------------------------------------------------------------------------- 1 | from tqdm import tqdm 2 | 3 | DECRYPT_KEY = 811589153 4 | 5 | with open("./day_20.in") as fin: 6 | lines = fin.read().strip().split("\n") 7 | nums = list(map(int, lines)) 8 | 9 | for i in range(len(nums)): 10 | nums[i] = (i, nums[i] * DECRYPT_KEY) 11 | 12 | 13 | n = len(nums) 14 | og = nums.copy() 15 | 16 | 17 | def swap(nums, a, b): 18 | assert (0 <= a < n) and (0 <= b < n) 19 | 20 | nums[a], nums[b] = nums[b], nums[a] 21 | return nums 22 | 23 | 24 | for _ in tqdm(range(10)): 25 | for i, x in tqdm(og): 26 | for idx in range(n): 27 | if nums[idx][0] == i: 28 | break 29 | 30 | assert (nums[idx][1]) == x 31 | 32 | x %= (n - 1) 33 | 34 | if x > 0: 35 | cur = idx 36 | for _ in range(x): 37 | nums = swap(nums, cur, (cur + 1) % n) 38 | cur = (cur + 1) % n 39 | 40 | 41 | coords = [1000, 2000, 3000] 42 | 43 | ans = 0 44 | for zero_idx in range(n): 45 | if nums[zero_idx][1] == 0: 46 | break 47 | 48 | for c in coords: 49 | ans += nums[(zero_idx + c) % n][1] 50 | 51 | print(ans) 52 | -------------------------------------------------------------------------------- /day_21/day_21_p1.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from pprint import pprint 3 | 4 | with open("./day_21.in") as fin: 5 | lines = fin.read().strip().split("\n") 6 | 7 | 8 | lookup = {} 9 | 10 | 11 | @lru_cache(None) 12 | def compute(name): 13 | if isinstance(lookup[name], int): 14 | return lookup[name] 15 | 16 | parts = lookup[name] 17 | 18 | left = compute(parts[0]) 19 | right = compute(parts[2]) 20 | 21 | return eval(f"{left}{parts[1]}{right}") 22 | 23 | 24 | for line in lines: 25 | parts = line.split(" ") 26 | 27 | monkey = parts[0][:-1] 28 | 29 | if len(parts) == 2: 30 | lookup[monkey] = int(parts[1]) 31 | else: 32 | lookup[monkey] = parts[1:] 33 | 34 | 35 | pprint(lookup) 36 | 37 | ans = compute("root") 38 | print(ans) 39 | -------------------------------------------------------------------------------- /day_21/day_21_p2.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from sympy import symbols, solve_linear 3 | from sympy.parsing.sympy_parser import parse_expr 4 | 5 | with open("./day_21.in") as fin: 6 | lines = fin.read().strip().split("\n") 7 | 8 | 9 | lookup = {} 10 | 11 | humn = symbols("humn") 12 | 13 | 14 | @lru_cache(None) 15 | def compute(name): 16 | if name == "humn": 17 | return humn 18 | 19 | if isinstance(lookup[name], int): 20 | return lookup[name] 21 | 22 | parts = lookup[name] 23 | 24 | left = compute(parts[0]) 25 | right = compute(parts[2]) 26 | 27 | return parse_expr(f"({left}){parts[1]}({right})") 28 | 29 | 30 | for line in lines: 31 | parts = line.split(" ") 32 | 33 | monkey = parts[0][:-1] 34 | 35 | if len(parts) == 2: 36 | lookup[monkey] = int(parts[1]) 37 | else: 38 | lookup[monkey] = parts[1:] 39 | 40 | 41 | left = compute(lookup["root"][0]) 42 | right = compute(lookup["root"][2]) 43 | 44 | ans = solve_linear(left, right)[1] 45 | print(ans) 46 | -------------------------------------------------------------------------------- /day_22/day_22_p1.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from pprint import pprint 3 | 4 | with open("./day_22.in") as fin: 5 | # Assumes input ends with newline character 6 | parts = fin.read()[:-1].split("\n\n") 7 | 8 | board = parts[0].split("\n") 9 | path = parts[1] 10 | 11 | # Parse the path 12 | idx = 0 13 | commands = [] 14 | cur_num = "" 15 | for idx in range(len(path)): 16 | if path[idx].isdigit(): 17 | cur_num += path[idx] 18 | 19 | else: 20 | commands.append(int(cur_num)) 21 | cur_num = "" 22 | commands.append(path[idx]) 23 | 24 | # If last command is a number 25 | if cur_num != "": 26 | commands.append(int(cur_num)) 27 | 28 | # Possible orientations 29 | dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]] 30 | 31 | 32 | # Parse the board 33 | nrows = len(board) 34 | ncols = max([len(row) for row in board]) 35 | 36 | bound_row = [[ncols, -1] for _ in range(nrows)] 37 | bound_col = [[nrows, -1] for _ in range(ncols)] 38 | 39 | adj = set() 40 | for row, line in enumerate(board): 41 | for col in range(len(line)): 42 | c = line[col] 43 | if c == ".": 44 | adj.add((row, col)) 45 | 46 | if c in [".", "#"]: 47 | bound_row[row][0] = min(bound_row[row][0], col) 48 | bound_row[row][1] = max(bound_row[row][1], col) 49 | bound_col[col][0] = min(bound_col[col][0], row) 50 | bound_col[col][1] = max(bound_col[col][1], row) 51 | 52 | 53 | # Do the instructions 54 | direction = 0 55 | row = 0 56 | col = bound_row[0][0] 57 | 58 | for cmd in commands: 59 | if isinstance(cmd, str): 60 | if cmd == "L": 61 | direction = (direction - 1) % 4 62 | else: 63 | direction = (direction + 1) % 4 64 | 65 | continue 66 | 67 | # Move! 68 | drow, dcol = dirs[direction] 69 | 70 | for _ in range(cmd): 71 | if (row, col) not in adj: 72 | break 73 | 74 | new_row = row + drow 75 | new_col = col + dcol 76 | 77 | if drow != 0: 78 | rbounds = bound_col[col] 79 | height = rbounds[1] - rbounds[0] + 1 80 | new_row = (new_row - rbounds[0]) % height + rbounds[0] 81 | 82 | if dcol != 0: 83 | cbounds = bound_row[row] 84 | width = cbounds[1] - cbounds[0] + 1 85 | new_col = (new_col - cbounds[0]) % width + cbounds[0] 86 | 87 | if (new_row, new_col) not in adj: 88 | break 89 | 90 | row, col = new_row, new_col 91 | 92 | 93 | ans = 1000 * (row + 1) + 4 * (col + 1) + direction 94 | print(ans) 95 | -------------------------------------------------------------------------------- /day_22/day_22_p2.py: -------------------------------------------------------------------------------- 1 | with open("./day_22.in") as fin: 2 | # Assumes input ends with newline character 3 | parts = fin.read()[:-1].split("\n\n") 4 | 5 | board = parts[0].split("\n") 6 | path = parts[1] 7 | 8 | debug = False 9 | 10 | # Parse the path 11 | idx = 0 12 | commands = [] 13 | cur_num = "" 14 | for idx in range(len(path)): 15 | if path[idx].isdigit(): 16 | cur_num += path[idx] 17 | 18 | else: 19 | 20 | commands.append(int(cur_num)) 21 | cur_num = "" 22 | commands.append(path[idx]) 23 | 24 | # If last command is a number 25 | if cur_num != "": 26 | commands.append(int(cur_num)) 27 | 28 | # Possible orientations 29 | dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]] 30 | 31 | 32 | # Parse the board 33 | nrows = len(board) 34 | ncols = max([len(row) for row in board]) 35 | 36 | bound_row = [[ncols, -1] for _ in range(nrows)] 37 | bound_col = [[nrows, -1] for _ in range(ncols)] 38 | 39 | adj = set() 40 | for row, line in enumerate(board): 41 | for col in range(len(line)): 42 | c = line[col] 43 | if c == ".": 44 | adj.add((row, col)) 45 | 46 | if c in [".", "#"]: 47 | bound_row[row][0] = min(bound_row[row][0], col) 48 | bound_row[row][1] = max(bound_row[row][1], col) 49 | bound_col[col][0] = min(bound_col[col][0], row) 50 | bound_col[col][1] = max(bound_col[col][1], row) 51 | 52 | 53 | # Do the instructions 54 | direction = 0 55 | row = 0 56 | col = bound_row[0][0] 57 | 58 | SCALE = 50 # Hard-coded 59 | 60 | # MAP SOME EDGES TOGETHER 61 | edge_map = [ 62 | [[(0, 1), (0, 2), "^"], [(3, 0), (4, 0), "<"]], # good 63 | [[(0, 2), (0, 3), "^"], [(4, 0), (4, 1), "v"]], # good 64 | [[(0, 3), (1, 3), ">"], [(3, 2), (2, 2), ">"]], # good 65 | [[(1, 2), (1, 3), "v"], [(1, 2), (2, 2), ">"]], # good 66 | [[(3, 1), (3, 2), "v"], [(3, 1), (4, 1), ">"]], # good 67 | [[(2, 0), (3, 0), "<"], [(1, 1), (0, 1), "<"]], # good 68 | [[(2, 0), (2, 1), "^"], [(1, 1), (2, 1), "<"]] # good 69 | ] 70 | 71 | 72 | def sign(x): 73 | if x == 0: 74 | return 0 75 | return 1 if x > 0 else -1 76 | 77 | 78 | def add(a, b): 79 | """Because I don't want to have to use numpy""" 80 | a_copy = list(a) 81 | for i in range(len(a)): 82 | a_copy[i] += b[i] 83 | return tuple(a_copy) 84 | 85 | 86 | def mult(a, k): 87 | """Also because I don't want to have to use numpy""" 88 | a_copy = list(a) 89 | for i in range(len(a)): 90 | a_copy[i] *= k 91 | return tuple(a_copy) 92 | 93 | 94 | for i in range(len(edge_map)): 95 | for j in range(2): 96 | edge_map[i][j][2] = ">v<^".index(edge_map[i][j][2]) 97 | for k in range(2): 98 | edge_map[i][j][k] = mult(edge_map[i][j][k], SCALE) 99 | 100 | 101 | def along_edge_dir(edge): 102 | """ 103 | Return the vector along the edge 104 | For instance, (8, 12) -> (8, 8) produces (0, -1) 105 | """ 106 | start, end, _ = edge 107 | return sign(end[0] - start[0]), sign(end[1] - start[1]) 108 | 109 | 110 | # Off-by-one errors if edge is backwards 111 | for i in range(len(edge_map)): 112 | for j in range(2): 113 | edge = edge_map[i][j] 114 | along = along_edge_dir(edge) 115 | 116 | if along[0] < 0 or along[1] < 0: 117 | edge[0] = add(edge[0], along) 118 | edge[1] = add(edge[1], along) 119 | 120 | edge_map[i][j] = edge 121 | 122 | 123 | def off_edge(point, edge, direction): 124 | """ 125 | Did we just walk off this edge? 126 | If so, returns the index along this edge 127 | """ 128 | start, end, edge_dir = edge 129 | 130 | if edge_dir != direction: 131 | return None 132 | 133 | # Up- and left- facing edges need to modify a bit 134 | if direction in [2, 3]: 135 | start = add(start, dirs[direction]) 136 | end = add(end, dirs[direction]) 137 | 138 | # Walk from start vertex to end vertex, can't be that bad right? 139 | idx = 0 140 | 141 | drow, dcol = along_edge_dir(edge) # NOT dirs[direction] 142 | row, col = start[0], start[1] 143 | 144 | for idx in range(SCALE): 145 | # This is all about wrapping at this point 146 | if (row, col) == point: 147 | return idx 148 | 149 | row += drow 150 | col += dcol 151 | 152 | return None 153 | 154 | 155 | def point_at(edge, idx): 156 | assert idx < SCALE 157 | 158 | start, end, edge_dir = edge 159 | drow, dcol = along_edge_dir(edge) 160 | 161 | # FLIP THE OTHER WAY 162 | if edge_dir in [0, 1]: 163 | start = add(start, mult(dirs[edge_dir], -1)) 164 | end = add(end, mult(dirs[edge_dir], -1)) 165 | 166 | return add(start, mult((drow, dcol), idx)) 167 | 168 | 169 | def wrap(row, col, direction): 170 | """ 171 | Return new point and new direction 172 | """ 173 | idx = None 174 | 175 | for e1, e2 in edge_map: 176 | # Did we just walk off this edge? 177 | idx = off_edge((row, col), e1, direction) 178 | if isinstance(idx, int): 179 | debug and (f"match with {(row, col), e1, direction}") 180 | break 181 | 182 | idx = off_edge((row, col), e2, direction) 183 | if isinstance(idx, int): 184 | debug and (idx) 185 | debug and (f"match with {(row, col), e2, direction}") 186 | e1, e2 = e2, e1 # Swap them around 187 | break 188 | 189 | if idx == None: 190 | return (row, col, direction) 191 | 192 | debug and (e2, idx) 193 | 194 | assert idx == off_edge((row, col), e1, direction) 195 | 196 | # We go from e1 to e2 197 | new_point = point_at(e2, idx) 198 | 199 | # e2[2] is the direction of the edge pointing OUT 200 | new_direction = [2, 3, 0, 1][e2[2]] 201 | # This remaps it to point inwards 202 | 203 | return new_point[0], new_point[1], new_direction 204 | 205 | 206 | """ row, col = 3, 6 207 | debug and (wrap(row, col, 3)) 208 | 209 | exit() """ 210 | 211 | 212 | for cmd in commands: 213 | debug and (f"\n=== parsing [{cmd}] ===") 214 | 215 | if isinstance(cmd, str): 216 | if cmd == "L": 217 | direction = (direction - 1) % 4 218 | else: 219 | direction = (direction + 1) % 4 220 | 221 | debug and (f" switched direction to {dirs[direction]}") 222 | 223 | continue 224 | 225 | # Move! 226 | for _ in range(cmd): 227 | drow, dcol = dirs[direction] 228 | 229 | debug and (f" currently at {row}, {col}") 230 | debug and ( 231 | f" heading in direction {drow}, {dcol} [direction {direction}]") 232 | if (row, col) not in adj: 233 | break 234 | 235 | new_row, new_col = row + drow, col + dcol 236 | 237 | debug and ( 238 | f" attempting to move to {new_row}, {new_col}, dir={direction}") 239 | 240 | new_new_row, new_new_col, new_direction = wrap( 241 | new_row, new_col, direction) 242 | 243 | if (new_new_row, new_new_col) != (new_row, new_col): 244 | debug and (f" [!] wrapped to {new_new_row}, {new_new_col}") 245 | 246 | if (new_new_row, new_new_col) not in adj: 247 | debug and (f" [*] hit a barrier, stopping") 248 | break 249 | 250 | row, col = new_new_row, new_new_col 251 | direction = new_direction 252 | 253 | 254 | ans = 1000 * (row + 1) + 4 * (col + 1) + direction 255 | print(ans) 256 | -------------------------------------------------------------------------------- /day_23/day_23_p1.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | with open("./day_23.in") as fin: 4 | grid = fin.read().strip().split("\n") 5 | 6 | 7 | checks = [ 8 | [[1, 2, 3], 2], 9 | [[7, 6, 5], 6], 10 | [[5, 4, 3], 4], 11 | [[1, 0, 7], 0] 12 | ] 13 | 14 | 15 | n = len(grid) 16 | m = len(grid[0]) 17 | 18 | elves = set() 19 | 20 | for i in range(n): 21 | for j in range(m): 22 | if grid[i][j] == "#": 23 | elves.add((i, j)) 24 | 25 | 26 | dirs = [ 27 | [0, 1], 28 | [-1, 1], 29 | [-1, 0], 30 | [-1, -1], 31 | [0, -1], 32 | [1, -1], 33 | [1, 0], 34 | [1, 1] 35 | ] 36 | 37 | 38 | def get_bounds(elves): 39 | min_row = 1 << 60 40 | max_row = -(1 << 60) 41 | min_col = 1 << 60 42 | max_col = -(1 << 60) 43 | 44 | for row, col in elves: 45 | min_row = min(min_row, row) 46 | max_row = max(max_row, row) 47 | 48 | min_col = min(min_col, col) 49 | max_col = max(max_col, col) 50 | 51 | return min_row, max_row, min_col, max_col 52 | 53 | 54 | def print_elves(elves): 55 | min_row, max_row, min_col, max_col = get_bounds(elves) 56 | for row in range(min_row, max_row + 1): 57 | for col in range(min_col, max_col + 1): 58 | if (row, col) in elves: 59 | print("#", end="") 60 | else: 61 | print(".", end="") 62 | 63 | print() 64 | 65 | 66 | for round in range(10): 67 | # Stage 1: make proposals 68 | propose = {} 69 | proposed = defaultdict(int) 70 | for elf in elves: 71 | row, col = elf 72 | 73 | # If elf has no neighbors, continue 74 | good = False 75 | for drow, dcol in dirs: 76 | if (row + drow, col + dcol) in elves: 77 | good = True 78 | break 79 | if not good: 80 | continue 81 | 82 | for check_dirs, propose_dir in checks: 83 | good = True 84 | for d in check_dirs: 85 | drow, dcol = dirs[d] 86 | if (row + drow, col + dcol) in elves: 87 | good = False 88 | break 89 | 90 | if not good: 91 | continue 92 | 93 | drow, dcol = dirs[propose_dir] 94 | propose[elf] = (row + drow, col + dcol) 95 | proposed[propose[elf]] += 1 96 | break 97 | 98 | # Stage 2: do the proposals 99 | new_elves = set() 100 | for elf in elves: 101 | if elf in propose: 102 | new_loc = propose[elf] 103 | if proposed[new_loc] > 1 or proposed[new_loc] == 0: 104 | new_elves.add(elf) 105 | else: 106 | new_elves.add(new_loc) 107 | else: 108 | new_elves.add(elf) 109 | 110 | # Rotate stuff 111 | checks = checks[1:] + [checks[0]] 112 | elves = new_elves 113 | 114 | 115 | # Find bounding box 116 | min_row, max_row, min_col, max_col = get_bounds(elves) 117 | 118 | print(min_row, max_row) 119 | print(min_col, max_col) 120 | 121 | print((max_row - min_row + 1) * (max_col - min_col + 1) - len(elves)) 122 | -------------------------------------------------------------------------------- /day_23/day_23_p2.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | 3 | with open("./day_23.in") as fin: 4 | grid = fin.read().strip().split("\n") 5 | 6 | 7 | checks = [ 8 | [[1, 2, 3], 2], 9 | [[7, 6, 5], 6], 10 | [[5, 4, 3], 4], 11 | [[1, 0, 7], 0] 12 | ] 13 | 14 | 15 | n = len(grid) 16 | m = len(grid[0]) 17 | 18 | elves = set() 19 | 20 | for i in range(n): 21 | for j in range(m): 22 | if grid[i][j] == "#": 23 | elves.add((i, j)) 24 | 25 | 26 | dirs = [ 27 | [0, 1], 28 | [-1, 1], 29 | [-1, 0], 30 | [-1, -1], 31 | [0, -1], 32 | [1, -1], 33 | [1, 0], 34 | [1, 1] 35 | ] 36 | 37 | 38 | def get_bounds(elves): 39 | min_row = 1 << 60 40 | max_row = -(1 << 60) 41 | min_col = 1 << 60 42 | max_col = -(1 << 60) 43 | 44 | for row, col in elves: 45 | min_row = min(min_row, row) 46 | max_row = max(max_row, row) 47 | 48 | min_col = min(min_col, col) 49 | max_col = max(max_col, col) 50 | 51 | return min_row, max_row, min_col, max_col 52 | 53 | 54 | def print_elves(elves): 55 | min_row, max_row, min_col, max_col = get_bounds(elves) 56 | for row in range(min_row, max_row + 1): 57 | for col in range(min_col, max_col + 1): 58 | if (row, col) in elves: 59 | print("#", end="") 60 | else: 61 | print(".", end="") 62 | 63 | print() 64 | 65 | 66 | round = 1 67 | 68 | while True: 69 | # Stage 1: make proposals 70 | propose = {} 71 | proposed = defaultdict(int) 72 | for elf in elves: 73 | row, col = elf 74 | 75 | # If elf has no neighbors, continue 76 | good = False 77 | for drow, dcol in dirs: 78 | if (row + drow, col + dcol) in elves: 79 | good = True 80 | break 81 | if not good: 82 | continue 83 | 84 | for check_dirs, propose_dir in checks: 85 | good = True 86 | for d in check_dirs: 87 | drow, dcol = dirs[d] 88 | if (row + drow, col + dcol) in elves: 89 | good = False 90 | break 91 | 92 | if not good: 93 | continue 94 | 95 | drow, dcol = dirs[propose_dir] 96 | propose[elf] = (row + drow, col + dcol) 97 | proposed[propose[elf]] += 1 98 | break 99 | 100 | if len(propose) == 0: 101 | print(round) 102 | break 103 | 104 | # Stage 2: do the proposals 105 | new_elves = set() 106 | for elf in elves: 107 | if elf in propose: 108 | new_loc = propose[elf] 109 | if proposed[new_loc] > 1 or proposed[new_loc] == 0: 110 | new_elves.add(elf) 111 | else: 112 | new_elves.add(new_loc) 113 | else: 114 | new_elves.add(elf) 115 | 116 | # Rotate stuff 117 | checks = checks[1:] + [checks[0]] 118 | elves = new_elves 119 | 120 | round += 1 121 | -------------------------------------------------------------------------------- /day_24/day_24_p1.py: -------------------------------------------------------------------------------- 1 | from math import lcm 2 | from heapq import heappop, heappush 3 | 4 | with open("./day_24.in") as fin: 5 | lines = fin.read().strip().split("\n") 6 | 7 | 8 | n = len(lines) 9 | m = len(lines[0]) 10 | 11 | start = (0, 1) 12 | end = (n - 1, m - 2) 13 | 14 | # Things wrap after lcm(n, m) steps 15 | period = lcm(n - 2, m - 2) 16 | 17 | # Calculate blizzard positions after every point 18 | arrows = ">v<^" 19 | 20 | dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]] 21 | 22 | storms = set() 23 | for i in range(n): 24 | for j in range(m): 25 | c = lines[i][j] 26 | if c in arrows: 27 | storms.add((i, j, arrows.index(c))) 28 | 29 | 30 | states = [None] * period 31 | states[0] = storms 32 | for t in range(1, period): 33 | new_storms = set() 34 | 35 | for storm in storms: 36 | row, col, d = storm 37 | drow, dcol = dirs[d] 38 | new_row, new_col = row + drow, col + dcol 39 | 40 | if new_row == 0: 41 | assert d == 3 42 | new_row = n - 2 43 | elif new_row == n - 1: 44 | assert d == 1 45 | new_row = 1 46 | 47 | if new_col == 0: 48 | assert d == 2 49 | new_col = m - 2 50 | elif new_col == m - 1: 51 | assert d == 0 52 | new_col = 1 53 | 54 | new_storms.add((new_row, new_col, d)) 55 | 56 | states[t] = new_storms 57 | storms = new_storms # Update 58 | 59 | 60 | def grid_state(storms): 61 | ans = [["."] * m for _ in range(n)] 62 | for i in range(n): 63 | for j in range(m): 64 | if (i in [0, n - 1] or j in [0, m - 1]) and not (i, j) in [start, end]: 65 | ans[i][j] = "#" 66 | continue 67 | 68 | for d in range(4): 69 | if (i, j, d) in storms: 70 | if isinstance(ans[i][j], int): 71 | ans[i][j] += 1 72 | elif ans[i][j] != ".": 73 | ans[i][j] = 2 74 | else: 75 | ans[i][j] = arrows[d] 76 | 77 | return ans 78 | 79 | 80 | def print_grid(grid): 81 | print("\n".join(["".join(map(str, row)) for row in grid])) 82 | 83 | 84 | def occupied(loc, st): 85 | for d in range(4): 86 | if (loc[0], loc[1], d) in st: 87 | return True 88 | return False 89 | 90 | 91 | # Go go go 92 | pq = [(0, start)] 93 | visited = set() 94 | 95 | while len(pq) > 0: 96 | top = heappop(pq) 97 | if top in visited: 98 | continue 99 | visited.add(top) 100 | 101 | t, loc = top 102 | row, col = loc 103 | 104 | assert not occupied(loc, states[t % period]) 105 | 106 | if loc == end: 107 | print(t) 108 | break 109 | 110 | # Go through neighbors 111 | for drow, dcol in (dirs + [[0, 0]]): 112 | new_row, new_col = row + drow, col + dcol 113 | new_loc = (new_row, new_col) 114 | 115 | # Within bounds? 116 | if (not new_loc in [start, end]) \ 117 | and not (1 <= new_row <= n - 2 118 | and 1 <= new_col <= m - 2): 119 | continue 120 | 121 | # Check if hitting a blizzard 122 | if occupied(new_loc, states[(t + 1) % period]): 123 | continue 124 | 125 | new_state = (t + 1, new_loc) 126 | heappush(pq, new_state) 127 | -------------------------------------------------------------------------------- /day_24/day_24_p2.py: -------------------------------------------------------------------------------- 1 | from math import lcm 2 | from heapq import heappop, heappush 3 | from icecream import ic 4 | 5 | with open("./day_24.in") as fin: 6 | lines = fin.read().strip().split("\n") 7 | 8 | 9 | n = len(lines) 10 | m = len(lines[0]) 11 | 12 | start = (0, 1) 13 | end = (n - 1, m - 2) 14 | 15 | # Things wrap after lcm(n, m) steps 16 | period = lcm(n - 2, m - 2) 17 | 18 | # Calculate blizzard positions after every point 19 | arrows = ">v<^" 20 | 21 | dirs = dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]] 22 | 23 | storms = set() 24 | for i in range(n): 25 | for j in range(m): 26 | c = lines[i][j] 27 | if c in arrows: 28 | storms.add((i, j, arrows.index(c))) 29 | 30 | 31 | states = [None] * period 32 | states[0] = storms 33 | for t in range(1, period): 34 | new_storms = set() 35 | 36 | for storm in storms: 37 | row, col, d = storm 38 | drow, dcol = dirs[d] 39 | new_row, new_col = row + drow, col + dcol 40 | 41 | if new_row == 0: 42 | assert d == 3 43 | new_row = n - 2 44 | elif new_row == n - 1: 45 | assert d == 1 46 | new_row = 1 47 | 48 | if new_col == 0: 49 | assert d == 2 50 | new_col = m - 2 51 | elif new_col == m - 1: 52 | assert d == 0 53 | new_col = 1 54 | 55 | new_storms.add((new_row, new_col, d)) 56 | 57 | states[t] = new_storms 58 | storms = new_storms # Update 59 | 60 | 61 | def grid_state(storms): 62 | ans = [["."] * m for _ in range(n)] 63 | for i in range(n): 64 | for j in range(m): 65 | if (i in [0, n - 1] or j in [0, m - 1]) and not (i, j) in [start, end]: 66 | ans[i][j] = "#" 67 | continue 68 | 69 | for d in range(4): 70 | if (i, j, d) in storms: 71 | if isinstance(ans[i][j], int): 72 | ans[i][j] += 1 73 | elif ans[i][j] != ".": 74 | ans[i][j] = 2 75 | else: 76 | ans[i][j] = arrows[d] 77 | 78 | return ans 79 | 80 | 81 | def print_grid(grid): 82 | print("\n".join(["".join(map(str, row)) for row in grid])) 83 | 84 | 85 | def occupied(loc, st): 86 | for d in range(4): 87 | if (loc[0], loc[1], d) in st: 88 | return True 89 | return False 90 | 91 | 92 | # Go go go 93 | pq = [(0, start, False, False)] 94 | visited = set() 95 | 96 | while len(pq) > 0: 97 | top = heappop(pq) 98 | if top in visited: 99 | continue 100 | visited.add(top) 101 | 102 | t, loc, hit_end, hit_start = top 103 | row, col = loc 104 | 105 | assert not (hit_start and not hit_end) 106 | 107 | assert not occupied(loc, states[t % period]) 108 | 109 | if loc == end: 110 | if hit_end and hit_start: 111 | print(t) 112 | break 113 | 114 | hit_end = True 115 | 116 | if loc == start: 117 | if hit_end: 118 | hit_start = True 119 | 120 | # Go through neighbors 121 | for drow, dcol in (dirs + [[0, 0]]): 122 | new_row, new_col = row + drow, col + dcol 123 | new_loc = (new_row, new_col) 124 | 125 | # Within bounds? 126 | if (not new_loc in [start, end]) \ 127 | and not (1 <= new_row <= n - 2 128 | and 1 <= new_col <= m - 2): 129 | continue 130 | 131 | # Check if hitting a blizzard 132 | if occupied(new_loc, states[(t + 1) % period]): 133 | continue 134 | 135 | new_state = (t + 1, new_loc, hit_end, hit_start) 136 | heappush(pq, new_state) 137 | -------------------------------------------------------------------------------- /day_25/day_25_p1.py: -------------------------------------------------------------------------------- 1 | with open("./day_25.in") as fin: 2 | lines = fin.read().strip().split() 3 | 4 | # String to digit 5 | s2d = { 6 | "0": 0, 7 | "1": 1, 8 | "2": 2, 9 | "-": -1, 10 | "=": -2 11 | } 12 | 13 | d2s = {d: s for s, d in s2d.items()} 14 | 15 | 16 | def parse_snafu(s): 17 | ans = 0 18 | d = len(s) 19 | p = 1 20 | s = s[::-1] 21 | for i in range(d): 22 | ans += p * s2d[s[i]] 23 | p *= 5 24 | return ans 25 | 26 | 27 | ans = 0 28 | for line in lines: 29 | ans += parse_snafu(line) 30 | 31 | 32 | def int_to_snafu(n): 33 | res = "" 34 | while n > 0: 35 | d = ((n + 2) % 5) - 2 36 | res += d2s[d] 37 | n -= d 38 | n //= 5 39 | return res[::-1] 40 | 41 | 42 | print(int_to_snafu(ans)) 43 | -------------------------------------------------------------------------------- /day_25/day_25_p2.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/womogenes/AoC-2022-Solutions/67d5e59c0c1ace5c1a4a2ad94ffc5b2cd0fcb563/day_25/day_25_p2.py -------------------------------------------------------------------------------- /new_day.py: -------------------------------------------------------------------------------- 1 | # Generate a new day with all files and stuff 2 | import os 3 | 4 | import datetime as dt 5 | day = (dt.datetime.now() + dt.timedelta(hours=4, minutes=30)).day 6 | year = dt.datetime.now().year 7 | 8 | prefix = f"day_{str(day).zfill(2)}" 9 | 10 | if not prefix in os.listdir(): 11 | os.mkdir(prefix) 12 | os.chdir(prefix) 13 | with open(f"./{prefix}_p1.py", "w") as fout: 14 | fout.write(f"""with open("./{prefix}.in") as fin: 15 | lines = fin.read().strip().split() 16 | """) 17 | 18 | open(f"./{prefix}_p2.py", "w") 19 | 20 | open(f"./{prefix}.in", "w") 21 | --------------------------------------------------------------------------------