Compare commits

...

24 commits

Author SHA1 Message Date
5b090b2a65 2019: d25: ex2: add solution 2024-12-27 18:03:04 -05:00
b24dbd4614 2019: d25: ex2: add input 2024-12-27 18:03:00 -05:00
455b1250a8 2019: d25: ex1: add solution 2024-12-27 18:02:54 -05:00
6d7f16f4a9 2019: d25: ex1: add input 2024-12-27 18:02:43 -05:00
22ad878348 2019: d24: ex2: add solution 2024-12-27 15:41:06 -05:00
b53a813f01 2019: d24: ex2: add input 2024-12-27 15:40:58 -05:00
fc82aa39d9 2019: d24: ex1: add solution 2024-12-27 15:40:53 -05:00
3b038197ac 2019: d24: ex1: add input 2024-12-27 15:40:47 -05:00
1928f14449 2019: d23: ex2: add solution 2024-12-27 00:18:09 -05:00
965d8ceb41 2019: d23: ex2: add input 2024-12-27 00:17:57 -05:00
22593a20cd 2019: d23: ex1: add solution 2024-12-27 00:17:51 -05:00
7ee937a858 2019: d23: ex1: add input 2024-12-27 00:17:45 -05:00
6d21ad0bc0 2019: d22: ex2: add solution 2024-12-26 22:53:16 -05:00
7164cca51e 2019: d22: ex2: add input 2024-12-26 22:52:58 -05:00
a4f9db4828 2019: d22: ex1: add solution 2024-12-26 22:52:47 -05:00
e0a287938b 2019: d22: ex1: add input 2024-12-26 22:52:30 -05:00
b74078e68d 2019: d21: ex2: add solution 2024-12-26 22:06:43 -05:00
b337e24f76 2019: d21: ex2: add input 2024-12-26 22:06:32 -05:00
8a8f735399 2019: d21: ex1: add solution 2024-12-26 22:06:26 -05:00
9036daa682 2019: d21: ex1: add input 2024-12-26 22:06:09 -05:00
31339932f8 2019: d20: ex2: add solution 2024-12-26 21:41:13 -05:00
1d1d7a9ef4 2019: d20: ex2: add input 2024-12-26 21:39:37 -05:00
e8e90e64ed 2019: d20: ex1: add solution 2024-12-26 21:39:32 -05:00
944a4cc401 2019: d20: ex1: add input 2024-12-26 21:39:27 -05:00
24 changed files with 2442 additions and 0 deletions

106
2019/d20/ex1/ex1.py Executable file
View file

@ -0,0 +1,106 @@
#!/usr/bin/env python
import heapq
import sys
from collections import defaultdict
from typing import Iterator, NamedTuple
class Point(NamedTuple):
x: int
y: int
def neighbours(self) -> Iterator["Point"]:
for dx, dy in (
(-1, 0),
(1, 0),
(0, -1),
(0, 1),
):
yield Point(self.x + dx, self.y + dy)
Graph = dict[Point, set[Point]]
def solve(input: str) -> int:
def post_process_gates(
letters: dict[Point, str], paths: set[Point]
) -> dict[str, set[Point]]:
res: dict[str, set[Point]] = defaultdict(set)
for p1, first in letters.items():
for dx, dy in ((0, 1), (1, 0)):
p2 = Point(p1.x + dx, p1.y + dy)
if p2 not in letters:
continue
gate = first + letters[p2]
p0 = Point(p1.x - dx, p1.y - dy)
p3 = Point(p2.x + dx, p2.y + dy)
res[gate] |= {p0, p3} & paths
return res
def to_graph(paths: set[Point], gates: dict[str, set[Point]]) -> Graph:
res: dict[Point, set[Point]] = defaultdict(set)
for p in paths:
res[p] |= set(p.neighbours()) & paths
for gate, points in gates.items():
if len(points) == 1:
assert gate in ("AA", "ZZ") # Sanity check
continue
for p in points:
res[p] |= points
res[p].remove(p)
return res
def parse(input: list[str]) -> tuple[Graph, Point, Point]:
letters: dict[Point, str] = {}
paths: set[Point] = set()
for x, line in enumerate(input):
for y, c in enumerate(line):
if c == "#" or c == " ":
continue
p = Point(x, y)
if c == ".":
paths.add(p)
continue
letters[p] = c
gates = post_process_gates(letters, paths)
graph = to_graph(paths, post_process_gates(letters, paths))
return graph, next(iter(gates["AA"])), next(iter(gates["ZZ"]))
def djikstra(start: Point, end: Point, graph: Graph) -> int:
# Priority queue of (distance, point)
queue = [(0, start)]
seen: set[Point] = set()
while len(queue) > 0:
dist, p = heapq.heappop(queue)
if p == end:
return dist
# We must have seen p with a smaller distance before
if p in seen:
continue
# First time encountering p, must be the smallest distance to it
seen.add(p)
# Add all neighbours to be visited
for n in graph[p]:
heapq.heappush(queue, (dist + 1, n))
assert False # Sanity check
graph, start, end = parse(input.splitlines())
return djikstra(start, end, graph)
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

115
2019/d20/ex1/input Normal file
View file

@ -0,0 +1,115 @@
A N N I K F W
P N A J D A L
###############################.#######.#########.#########.#####.###.#.#####################################
#.#.#.#.#...#.....#...#.........#.....#.....#.....#.......#.....#.....#...#.#...#...#.#.............#.......#
#.#.#.#.###.#####.###.#########.#.###.#.#######.###.#.#####.#.###.###.#.#.#.#.#.#.###.#.#############.#######
#...............#.#...#...........#...#...#.....#...#.#.....#.#.#.#...#.#.....#...#.#.......#...#.#...#...#.#
#####.###.#####.#.###.###.###.#####.###.#####.#.#.#.###.#.###.#.###.#####.###.###.#.#.###.###.###.###.###.#.#
#.....#.#.#.#.....#.#.#.#...#.....#...#.....#.#.#.#...#.#.#...#.......#...#.#...#.#...#.......#.#.....#.#.#.#
#######.###.#####.#.#.#.#.#.#########.#.#######.#.###.#.#####.#.#########.#.#.#####.###.#.#.#.#.#.#####.#.#.#
#.#...............#.#.....#.#.....#...#.....#.......#.#...#...#.....#...#.#...#...#...#.#.#.#.........#.....#
#.#.###.#.###.###.#.#.#.#######.#.#.#####.###########.#####.#.#.#.#####.###.#.#.###.#######.###########.#####
#.#.#...#.#...#.#.....#.#.......#...#.....#...#.....#.#.....#.#.#.#.......#.#...........#.....#.#.#.#.#.....#
#.###########.#.#.#.###.#######.#######.#####.#.###.#.###.###.#.#########.###.#####.#.#.###.#.#.#.#.#.#.#####
#.#.....#...#.#...#...#.#.#...#...#.......#.......#...#...#...#.....#.............#.#.#.#...#.#.....#.#...#.#
#.#.###.###.#.#.###.###.#.###.#.#######.#########.###########.#.#.###.###.###.#####.###############.#.#.###.#
#.....#.#.#...#.#...#.....#.#.....#.#.#.....#.#...#.......#...#.#...#.#...#...#.#.#.....#...#.#...#...#.....#
###.#####.###.#####.#######.#.#.###.#.###.###.#.#.###.#.#####.###.#######.###.#.#.#######.###.###.#.#####.###
#.........#...#.......#.......#.#.#...#.......#.#...#.#.....#.#.....#.....#.........#.#.#...#...#.......#...#
###.#####.###.#.#.###.#.###.#.#.#.#.#########.#.#######.###.#.#.#######.#####.###.###.#.#.###.###.#####.#.#.#
#...#.#.#.#.#.#.#.#...#.#.#.#.#.......#.#.....#.#.#...#.#.#.#.#.#.#.#.....#.#.#.#.#.........#...#.#.#.#...#.#
###.#.#.###.#######.#####.#.###.###.###.###.#.#.#.#.###.#.#.#.#.#.#.###.###.###.#####.#######.#####.#.###.###
#.....#...#...#.#.#.#.....#.#.....#.#.#.....#.#.....#.....#...#.....#...#.....#...#...#.....#.#...#.#.#.....#
###.###.###.#.#.#.#######.#####.#####.###.#.###.#.#.#####.#####.#######.###.###.###.#.###.###.###.#.#.###.#.#
#.......#.#.#.....#.#.#.#...#.#...#.#.....#.#.#.#.#.#.#.#.#...#...#.#...............#.#.#...#.#.#.#.......#.#
###.#####.###.###.#.#.#.#.###.#.###.###.#####.#.###.#.#.#.#.#.#.###.#.#.#####.#.#.#.###.#.###.#.#.#.#.#.#####
#.#...#.....#.#.#.................#.#...#.........#.#.#.#...#.#.....#.#.#...#.#.#.#.........#.#...#.#.#...#.#
#.#.###.###.###.###.#.#.#.#####.###.###.#######.#####.#.#.###.#####.#.#.#.#.#.#.#.#.#.#####.#.#.#.#####.#.#.#
#.#.....#...#.....#.#.#.#.#...........#...#.......#.......#.....#...#.#.#.#...#.#.#.#.#...#.#.#.#.#.#...#...#
#.#.#######.#####.###.###.#########.#####.#####.###########.#####.#.#####.#############.#####.###.#.###.#####
#.........#.#...#...#...#.# J T S F N W A #...#.#.#...#.#.#.....#.#.#
###.#####.#####.#.######### R R E A N P P #.###.#.###.#.#.###.#.#.#.#
#...#.#.#...#.#...#...#...# #.#...#.......#...#.#.....#
###.#.#.#####.###.###.#.### #.###.#####.#####.###.#####
#...#.#.#.#...#...#.#...#..HV #.#.#.#...#...#...#.#...#.#
#.#.#.#.#.###.###.#.###.#.# #.#.#.###.#.#####.#.#.###.#
#.#.......#.....#.....#...# #.#...#.#.........#.....#.#
#.###.#######.#.#.#.#.#.#.# #.#.#.#.#.#######.#.#.###.#
#.#...#.#...#.#.#.#.#.#.#.# QJ..#.#.#.#.......#...#...#..KV
#.#.###.#.###.###.#.###.### #.#.#.#.#.###.#########.#.#
FZ..#...............#.......# #...#.#.....#.#.....#...#..ZZ
#####.#.#.#####.###.#.##### #.###.#.#########.###.###.#
JR..#.#.#.#...#.#...#.#.#....HR #.#.#...#.....#.#.#.......#
#.#.#########.#########.### ###.#####.#####.#.#########
#...#.#.#.#.....#.#.......# IJ....#...#.............#.#.#
#.#.#.#.#.###.#.#.###.##### ###.#.#####.###.#.#.###.#.#
#.#...........#.......#.#.# #.....#.....#...#.#...#....SE
#########.###.#########.#.# ###.###.###.#######.###.###
#.......#...#.#.#...#.....# #.#.....#.....#.....#.....#
#.#.###########.#.#.#.#.### #.###.#.###########.#####.#
#.#...#.#...#.#.#.#...#...# #.#.#.#.#.....#...........#
#.#.###.#.###.#.#.#####.### #.#.#####.###############.#
AB..#.......#.........#.....# #.............#.........#.#
#####.###.###.#.#####.##### #.#.###.###.#######.#.#.###
#...#.#.......#...#........KV KD..#.#.#.#.......#.#.#.#...#
#.#########.###.###.#.#.### #.###.#####.###.#.###.#.###
QJ........#.#...#.#...#.#.#.# #.#.....#...#.#.....#.#...#
#.#.#.#.#.#.#########.###.# #.###.#######.###.#.#.#.#.#
#.#.#.#.#.#.#.#...#...#.#.# #.#.....#.#...#.#.#...#.#..YO
#####.#.#.###.#.#######.#.# #####.###.###.#.###########
#.....#...#.#...#.#........RF KR..........#................AX
#########.#.###.#.###.##### #.#.###.###.#.#.###.###.#.#
#.#...#...................# #.#.#.....#.#.#...#.#...#.#
#.###.#####.#.#.#.#.####### #.#####.###.#########.#####
#.........#.#.#.#.#.#.....# #.#.#.......#.#...#.#.#...#
###.#.#########.#####.###.# ###.#.#######.#.###.###.###
KR....#.#.......#.#.#.#.#...# #.....#.....#.............#
#####.#.###.#####.#.#.#.### ###.#####.#.###.###.#.#.#.#
#.....#.#.....#.#...#.#....HM #.#...#.#.#.....#.#.#.#.#..XY
#.#####.#.#####.#.#.#.##### #.#####.#.#.#.###.#.###.###
#.......#.........#.....#.# AI..#.#.#...#.#.#.#.#.#.#...#
#################.###.#.#.# #.#.#.###.#.###.#.###.#.###
#...........#...#.#...#.#.# #.........#.#...#.#.....#.#
#.###.#.###.#.#.#.#.#####.# #.###.#########.#.#######.#
#.#.#.#...#...#.#.#.....#..ZW #.#...#.....#...#.....#....WP
#.#######.#.#############.# #######.#.#.#.#.#.#.#.#.###
AA......#...#.......#.....#.# XY....#...#.#...#...#.#.#...#
###.#######.###.#.###.###.# ###.#.#####.#####.###.#.#.#
HV......#.#...#.#.#.........# #.#...#.........#.#.#.#.#.#
#######.#####.###.#.###.### #.#.#######.###.###.#.###.#
RF..#.#...#.#.#.#.#.#.#.#.#..KH #.....#.......#...#.......#
#.#.###.#.#.#.#.#####.###.# #.#######.#.###.#####.#####
#.#.....#.....#...#.....#.# #.#.#.....#.#.....#.......#
#.#.#.###.###.#.#.#.###.#.# #.#.#.#.#.#.###.#####.#.###
#...#.....#.#...#...#.....# #.#.#.#.#.#.#.......#.#...#
#####.#.#.#.#.#####.###.#.# A W N F Y U A ###.#####.#.###.#####.#.#.#
#.....#.#...#...#.....#.#.# B L A Z O E X #.#.#...#.#.#.#.#...#.#.#.#
###.#.###.#######.#.#####.#######.###.#########.#######.#######.#####.#######.#####.#.#.###.###.#.###.#.#.###
#...#.#.....#.....#.....#.......#...#.#.......#.#.#.....#...#.#.....#...#.....#...........#...#.......#.#...#
#.###.###.#.###.#.###.###.#.#####.###.#.###.###.#.###.###.#.#.###.###.#.#.#####.###.#.#.#######.#####.#######
#.#.....#.#.#...#.#.....#.#.#.....#.....#...#...#.#.....#.#.#.......#.#.#.........#.#.#.#.#.......#.#.#.....#
#####.#####.#####.###.#.#########.#######.#.#.###.#####.#.#.#.#########.###.###.#.#.#####.###.#####.#.###.###
#.........#.#.....#...#.#.#.........#.....#.#.......#.....#.#.......#...#...#...#.#.......#.#.#.............#
#####.###.#######.#####.#.###.#####.#.#############.#######.#######.###.#.#####.#.###.###.#.#####.###.#####.#
#.......#.#...#.....#.......#.#.#.#.#.....#.#.......#.....#...#.#.....#.#.#...#.#.#.....#.....#...#.......#.#
#.###.#####.#.###.#.#.#.#######.#.#.###.###.#.#####.###.#.#.###.###.###.#.#.#########.#####.#.###.#.###.#.#.#
#.#.........#.#.#.#.#.#.#.....#.....#.......#.....#.#...#...#...#.#...#.#...........#.#.#.#.#...#.#.#.#.#.#.#
#.#.#.#.#.###.#.#############.###.#.#.#######.#####.#.###.###.###.#.###.#.#.#.#####.#.#.#.#.#########.#.#####
#.#.#.#.#...#.#.#.....#...#.#...#.#.#...#.........#.#.#.......#.....#...#.#.#.#.....#...#.#.......#.#.....#.#
#.#.#.#.#.#####.###.#####.#.#.#.###.#.#.#.#######.#.#.###.#.#####.###.###.###############.#.#######.#####.#.#
#.#.#.#.#.....#.........#.#.#.#.#...#.#.#.#.....#.#.#.#...#.#.......#.#...#.#.......#...#.#...#...#.........#
###.#.#.###.###.#.#.###.#.#.###.#.###.#####.#.###.###.#############.#.###.#.#####.###.###.#.#####.#.#####.#.#
#...#.#.#.....#.#.#.#...............#.#...#.#.......#.........#.....#.#.#.......#.....#.#.........#.#.....#.#
#######################.#.#########.#.###.#####.###.#.###########.#.#.#.###.#####.#####.###.#########.#.###.#
#.#.......#.............#.#.#.......#.......#.#.#.#.#...#...#.....#.#...#.....#.#.#...#.#.#...#.....#.#...#.#
#.#####.#.###.#############.#.###.#####.#####.###.#.#.###.#####.#######.#.#####.#.###.#.#.#######.#####.#.#.#
#.......#.......#.........#.#.#.....#.......#.......#.#.#.#.#.#.......#.#.....#...#.#...#.#.....#.#.#...#.#.#
#.###.###.#.#.###########.#.#######.#.#####.#######.#.#.#.#.#.#####.#.#.###.#.###.#.#.#.#.#.#.#.#.#.###.#####
#...#.#...#.#.#.....#...#.....#...#.#.#.#...#...#...#.........#.....#.#.#...#...#.....#.....#.#.....#.#.....#
###.###.###.#####.#####.#.###.#.###.#.#.#######.###.#.#.###.#.#####.###.#.#######.#####.#######.#####.#####.#
#...#...#...#...........#.#.........#.......#.....#.#.#.#...#.#.#.....#.#...#.#.#.#.#...#.....#.#.......#.#.#
#.#.###.###.#####.#####.#####.#########.###.###.###.#####.#####.###.###.###.#.#.#.#.#####.#.###.#.#.#.#.#.###
#.#.#...#.......#...#.............#.......#.#.......#.........#.....#.....#...............#...#...#.#.#.....#
#################################.###.#########.#######.#########.###.#######.###############################
H U H A Z K T
M E R I W H R

124
2019/d20/ex2/ex2.py Executable file
View file

@ -0,0 +1,124 @@
#!/usr/bin/env python
import enum
import heapq
import sys
from collections import defaultdict
from typing import Iterator, NamedTuple
class Point(NamedTuple):
x: int
y: int
def neighbours(self) -> Iterator["Point"]:
for dx, dy in (
(-1, 0),
(1, 0),
(0, -1),
(0, 1),
):
yield Point(self.x + dx, self.y + dy)
class LevelDelta(enum.IntEnum):
PATH = 0
INNER_GATE = 1
OUTER_GATE = -1
Graph = dict[Point, set[tuple[Point, LevelDelta]]]
def solve(input: str) -> int:
def post_process_gates(
letters: dict[Point, str], paths: set[Point]
) -> dict[str, set[Point]]:
res: dict[str, set[Point]] = defaultdict(set)
for p1, first in letters.items():
for dx, dy in ((0, 1), (1, 0)):
p2 = Point(p1.x + dx, p1.y + dy)
if p2 not in letters:
continue
gate = first + letters[p2]
p0 = Point(p1.x - dx, p1.y - dy)
p3 = Point(p2.x + dx, p2.y + dy)
res[gate] |= {p0, p3} & paths
return res
def to_graph(paths: set[Point], gates: dict[str, set[Point]]) -> Graph:
res: dict[Point, set[tuple[Point, LevelDelta]]] = defaultdict(set)
for p in paths:
res[p] |= {(n, LevelDelta.PATH) for n in p.neighbours() if n in paths}
outer_x = {min(p.x for p in paths), max(p.x for p in paths)}
outer_y = {min(p.y for p in paths), max(p.y for p in paths)}
for gate, points in gates.items():
if len(points) == 1:
assert gate in ("AA", "ZZ") # Sanity check
continue
for p in points:
other = next(iter(other for other in points if other != p))
delta = (
LevelDelta.OUTER_GATE
if p.x in outer_x or p.y in outer_y
else LevelDelta.INNER_GATE
)
res[p].add((other, delta))
return res
def parse(input: list[str]) -> tuple[Graph, Point, Point]:
letters: dict[Point, str] = {}
paths: set[Point] = set()
for x, line in enumerate(input):
for y, c in enumerate(line):
if c == "#" or c == " ":
continue
p = Point(x, y)
if c == ".":
paths.add(p)
continue
letters[p] = c
gates = post_process_gates(letters, paths)
graph = to_graph(paths, post_process_gates(letters, paths))
return graph, next(iter(gates["AA"])), next(iter(gates["ZZ"]))
def djikstra(start: Point, end: Point, graph: Graph) -> int:
# Priority queue of (distance, point, level)
queue = [(0, start, 0)]
seen: set[tuple[Point, int]] = set()
while len(queue) > 0:
dist, p, level = heapq.heappop(queue)
if p == end and level == 0:
return dist
# We must have seen p at this level with a smaller distance before
if (p, level) in seen:
continue
# First time encountering p at this level, must be the smallest distance to it
seen.add((p, level))
# Add all neighbours to be visited
for n, delta in graph[p]:
n_level = level + delta
# Don't attempt to go out when at the most outer level
if n_level < 0:
continue
heapq.heappush(queue, (dist + 1, n, n_level))
assert False # Sanity check
graph, start, end = parse(input.splitlines())
return djikstra(start, end, graph)
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

115
2019/d20/ex2/input Normal file
View file

@ -0,0 +1,115 @@
A N N I K F W
P N A J D A L
###############################.#######.#########.#########.#####.###.#.#####################################
#.#.#.#.#...#.....#...#.........#.....#.....#.....#.......#.....#.....#...#.#...#...#.#.............#.......#
#.#.#.#.###.#####.###.#########.#.###.#.#######.###.#.#####.#.###.###.#.#.#.#.#.#.###.#.#############.#######
#...............#.#...#...........#...#...#.....#...#.#.....#.#.#.#...#.#.....#...#.#.......#...#.#...#...#.#
#####.###.#####.#.###.###.###.#####.###.#####.#.#.#.###.#.###.#.###.#####.###.###.#.#.###.###.###.###.###.#.#
#.....#.#.#.#.....#.#.#.#...#.....#...#.....#.#.#.#...#.#.#...#.......#...#.#...#.#...#.......#.#.....#.#.#.#
#######.###.#####.#.#.#.#.#.#########.#.#######.#.###.#.#####.#.#########.#.#.#####.###.#.#.#.#.#.#####.#.#.#
#.#...............#.#.....#.#.....#...#.....#.......#.#...#...#.....#...#.#...#...#...#.#.#.#.........#.....#
#.#.###.#.###.###.#.#.#.#######.#.#.#####.###########.#####.#.#.#.#####.###.#.#.###.#######.###########.#####
#.#.#...#.#...#.#.....#.#.......#...#.....#...#.....#.#.....#.#.#.#.......#.#...........#.....#.#.#.#.#.....#
#.###########.#.#.#.###.#######.#######.#####.#.###.#.###.###.#.#########.###.#####.#.#.###.#.#.#.#.#.#.#####
#.#.....#...#.#...#...#.#.#...#...#.......#.......#...#...#...#.....#.............#.#.#.#...#.#.....#.#...#.#
#.#.###.###.#.#.###.###.#.###.#.#######.#########.###########.#.#.###.###.###.#####.###############.#.#.###.#
#.....#.#.#...#.#...#.....#.#.....#.#.#.....#.#...#.......#...#.#...#.#...#...#.#.#.....#...#.#...#...#.....#
###.#####.###.#####.#######.#.#.###.#.###.###.#.#.###.#.#####.###.#######.###.#.#.#######.###.###.#.#####.###
#.........#...#.......#.......#.#.#...#.......#.#...#.#.....#.#.....#.....#.........#.#.#...#...#.......#...#
###.#####.###.#.#.###.#.###.#.#.#.#.#########.#.#######.###.#.#.#######.#####.###.###.#.#.###.###.#####.#.#.#
#...#.#.#.#.#.#.#.#...#.#.#.#.#.......#.#.....#.#.#...#.#.#.#.#.#.#.#.....#.#.#.#.#.........#...#.#.#.#...#.#
###.#.#.###.#######.#####.#.###.###.###.###.#.#.#.#.###.#.#.#.#.#.#.###.###.###.#####.#######.#####.#.###.###
#.....#...#...#.#.#.#.....#.#.....#.#.#.....#.#.....#.....#...#.....#...#.....#...#...#.....#.#...#.#.#.....#
###.###.###.#.#.#.#######.#####.#####.###.#.###.#.#.#####.#####.#######.###.###.###.#.###.###.###.#.#.###.#.#
#.......#.#.#.....#.#.#.#...#.#...#.#.....#.#.#.#.#.#.#.#.#...#...#.#...............#.#.#...#.#.#.#.......#.#
###.#####.###.###.#.#.#.#.###.#.###.###.#####.#.###.#.#.#.#.#.#.###.#.#.#####.#.#.#.###.#.###.#.#.#.#.#.#####
#.#...#.....#.#.#.................#.#...#.........#.#.#.#...#.#.....#.#.#...#.#.#.#.........#.#...#.#.#...#.#
#.#.###.###.###.###.#.#.#.#####.###.###.#######.#####.#.#.###.#####.#.#.#.#.#.#.#.#.#.#####.#.#.#.#####.#.#.#
#.#.....#...#.....#.#.#.#.#...........#...#.......#.......#.....#...#.#.#.#...#.#.#.#.#...#.#.#.#.#.#...#...#
#.#.#######.#####.###.###.#########.#####.#####.###########.#####.#.#####.#############.#####.###.#.###.#####
#.........#.#...#...#...#.# J T S F N W A #...#.#.#...#.#.#.....#.#.#
###.#####.#####.#.######### R R E A N P P #.###.#.###.#.#.###.#.#.#.#
#...#.#.#...#.#...#...#...# #.#...#.......#...#.#.....#
###.#.#.#####.###.###.#.### #.###.#####.#####.###.#####
#...#.#.#.#...#...#.#...#..HV #.#.#.#...#...#...#.#...#.#
#.#.#.#.#.###.###.#.###.#.# #.#.#.###.#.#####.#.#.###.#
#.#.......#.....#.....#...# #.#...#.#.........#.....#.#
#.###.#######.#.#.#.#.#.#.# #.#.#.#.#.#######.#.#.###.#
#.#...#.#...#.#.#.#.#.#.#.# QJ..#.#.#.#.......#...#...#..KV
#.#.###.#.###.###.#.###.### #.#.#.#.#.###.#########.#.#
FZ..#...............#.......# #...#.#.....#.#.....#...#..ZZ
#####.#.#.#####.###.#.##### #.###.#.#########.###.###.#
JR..#.#.#.#...#.#...#.#.#....HR #.#.#...#.....#.#.#.......#
#.#.#########.#########.### ###.#####.#####.#.#########
#...#.#.#.#.....#.#.......# IJ....#...#.............#.#.#
#.#.#.#.#.###.#.#.###.##### ###.#.#####.###.#.#.###.#.#
#.#...........#.......#.#.# #.....#.....#...#.#...#....SE
#########.###.#########.#.# ###.###.###.#######.###.###
#.......#...#.#.#...#.....# #.#.....#.....#.....#.....#
#.#.###########.#.#.#.#.### #.###.#.###########.#####.#
#.#...#.#...#.#.#.#...#...# #.#.#.#.#.....#...........#
#.#.###.#.###.#.#.#####.### #.#.#####.###############.#
AB..#.......#.........#.....# #.............#.........#.#
#####.###.###.#.#####.##### #.#.###.###.#######.#.#.###
#...#.#.......#...#........KV KD..#.#.#.#.......#.#.#.#...#
#.#########.###.###.#.#.### #.###.#####.###.#.###.#.###
QJ........#.#...#.#...#.#.#.# #.#.....#...#.#.....#.#...#
#.#.#.#.#.#.#########.###.# #.###.#######.###.#.#.#.#.#
#.#.#.#.#.#.#.#...#...#.#.# #.#.....#.#...#.#.#...#.#..YO
#####.#.#.###.#.#######.#.# #####.###.###.#.###########
#.....#...#.#...#.#........RF KR..........#................AX
#########.#.###.#.###.##### #.#.###.###.#.#.###.###.#.#
#.#...#...................# #.#.#.....#.#.#...#.#...#.#
#.###.#####.#.#.#.#.####### #.#####.###.#########.#####
#.........#.#.#.#.#.#.....# #.#.#.......#.#...#.#.#...#
###.#.#########.#####.###.# ###.#.#######.#.###.###.###
KR....#.#.......#.#.#.#.#...# #.....#.....#.............#
#####.#.###.#####.#.#.#.### ###.#####.#.###.###.#.#.#.#
#.....#.#.....#.#...#.#....HM #.#...#.#.#.....#.#.#.#.#..XY
#.#####.#.#####.#.#.#.##### #.#####.#.#.#.###.#.###.###
#.......#.........#.....#.# AI..#.#.#...#.#.#.#.#.#.#...#
#################.###.#.#.# #.#.#.###.#.###.#.###.#.###
#...........#...#.#...#.#.# #.........#.#...#.#.....#.#
#.###.#.###.#.#.#.#.#####.# #.###.#########.#.#######.#
#.#.#.#...#...#.#.#.....#..ZW #.#...#.....#...#.....#....WP
#.#######.#.#############.# #######.#.#.#.#.#.#.#.#.###
AA......#...#.......#.....#.# XY....#...#.#...#...#.#.#...#
###.#######.###.#.###.###.# ###.#.#####.#####.###.#.#.#
HV......#.#...#.#.#.........# #.#...#.........#.#.#.#.#.#
#######.#####.###.#.###.### #.#.#######.###.###.#.###.#
RF..#.#...#.#.#.#.#.#.#.#.#..KH #.....#.......#...#.......#
#.#.###.#.#.#.#.#####.###.# #.#######.#.###.#####.#####
#.#.....#.....#...#.....#.# #.#.#.....#.#.....#.......#
#.#.#.###.###.#.#.#.###.#.# #.#.#.#.#.#.###.#####.#.###
#...#.....#.#...#...#.....# #.#.#.#.#.#.#.......#.#...#
#####.#.#.#.#.#####.###.#.# A W N F Y U A ###.#####.#.###.#####.#.#.#
#.....#.#...#...#.....#.#.# B L A Z O E X #.#.#...#.#.#.#.#...#.#.#.#
###.#.###.#######.#.#####.#######.###.#########.#######.#######.#####.#######.#####.#.#.###.###.#.###.#.#.###
#...#.#.....#.....#.....#.......#...#.#.......#.#.#.....#...#.#.....#...#.....#...........#...#.......#.#...#
#.###.###.#.###.#.###.###.#.#####.###.#.###.###.#.###.###.#.#.###.###.#.#.#####.###.#.#.#######.#####.#######
#.#.....#.#.#...#.#.....#.#.#.....#.....#...#...#.#.....#.#.#.......#.#.#.........#.#.#.#.#.......#.#.#.....#
#####.#####.#####.###.#.#########.#######.#.#.###.#####.#.#.#.#########.###.###.#.#.#####.###.#####.#.###.###
#.........#.#.....#...#.#.#.........#.....#.#.......#.....#.#.......#...#...#...#.#.......#.#.#.............#
#####.###.#######.#####.#.###.#####.#.#############.#######.#######.###.#.#####.#.###.###.#.#####.###.#####.#
#.......#.#...#.....#.......#.#.#.#.#.....#.#.......#.....#...#.#.....#.#.#...#.#.#.....#.....#...#.......#.#
#.###.#####.#.###.#.#.#.#######.#.#.###.###.#.#####.###.#.#.###.###.###.#.#.#########.#####.#.###.#.###.#.#.#
#.#.........#.#.#.#.#.#.#.....#.....#.......#.....#.#...#...#...#.#...#.#...........#.#.#.#.#...#.#.#.#.#.#.#
#.#.#.#.#.###.#.#############.###.#.#.#######.#####.#.###.###.###.#.###.#.#.#.#####.#.#.#.#.#########.#.#####
#.#.#.#.#...#.#.#.....#...#.#...#.#.#...#.........#.#.#.......#.....#...#.#.#.#.....#...#.#.......#.#.....#.#
#.#.#.#.#.#####.###.#####.#.#.#.###.#.#.#.#######.#.#.###.#.#####.###.###.###############.#.#######.#####.#.#
#.#.#.#.#.....#.........#.#.#.#.#...#.#.#.#.....#.#.#.#...#.#.......#.#...#.#.......#...#.#...#...#.........#
###.#.#.###.###.#.#.###.#.#.###.#.###.#####.#.###.###.#############.#.###.#.#####.###.###.#.#####.#.#####.#.#
#...#.#.#.....#.#.#.#...............#.#...#.#.......#.........#.....#.#.#.......#.....#.#.........#.#.....#.#
#######################.#.#########.#.###.#####.###.#.###########.#.#.#.###.#####.#####.###.#########.#.###.#
#.#.......#.............#.#.#.......#.......#.#.#.#.#...#...#.....#.#...#.....#.#.#...#.#.#...#.....#.#...#.#
#.#####.#.###.#############.#.###.#####.#####.###.#.#.###.#####.#######.#.#####.#.###.#.#.#######.#####.#.#.#
#.......#.......#.........#.#.#.....#.......#.......#.#.#.#.#.#.......#.#.....#...#.#...#.#.....#.#.#...#.#.#
#.###.###.#.#.###########.#.#######.#.#####.#######.#.#.#.#.#.#####.#.#.###.#.###.#.#.#.#.#.#.#.#.#.###.#####
#...#.#...#.#.#.....#...#.....#...#.#.#.#...#...#...#.........#.....#.#.#...#...#.....#.....#.#.....#.#.....#
###.###.###.#####.#####.#.###.#.###.#.#.#######.###.#.#.###.#.#####.###.#.#######.#####.#######.#####.#####.#
#...#...#...#...........#.#.........#.......#.....#.#.#.#...#.#.#.....#.#...#.#.#.#.#...#.....#.#.......#.#.#
#.#.###.###.#####.#####.#####.#########.###.###.###.#####.#####.###.###.###.#.#.#.#.#####.#.###.#.#.#.#.#.###
#.#.#...#.......#...#.............#.......#.#.......#.........#.....#.....#...............#...#...#.#.#.....#
#################################.###.#########.#######.#########.###.#######.###############################
H U H A Z K T
M E R I W H R

229
2019/d21/ex1/ex1.py Executable file
View file

@ -0,0 +1,229 @@
#!/usr/bin/env python
import sys
from dataclasses import dataclass, field
from enum import IntEnum
from typing import List, NamedTuple
class ParameterMode(IntEnum):
POSITION = 0 # Acts on address
IMMEDIATE = 1 # Acts on the immediate value
RELATIVE = 2 # Acts on offset to relative base
class Instruction(NamedTuple):
address: int # The address of the instruction, for convenience
op: int # The opcode
p1_mode: ParameterMode # Which mode is the first parameter in
p2_mode: ParameterMode # Which mode is the second parameter in
p3_mode: ParameterMode # Which mode is the third parameter in
def lookup_ops(index: int, memory: List[int]) -> Instruction:
digits = list(map(int, str(memory[index])))
a, b, c, d, e = [0] * (5 - len(digits)) + digits # Pad with default values
return Instruction(
address=index,
op=d * 10 + e,
p1_mode=ParameterMode(c),
p2_mode=ParameterMode(b),
p3_mode=ParameterMode(a),
)
class InputInterrupt(Exception):
pass
class OutputInterrupt(Exception):
pass
@dataclass
class Computer:
memory: List[int] # Memory space
rip: int = 0 # Instruction pointer
input_list: List[int] = field(default_factory=list)
output_list: List[int] = field(default_factory=list)
is_halted: bool = field(default=False, init=False)
relative_base: int = field(default=0, init=False)
def run(self) -> None:
while not self.is_halted:
self.run_single()
def run_no_output_interrupt(self) -> None:
while not self.is_halted:
try:
self.run_single()
except OutputInterrupt:
continue
def run_single(self) -> None: # Returns True when halted
instr = lookup_ops(self.rip, self.memory)
if instr.op == 99: # Halt
self.is_halted = True
elif instr.op == 1: # Sum
self._do_addition(instr)
elif instr.op == 2: # Multiplication
self._do_multiplication(instr)
elif instr.op == 3: # Load from input
self._do_input(instr)
elif instr.op == 4: # Store to output
self._do_output(instr)
elif instr.op == 5: # Jump if true
self._do_jump_if_true(instr)
elif instr.op == 6: # Jump if false
self._do_jump_if_false(instr)
elif instr.op == 7: # Less than
self._do_less_than(instr)
elif instr.op == 8: # Equal to
self._do_equal_to(instr)
elif instr.op == 9: # Change relative base
self._do_change_relative_base(instr)
else:
assert False # Sanity check
def _fill_to_addres(self, address: int) -> None:
values = address - len(self.memory) + 1
if values <= 0:
return
for __ in range(values):
self.memory.append(0)
def _get_value(self, mode: ParameterMode, val: int) -> int:
if mode == ParameterMode.POSITION:
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
elif mode == ParameterMode.RELATIVE:
val += self.relative_base
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
assert mode == ParameterMode.IMMEDIATE # Sanity check
return val
def _set_value(self, mode: ParameterMode, address: int, value: int) -> None:
if mode == ParameterMode.RELATIVE:
address += self.relative_base
else:
assert mode == ParameterMode.POSITION # Sanity check
assert address >= 0 # Sanity check
self._fill_to_addres(address)
self.memory[address] = value
def _do_addition(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs + rhs)
self.rip += 4 # Length of the instruction
def _do_multiplication(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs * rhs)
self.rip += 4 # Length of the instruction
def _do_input(self, instr: Instruction) -> None:
if len(self.input_list) == 0:
raise InputInterrupt # No input, halt until an input is provided
value = int(self.input_list.pop(0))
param = self.memory[instr.address + 1]
self._set_value(instr.p1_mode, param, value)
self.rip += 2 # Length of the instruction
def _do_output(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.output_list.append(value)
self.rip += 2 # Length of the instruction
raise OutputInterrupt # Alert that we got an output to give
def _do_jump_if_true(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond != 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_jump_if_false(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond == 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_less_than(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs < rhs else 0)
self.rip += 4 # Length of the instruction
def _do_equal_to(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs == rhs else 0)
self.rip += 4 # Length of the instruction
def _do_change_relative_base(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.relative_base += value
self.rip += 2 # Length of the instruction
def main() -> None:
memory = [int(n) for n in sys.stdin.read().split(",")]
springdroid = Computer(memory)
script = "\n".join(
[
# J = B is a hole
"NOT B J",
# T = C is a hole
"NOT C T",
# J = B or C is a hole
"OR T J",
# J = (B or C is a hole) AND (D is ground)
"AND D J",
# T = A is a hole
"NOT A T",
# J = ((B or C is a hole) AND (D is ground)) OR (A is a hole)
"OR T J",
"WALK",
"", # Final newline
]
)
# Input our script
springdroid.input_list.extend(ord(c) for c in script)
springdroid.run_no_output_interrupt()
print(springdroid.output_list[-1])
if __name__ == "__main__":
main()

1
2019/d21/ex1/input Normal file

File diff suppressed because one or more lines are too long

231
2019/d21/ex2/ex2.py Executable file
View file

@ -0,0 +1,231 @@
#!/usr/bin/env python
import sys
from dataclasses import dataclass, field
from enum import IntEnum
from typing import List, NamedTuple
class ParameterMode(IntEnum):
POSITION = 0 # Acts on address
IMMEDIATE = 1 # Acts on the immediate value
RELATIVE = 2 # Acts on offset to relative base
class Instruction(NamedTuple):
address: int # The address of the instruction, for convenience
op: int # The opcode
p1_mode: ParameterMode # Which mode is the first parameter in
p2_mode: ParameterMode # Which mode is the second parameter in
p3_mode: ParameterMode # Which mode is the third parameter in
def lookup_ops(index: int, memory: List[int]) -> Instruction:
digits = list(map(int, str(memory[index])))
a, b, c, d, e = [0] * (5 - len(digits)) + digits # Pad with default values
return Instruction(
address=index,
op=d * 10 + e,
p1_mode=ParameterMode(c),
p2_mode=ParameterMode(b),
p3_mode=ParameterMode(a),
)
class InputInterrupt(Exception):
pass
class OutputInterrupt(Exception):
pass
@dataclass
class Computer:
memory: List[int] # Memory space
rip: int = 0 # Instruction pointer
input_list: List[int] = field(default_factory=list)
output_list: List[int] = field(default_factory=list)
is_halted: bool = field(default=False, init=False)
relative_base: int = field(default=0, init=False)
def run(self) -> None:
while not self.is_halted:
self.run_single()
def run_no_output_interrupt(self) -> None:
while not self.is_halted:
try:
self.run_single()
except OutputInterrupt:
continue
def run_single(self) -> None: # Returns True when halted
instr = lookup_ops(self.rip, self.memory)
if instr.op == 99: # Halt
self.is_halted = True
elif instr.op == 1: # Sum
self._do_addition(instr)
elif instr.op == 2: # Multiplication
self._do_multiplication(instr)
elif instr.op == 3: # Load from input
self._do_input(instr)
elif instr.op == 4: # Store to output
self._do_output(instr)
elif instr.op == 5: # Jump if true
self._do_jump_if_true(instr)
elif instr.op == 6: # Jump if false
self._do_jump_if_false(instr)
elif instr.op == 7: # Less than
self._do_less_than(instr)
elif instr.op == 8: # Equal to
self._do_equal_to(instr)
elif instr.op == 9: # Change relative base
self._do_change_relative_base(instr)
else:
assert False # Sanity check
def _fill_to_addres(self, address: int) -> None:
values = address - len(self.memory) + 1
if values <= 0:
return
for __ in range(values):
self.memory.append(0)
def _get_value(self, mode: ParameterMode, val: int) -> int:
if mode == ParameterMode.POSITION:
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
elif mode == ParameterMode.RELATIVE:
val += self.relative_base
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
assert mode == ParameterMode.IMMEDIATE # Sanity check
return val
def _set_value(self, mode: ParameterMode, address: int, value: int) -> None:
if mode == ParameterMode.RELATIVE:
address += self.relative_base
else:
assert mode == ParameterMode.POSITION # Sanity check
assert address >= 0 # Sanity check
self._fill_to_addres(address)
self.memory[address] = value
def _do_addition(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs + rhs)
self.rip += 4 # Length of the instruction
def _do_multiplication(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs * rhs)
self.rip += 4 # Length of the instruction
def _do_input(self, instr: Instruction) -> None:
if len(self.input_list) == 0:
raise InputInterrupt # No input, halt until an input is provided
value = int(self.input_list.pop(0))
param = self.memory[instr.address + 1]
self._set_value(instr.p1_mode, param, value)
self.rip += 2 # Length of the instruction
def _do_output(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.output_list.append(value)
self.rip += 2 # Length of the instruction
raise OutputInterrupt # Alert that we got an output to give
def _do_jump_if_true(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond != 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_jump_if_false(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond == 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_less_than(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs < rhs else 0)
self.rip += 4 # Length of the instruction
def _do_equal_to(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs == rhs else 0)
self.rip += 4 # Length of the instruction
def _do_change_relative_base(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.relative_base += value
self.rip += 2 # Length of the instruction
def main() -> None:
memory = [int(n) for n in sys.stdin.read().split(",")]
springdroid = Computer(memory)
script = "\n".join(
[
# J = B is a hole
"NOT B J",
# T = C is a hole
"NOT C T",
# J = B or C is a hole
"OR T J",
# J = (B or C is a hole) AND (D is ground)
"AND D J",
# J = (B or C is a hole) AND (D and H are ground)
"AND H J",
# T = A is a hole
"NOT A T",
# J = ((B or C is a hole) AND (D and H are ground)) OR (A is a hole)
"OR T J",
"RUN",
"", # Final newline
]
)
# Input our script
springdroid.input_list.extend(ord(c) for c in script)
springdroid.run_no_output_interrupt()
print(springdroid.output_list[-1])
if __name__ == "__main__":
main()

1
2019/d21/ex2/input Normal file

File diff suppressed because one or more lines are too long

64
2019/d22/ex1/ex1.py Executable file
View file

@ -0,0 +1,64 @@
#!/usr/bin/env python
import dataclasses
import enum
import sys
class Technique(enum.Enum):
DEAL_NEW = enum.auto()
CUT = enum.auto()
DEAL_INCR = enum.auto()
@dataclasses.dataclass
class Instruction:
tech: Technique
n: int
def to_linear(self) -> tuple[int, int]:
if self.tech == Technique.DEAL_NEW:
return (-1, -1)
if self.tech == Technique.CUT:
return (1, -self.n)
if self.tech == Technique.DEAL_INCR:
return (self.n, 0)
assert False # Sanity check
def apply(self, card_pos: int, deck_size: int) -> int:
a, b = self.to_linear()
return (card_pos * a + b) % deck_size
def solve(input: str) -> int:
def parse_instruction(input: str) -> Instruction:
if input == "deal into new stack":
return Instruction(Technique.DEAL_NEW, 0)
n = int(input.split()[-1])
if input.startswith("cut"):
return Instruction(Technique.CUT, n)
if input.startswith("deal with increment"):
return Instruction(Technique.DEAL_INCR, n)
assert False # Sanity check
def parse(input: list[str]) -> list[Instruction]:
return [parse_instruction(line) for line in input]
def find_final_pos(
card_pos: int, deck_size: int, instructions: list[Instruction]
) -> int:
for instr in instructions:
card_pos = instr.apply(card_pos, deck_size)
return card_pos
instructions = parse(input.splitlines())
return find_final_pos(2019, 10007, instructions)
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

100
2019/d22/ex1/input Normal file
View file

@ -0,0 +1,100 @@
deal into new stack
deal with increment 68
cut 4888
deal with increment 44
cut -7998
deal into new stack
cut -5078
deal with increment 26
cut 7651
deal with increment 60
cut 8998
deal into new stack
deal with increment 64
cut -8235
deal into new stack
deal with increment 9
cut -8586
deal with increment 49
cut -7466
deal with increment 66
cut -565
deal with increment 19
cut -6306
deal with increment 67
deal into new stack
cut 886
deal with increment 63
cut 3550
deal with increment 36
cut 5593
deal with increment 18
deal into new stack
deal with increment 70
deal into new stack
cut 5168
deal with increment 39
cut 7701
deal with increment 2
deal into new stack
deal with increment 45
cut 6021
deal with increment 46
cut -6927
deal with increment 49
cut 4054
deal into new stack
deal with increment 33
deal into new stack
deal with increment 11
cut -3928
deal with increment 19
deal into new stack
deal with increment 32
cut -7786
deal with increment 27
deal into new stack
deal with increment 37
cut -744
deal with increment 25
cut -98
deal with increment 61
cut 2042
deal with increment 71
cut 5761
deal with increment 6
cut -2628
deal with increment 33
cut -9509
deal with increment 16
cut 2599
deal with increment 28
cut 2767
deal into new stack
cut 3076
deal with increment 61
deal into new stack
cut 1182
deal with increment 4
cut 2274
deal into new stack
deal with increment 31
cut -5897
deal into new stack
cut -3323
deal with increment 29
cut 879
deal with increment 12
deal into new stack
deal with increment 34
cut -5755
deal with increment 59
cut 7437
deal into new stack
cut 5095
deal into new stack
cut 453
deal with increment 24
cut -3537
deal with increment 41
deal into new stack

96
2019/d22/ex2/ex2.py Executable file
View file

@ -0,0 +1,96 @@
#!/usr/bin/env python
import dataclasses
import enum
import sys
class Technique(enum.Enum):
DEAL_NEW = enum.auto()
CUT = enum.auto()
DEAL_INCR = enum.auto()
@dataclasses.dataclass
class Instruction:
tech: Technique
n: int
def to_linear(self) -> tuple[int, int]:
if self.tech == Technique.DEAL_NEW:
return (-1, -1)
if self.tech == Technique.CUT:
return (1, -self.n)
if self.tech == Technique.DEAL_INCR:
return (self.n, 0)
assert False # Sanity check
def apply(self, card_pos: int, deck_size: int) -> int:
a, b = self.to_linear()
return (card_pos * a + b) % deck_size
def solve(input: str) -> int:
def parse_instruction(input: str) -> Instruction:
if input == "deal into new stack":
return Instruction(Technique.DEAL_NEW, 0)
n = int(input.split()[-1])
if input.startswith("cut"):
return Instruction(Technique.CUT, n)
if input.startswith("deal with increment"):
return Instruction(Technique.DEAL_INCR, n)
assert False # Sanity check
def parse(input: list[str]) -> list[Instruction]:
return [parse_instruction(line) for line in input]
def to_linear(instructions: list[Instruction]) -> tuple[int, int]:
a, b = 1, 0
for instr in instructions:
new_a, new_b = instr.to_linear()
a = a * new_a
b = b * new_a + new_b
return a, b
def mod_pow(n: int, pow: int, mod: int) -> int:
if pow == 0:
return 1
if pow == 1:
return n % mod
res = mod_pow(n, pow // 2, mod) ** 2
if pow % 2 == 1:
res *= n
return res % mod
def mod_inverse(n: int, mod: int) -> int:
def extended_gcd(a: int, b: int) -> tuple[int, int, int]:
if b == 0:
return a, 1, 0
gcd, x, y = extended_gcd(b, a % b)
# we want x * a + y * b == gcd
return gcd, y, x - (a // b) * y
gcd, _, y = extended_gcd(mod, n)
assert gcd == 1 # Sanity check
return y % mod
def find_in_pos(
card_pos: int, deck_size: int, repetitions: int, instructions: list[Instruction]
) -> int:
a, b = to_linear(instructions)
repeat_a = mod_pow(a, repetitions, deck_size)
repeat_b = (b * (repeat_a - 1) * mod_inverse(a - 1, deck_size)) % deck_size
return ((card_pos - repeat_b) * mod_inverse(repeat_a, deck_size)) % deck_size
instructions = parse(input.splitlines())
return find_in_pos(2020, 119315717514047, 101741582076661, instructions)
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

100
2019/d22/ex2/input Normal file
View file

@ -0,0 +1,100 @@
deal into new stack
deal with increment 68
cut 4888
deal with increment 44
cut -7998
deal into new stack
cut -5078
deal with increment 26
cut 7651
deal with increment 60
cut 8998
deal into new stack
deal with increment 64
cut -8235
deal into new stack
deal with increment 9
cut -8586
deal with increment 49
cut -7466
deal with increment 66
cut -565
deal with increment 19
cut -6306
deal with increment 67
deal into new stack
cut 886
deal with increment 63
cut 3550
deal with increment 36
cut 5593
deal with increment 18
deal into new stack
deal with increment 70
deal into new stack
cut 5168
deal with increment 39
cut 7701
deal with increment 2
deal into new stack
deal with increment 45
cut 6021
deal with increment 46
cut -6927
deal with increment 49
cut 4054
deal into new stack
deal with increment 33
deal into new stack
deal with increment 11
cut -3928
deal with increment 19
deal into new stack
deal with increment 32
cut -7786
deal with increment 27
deal into new stack
deal with increment 37
cut -744
deal with increment 25
cut -98
deal with increment 61
cut 2042
deal with increment 71
cut 5761
deal with increment 6
cut -2628
deal with increment 33
cut -9509
deal with increment 16
cut 2599
deal with increment 28
cut 2767
deal into new stack
cut 3076
deal with increment 61
deal into new stack
cut 1182
deal with increment 4
cut 2274
deal into new stack
deal with increment 31
cut -5897
deal into new stack
cut -3323
deal with increment 29
cut 879
deal with increment 12
deal into new stack
deal with increment 34
cut -5755
deal with increment 59
cut 7437
deal into new stack
cut 5095
deal into new stack
cut 453
deal with increment 24
cut -3537
deal with increment 41
deal into new stack

230
2019/d23/ex1/ex1.py Executable file
View file

@ -0,0 +1,230 @@
#!/usr/bin/env python
import copy
import sys
from dataclasses import dataclass, field
from enum import IntEnum
from typing import List, NamedTuple
class ParameterMode(IntEnum):
POSITION = 0 # Acts on address
IMMEDIATE = 1 # Acts on the immediate value
RELATIVE = 2 # Acts on offset to relative base
class Instruction(NamedTuple):
address: int # The address of the instruction, for convenience
op: int # The opcode
p1_mode: ParameterMode # Which mode is the first parameter in
p2_mode: ParameterMode # Which mode is the second parameter in
p3_mode: ParameterMode # Which mode is the third parameter in
def lookup_ops(index: int, memory: List[int]) -> Instruction:
digits = list(map(int, str(memory[index])))
a, b, c, d, e = [0] * (5 - len(digits)) + digits # Pad with default values
return Instruction(
address=index,
op=d * 10 + e,
p1_mode=ParameterMode(c),
p2_mode=ParameterMode(b),
p3_mode=ParameterMode(a),
)
class InputInterrupt(Exception):
pass
class OutputInterrupt(Exception):
pass
@dataclass
class Computer:
memory: List[int] # Memory space
rip: int = 0 # Instruction pointer
input_list: List[int] = field(default_factory=list)
output_list: List[int] = field(default_factory=list)
is_halted: bool = field(default=False, init=False)
relative_base: int = field(default=0, init=False)
def run(self) -> None:
while not self.is_halted:
self.run_single()
def run_no_output_interrupt(self) -> None:
while not self.is_halted:
try:
self.run_single()
except OutputInterrupt:
continue
def run_single(self) -> None: # Returns True when halted
instr = lookup_ops(self.rip, self.memory)
if instr.op == 99: # Halt
self.is_halted = True
elif instr.op == 1: # Sum
self._do_addition(instr)
elif instr.op == 2: # Multiplication
self._do_multiplication(instr)
elif instr.op == 3: # Load from input
self._do_input(instr)
elif instr.op == 4: # Store to output
self._do_output(instr)
elif instr.op == 5: # Jump if true
self._do_jump_if_true(instr)
elif instr.op == 6: # Jump if false
self._do_jump_if_false(instr)
elif instr.op == 7: # Less than
self._do_less_than(instr)
elif instr.op == 8: # Equal to
self._do_equal_to(instr)
elif instr.op == 9: # Change relative base
self._do_change_relative_base(instr)
else:
assert False # Sanity check
def _fill_to_addres(self, address: int) -> None:
values = address - len(self.memory) + 1
if values <= 0:
return
for __ in range(values):
self.memory.append(0)
def _get_value(self, mode: ParameterMode, val: int) -> int:
if mode == ParameterMode.POSITION:
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
elif mode == ParameterMode.RELATIVE:
val += self.relative_base
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
assert mode == ParameterMode.IMMEDIATE # Sanity check
return val
def _set_value(self, mode: ParameterMode, address: int, value: int) -> None:
if mode == ParameterMode.RELATIVE:
address += self.relative_base
else:
assert mode == ParameterMode.POSITION # Sanity check
assert address >= 0 # Sanity check
self._fill_to_addres(address)
self.memory[address] = value
def _do_addition(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs + rhs)
self.rip += 4 # Length of the instruction
def _do_multiplication(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs * rhs)
self.rip += 4 # Length of the instruction
def _do_input(self, instr: Instruction) -> None:
if len(self.input_list) == 0:
raise InputInterrupt # No input, halt until an input is provided
value = int(self.input_list.pop(0))
param = self.memory[instr.address + 1]
self._set_value(instr.p1_mode, param, value)
self.rip += 2 # Length of the instruction
def _do_output(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.output_list.append(value)
self.rip += 2 # Length of the instruction
raise OutputInterrupt # Alert that we got an output to give
def _do_jump_if_true(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond != 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_jump_if_false(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond == 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_less_than(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs < rhs else 0)
self.rip += 4 # Length of the instruction
def _do_equal_to(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs == rhs else 0)
self.rip += 4 # Length of the instruction
def _do_change_relative_base(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.relative_base += value
self.rip += 2 # Length of the instruction
def solve(input: str) -> int:
memory = [int(n) for n in input.split(",")]
network = [Computer(copy.deepcopy(memory)) for _ in range(50)]
for i, computer in enumerate(network):
computer.input_list.append(i)
while True:
for i, computer in enumerate(network):
try:
computer.run_single()
except InputInterrupt:
computer.input_list.append(-1)
# Re-run it to actually execute the instruction
computer.run_single()
except OutputInterrupt:
if len(computer.output_list) == 3:
addr, x, y = computer.output_list
computer.output_list.clear()
if addr == 255:
return y
network[addr].input_list += [x, y]
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

1
2019/d23/ex1/input Normal file

File diff suppressed because one or more lines are too long

252
2019/d23/ex2/ex2.py Executable file
View file

@ -0,0 +1,252 @@
#!/usr/bin/env python
import copy
import sys
from dataclasses import dataclass, field
from enum import IntEnum
from typing import List, NamedTuple
class ParameterMode(IntEnum):
POSITION = 0 # Acts on address
IMMEDIATE = 1 # Acts on the immediate value
RELATIVE = 2 # Acts on offset to relative base
class Instruction(NamedTuple):
address: int # The address of the instruction, for convenience
op: int # The opcode
p1_mode: ParameterMode # Which mode is the first parameter in
p2_mode: ParameterMode # Which mode is the second parameter in
p3_mode: ParameterMode # Which mode is the third parameter in
def lookup_ops(index: int, memory: List[int]) -> Instruction:
digits = list(map(int, str(memory[index])))
a, b, c, d, e = [0] * (5 - len(digits)) + digits # Pad with default values
return Instruction(
address=index,
op=d * 10 + e,
p1_mode=ParameterMode(c),
p2_mode=ParameterMode(b),
p3_mode=ParameterMode(a),
)
class InputInterrupt(Exception):
pass
class OutputInterrupt(Exception):
pass
@dataclass
class Computer:
memory: List[int] # Memory space
rip: int = 0 # Instruction pointer
input_list: List[int] = field(default_factory=list)
output_list: List[int] = field(default_factory=list)
is_halted: bool = field(default=False, init=False)
relative_base: int = field(default=0, init=False)
def run(self) -> None:
while not self.is_halted:
self.run_single()
def run_no_output_interrupt(self) -> None:
while not self.is_halted:
try:
self.run_single()
except OutputInterrupt:
continue
def run_single(self) -> None: # Returns True when halted
instr = lookup_ops(self.rip, self.memory)
if instr.op == 99: # Halt
self.is_halted = True
elif instr.op == 1: # Sum
self._do_addition(instr)
elif instr.op == 2: # Multiplication
self._do_multiplication(instr)
elif instr.op == 3: # Load from input
self._do_input(instr)
elif instr.op == 4: # Store to output
self._do_output(instr)
elif instr.op == 5: # Jump if true
self._do_jump_if_true(instr)
elif instr.op == 6: # Jump if false
self._do_jump_if_false(instr)
elif instr.op == 7: # Less than
self._do_less_than(instr)
elif instr.op == 8: # Equal to
self._do_equal_to(instr)
elif instr.op == 9: # Change relative base
self._do_change_relative_base(instr)
else:
assert False # Sanity check
def _fill_to_addres(self, address: int) -> None:
values = address - len(self.memory) + 1
if values <= 0:
return
for __ in range(values):
self.memory.append(0)
def _get_value(self, mode: ParameterMode, val: int) -> int:
if mode == ParameterMode.POSITION:
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
elif mode == ParameterMode.RELATIVE:
val += self.relative_base
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
assert mode == ParameterMode.IMMEDIATE # Sanity check
return val
def _set_value(self, mode: ParameterMode, address: int, value: int) -> None:
if mode == ParameterMode.RELATIVE:
address += self.relative_base
else:
assert mode == ParameterMode.POSITION # Sanity check
assert address >= 0 # Sanity check
self._fill_to_addres(address)
self.memory[address] = value
def _do_addition(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs + rhs)
self.rip += 4 # Length of the instruction
def _do_multiplication(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs * rhs)
self.rip += 4 # Length of the instruction
def _do_input(self, instr: Instruction) -> None:
if len(self.input_list) == 0:
raise InputInterrupt # No input, halt until an input is provided
value = int(self.input_list.pop(0))
param = self.memory[instr.address + 1]
self._set_value(instr.p1_mode, param, value)
self.rip += 2 # Length of the instruction
def _do_output(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.output_list.append(value)
self.rip += 2 # Length of the instruction
raise OutputInterrupt # Alert that we got an output to give
def _do_jump_if_true(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond != 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_jump_if_false(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond == 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_less_than(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs < rhs else 0)
self.rip += 4 # Length of the instruction
def _do_equal_to(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs == rhs else 0)
self.rip += 4 # Length of the instruction
def _do_change_relative_base(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.relative_base += value
self.rip += 2 # Length of the instruction
def solve(input: str) -> int:
memory = [int(n) for n in input.split(",")]
network = [Computer(copy.deepcopy(memory)) for _ in range(50)]
nat: tuple[int, int] = -1, -1 # Init to a random value
previous_nat: tuple[int, int] = nat # Same
for i, computer in enumerate(network):
computer.input_list.append(i)
is_idle: set[int] = set()
idle_count = 0
while True:
for i, computer in enumerate(network):
try:
computer.run_single()
except InputInterrupt:
computer.input_list.append(-1)
# Re-run it to actually execute the instruction
computer.run_single()
is_idle.add(i)
except OutputInterrupt:
is_idle.discard(i)
if len(computer.output_list) == 3:
addr, x, y = computer.output_list
computer.output_list.clear()
if addr == 255:
nat = x, y
continue
is_idle.discard(addr)
network[addr].input_list += [x, y]
if len(is_idle) == len(network):
idle_count += 1
else:
idle_count = 0
# Use NAT after a short amount of time being idle
if idle_count == 100:
is_idle.clear()
idle_count = 0
if previous_nat[1] == nat[1]:
return nat[1]
network[0].input_list += nat
previous_nat = nat
assert False # Sanity check
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

1
2019/d23/ex2/input Normal file

File diff suppressed because one or more lines are too long

72
2019/d24/ex1/ex1.py Executable file
View file

@ -0,0 +1,72 @@
#!/usr/bin/env python
import itertools
import sys
from collections.abc import Iterator
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
def neighbours(self) -> Iterator["Point"]:
for dx, dy in (
(-1, 0),
(1, 0),
(0, -1),
(0, 1),
):
yield Point(self.x + dx, self.y + dy)
DIMS = 5
def solve(input: str) -> int:
def parse(input: list[str]) -> frozenset[Point]:
return frozenset(
Point(x, y)
for x, line in enumerate(input)
for y, c in enumerate(line)
if c == "#"
)
def step(bugs: frozenset[Point]) -> frozenset[Point]:
res: set[Point] = set()
for p in map(Point._make, itertools.product(range(DIMS), range(DIMS))):
neighbours = len(set(p.neighbours()) & bugs)
# Bug dies if not exactly one neighbour
if p in bugs and neighbours != 1:
continue
# An empty space stays empty if it doesn't have 1 or 2 neighbours
if p not in bugs and neighbours not in (1, 2):
continue
res.add(p)
return frozenset(res)
def biodiversity(bugs: frozenset[Point]) -> int:
res = 0
for x, y in itertools.product(range(DIMS), range(DIMS)):
res |= (Point(x, y) in bugs) << (x * DIMS + y)
return res
bugs = parse(input.splitlines())
layouts: set[frozenset[Point]] = set()
while True:
layouts.add(frozenset(bugs))
bugs = step(bugs)
if bugs in layouts:
return biodiversity(bugs)
assert False # Sanity check
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

5
2019/d24/ex1/input Normal file
View file

@ -0,0 +1,5 @@
..#.#
.#.##
...#.
...##
#.###

97
2019/d24/ex2/ex2.py Executable file
View file

@ -0,0 +1,97 @@
#!/usr/bin/env python
import itertools
import sys
from collections.abc import Iterator
from typing import NamedTuple
class Point(NamedTuple):
x: int
y: int
def neighbours(self) -> Iterator["Point"]:
for dx, dy in (
(-1, 0),
(1, 0),
(0, -1),
(0, 1),
):
yield Point(self.x + dx, self.y + dy)
DIMS = 5
INNER_NEIGHBOURS = {
Point(1, 2): {Point(0, y) for y in range(DIMS)},
Point(3, 2): {Point(DIMS - 1, y) for y in range(DIMS)},
Point(2, 1): {Point(x, 0) for x in range(DIMS)},
Point(2, 3): {Point(x, DIMS - 1) for x in range(DIMS)},
}
X_OUTER_NEIGHBOUR = {
p: n for n in (Point(1, 2), Point(3, 2)) for p in INNER_NEIGHBOURS[n]
}
Y_OUTER_NEIGHBOUR = {
p: n for n in (Point(2, 1), Point(2, 3)) for p in INNER_NEIGHBOURS[n]
}
def solve(input: str) -> int:
def parse(input: list[str]) -> set[tuple[Point, int]]:
return {
(Point(x, y), 0)
for x, line in enumerate(input)
for y, c in enumerate(line)
if c == "#"
}
def grid_neighbours(p: Point, level: int) -> set[tuple[Point, int]]:
assert p != Point(2, 2) # Sanity check
res: set[tuple[Point, int]] = set()
for n in p.neighbours():
if n == Point(2, 2):
res |= {(n, level + 1) for n in INNER_NEIGHBOURS[p]}
elif n.x in (-1, DIMS):
res.add((X_OUTER_NEIGHBOUR[p], level - 1))
elif n.y in (-1, DIMS):
res.add((Y_OUTER_NEIGHBOUR[p], level - 1))
else:
res.add((n, level))
return res
def step(bugs: set[tuple[Point, int]]) -> set[tuple[Point, int]]:
res: set[tuple[Point, int]] = set()
min_level, max_level = (
min(level for _, level in bugs),
max(level for _, level in bugs),
)
for level in range(min_level - 1, max_level + 1 + 1):
for p in map(Point._make, itertools.product(range(DIMS), range(DIMS))):
if p == Point(2, 2):
continue
neighbours = len(grid_neighbours(p, level) & bugs)
# Bug dies if not exactly one neighbour
if (p, level) in bugs and neighbours != 1:
continue
# An empty space stays empty if it doesn't have 1 or 2 neighbours
if (p, level) not in bugs and neighbours not in (1, 2):
continue
res.add((p, level))
return res
bugs = parse(input.splitlines())
for _ in range(200):
bugs = step(bugs)
return len(bugs)
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

5
2019/d24/ex2/input Normal file
View file

@ -0,0 +1,5 @@
..#.#
.#.##
...#.
...##
#.###

486
2019/d25/ex1/ex1.py Executable file
View file

@ -0,0 +1,486 @@
#!/usr/bin/env python
import itertools
import sys
from collections import deque
from copy import deepcopy
from dataclasses import dataclass, field
from enum import IntEnum, StrEnum
from typing import List, NamedTuple
class ParameterMode(IntEnum):
POSITION = 0 # Acts on address
IMMEDIATE = 1 # Acts on the immediate value
RELATIVE = 2 # Acts on offset to relative base
class Instruction(NamedTuple):
address: int # The address of the instruction, for convenience
op: int # The opcode
p1_mode: ParameterMode # Which mode is the first parameter in
p2_mode: ParameterMode # Which mode is the second parameter in
p3_mode: ParameterMode # Which mode is the third parameter in
def lookup_ops(index: int, memory: List[int]) -> Instruction:
digits = list(map(int, str(memory[index])))
a, b, c, d, e = [0] * (5 - len(digits)) + digits # Pad with default values
return Instruction(
address=index,
op=d * 10 + e,
p1_mode=ParameterMode(c),
p2_mode=ParameterMode(b),
p3_mode=ParameterMode(a),
)
class InputInterrupt(Exception):
pass
class OutputInterrupt(Exception):
pass
@dataclass
class Computer:
memory: List[int] # Memory space
rip: int = 0 # Instruction pointer
input_list: List[int] = field(default_factory=list)
output_list: List[int] = field(default_factory=list)
is_halted: bool = field(default=False, init=False)
relative_base: int = field(default=0, init=False)
def run(self) -> None:
while not self.is_halted:
self.run_single()
def run_no_output_interrupt(self) -> None:
while not self.is_halted:
try:
self.run_single()
except OutputInterrupt:
continue
def run_until_input_interrupt(self) -> None:
while not self.is_halted:
try:
self.run_no_output_interrupt()
except InputInterrupt:
return
def run_single(self) -> None: # Returns True when halted
instr = lookup_ops(self.rip, self.memory)
if instr.op == 99: # Halt
self.is_halted = True
elif instr.op == 1: # Sum
self._do_addition(instr)
elif instr.op == 2: # Multiplication
self._do_multiplication(instr)
elif instr.op == 3: # Load from input
self._do_input(instr)
elif instr.op == 4: # Store to output
self._do_output(instr)
elif instr.op == 5: # Jump if true
self._do_jump_if_true(instr)
elif instr.op == 6: # Jump if false
self._do_jump_if_false(instr)
elif instr.op == 7: # Less than
self._do_less_than(instr)
elif instr.op == 8: # Equal to
self._do_equal_to(instr)
elif instr.op == 9: # Change relative base
self._do_change_relative_base(instr)
else:
assert False # Sanity check
def _fill_to_addres(self, address: int) -> None:
values = address - len(self.memory) + 1
if values <= 0:
return
for __ in range(values):
self.memory.append(0)
def _get_value(self, mode: ParameterMode, val: int) -> int:
if mode == ParameterMode.POSITION:
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
elif mode == ParameterMode.RELATIVE:
val += self.relative_base
assert 0 <= val # Sanity check
self._fill_to_addres(val)
return self.memory[val]
assert mode == ParameterMode.IMMEDIATE # Sanity check
return val
def _set_value(self, mode: ParameterMode, address: int, value: int) -> None:
if mode == ParameterMode.RELATIVE:
address += self.relative_base
else:
assert mode == ParameterMode.POSITION # Sanity check
assert address >= 0 # Sanity check
self._fill_to_addres(address)
self.memory[address] = value
def _do_addition(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs + rhs)
self.rip += 4 # Length of the instruction
def _do_multiplication(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, lhs * rhs)
self.rip += 4 # Length of the instruction
def _do_input(self, instr: Instruction) -> None:
if len(self.input_list) == 0:
raise InputInterrupt # No input, halt until an input is provided
value = int(self.input_list.pop(0))
param = self.memory[instr.address + 1]
self._set_value(instr.p1_mode, param, value)
self.rip += 2 # Length of the instruction
def _do_output(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.output_list.append(value)
self.rip += 2 # Length of the instruction
raise OutputInterrupt # Alert that we got an output to give
def _do_jump_if_true(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond != 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_jump_if_false(self, instr: Instruction) -> None:
cond = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
value = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
if cond == 0:
self.rip = value
else:
self.rip += 3 # Length of the instruction
def _do_less_than(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs < rhs else 0)
self.rip += 4 # Length of the instruction
def _do_equal_to(self, instr: Instruction) -> None:
lhs = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
rhs = self._get_value(instr.p2_mode, self.memory[instr.address + 2])
dest = self.memory[instr.address + 3]
self._set_value(instr.p3_mode, dest, 1 if lhs == rhs else 0)
self.rip += 4 # Length of the instruction
def _do_change_relative_base(self, instr: Instruction) -> None:
value = self._get_value(instr.p1_mode, self.memory[instr.address + 1])
self.relative_base += value
self.rip += 2 # Length of the instruction
class Move(StrEnum):
NORTH = "north"
SOUTH = "south"
EAST = "east"
WEST = "west"
def opposite(self) -> "Move":
if self == Move.NORTH:
return Move.SOUTH
if self == Move.SOUTH:
return Move.NORTH
if self == Move.EAST:
return Move.WEST
if self == Move.WEST:
return Move.EAST
assert False # Sanity check
@dataclass
class Graph:
nodes: dict[str, dict[Move, str]] = field(default_factory=dict)
explored: set[str] = field(default_factory=set)
def add_room(self, room: str) -> None:
self.nodes.setdefault(room, {})
def add_door(self, room: str, move: Move) -> None:
if move in self.nodes[room]:
return
other_room = f"{move} of {room}"
self.nodes[room][move] = other_room
self.add_room(other_room)
self.nodes[other_room].setdefault(move.opposite(), room)
def set_visited(self, room: str) -> None:
self.explored.add(room)
# FIXME: repetition
def goto(self, start: str, end: str) -> list[Move]:
queue: deque[tuple[str, list[Move]]] = deque([(start, [])])
visited: set[str] = {start}
while queue:
room, path = queue.popleft()
visited.add(room)
if room == end:
return path
for dir, neighbour in self.nodes[room].items():
if neighbour in visited:
continue
queue.append((neighbour, path + [dir]))
assert False # Sanity check
def visit_unexplored(self, start: str) -> tuple[str, list[Move]] | None:
queue: deque[tuple[str, list[Move]]] = deque([(start, [])])
visited: set[str] = {start}
while queue:
room, path = queue.popleft()
visited.add(room)
if room not in self.explored:
return room, path
for dir, neighbour in self.nodes[room].items():
if neighbour in visited:
continue
queue.append((neighbour, path + [dir]))
return None
def rename(self, old: str, new: str) -> None:
if old == new or old not in self.nodes:
return
self.nodes[new] = self.nodes.pop(old)
for neighbours in self.nodes.values():
for move in Move:
if move not in neighbours:
continue
neighbours[move] = new if neighbours[move] == old else neighbours[move]
if old in self.explored:
self.explored.remove(old)
self.explored.add(new)
def get_room_name(output: list[str]) -> str:
for line in output:
if not line.startswith("== "):
continue
return line.replace("==", "").strip()
assert False # Sanity check
def add_doors(
output: list[str],
room: str,
graph: Graph,
) -> None:
DOORS_LEAD = "Doors here lead:"
if DOORS_LEAD not in output:
return
doors_lead_index = output.index(DOORS_LEAD)
doors_lead_end = output.index("", doors_lead_index)
directions = [
Move(line.removeprefix("- "))
for line in output[doors_lead_index:doors_lead_end]
if line.startswith("- ")
]
for dir in directions:
graph.add_door(room, dir)
def gather_items_in_room(
output: list[str],
) -> list[str]:
ITEMS_HERE = "Items here:"
# XXX: hard-coded list of items
CURSED_ITEMS = {
"escape pod",
"giant electromagnet",
"infinite loop",
"molten lava",
"photons",
}
if ITEMS_HERE not in output:
return []
items_here_index = output.index(ITEMS_HERE)
items_here_end = output.index("", items_here_index)
items = [
line.removeprefix("- ")
for line in output[items_here_index:items_here_end]
if line.startswith("- ")
]
return [f"take {item}" for item in items if item not in CURSED_ITEMS]
def explore_rooms(
output: list[str],
room: str,
graph: Graph,
) -> tuple[str, list[str], bool]:
SECURITY = "Security Checkpoint"
# Commands we want to execute
commands: list[str] = []
# Get the actual room name, and fix the graph to refer to it
actual_room = get_room_name(output)
graph.rename(room, actual_room)
room = actual_room
# Mark this room as visited
graph.set_visited(room)
# Add new doors to graph, except security checkpoint which won't let us through
if room != SECURITY:
add_doors(output, room, graph)
# Gather items in room
commands += gather_items_in_room(output)
# Go to an unexplored room if possible
if (res := graph.visit_unexplored(room)) is not None:
room, path = res
commands += path
else:
# Go to security room otherwise, with all our items
return SECURITY, list(map(str, graph.goto(room, SECURITY))), False
return room, commands, True
def get_last_output(droid: Computer) -> list[str]:
output = "".join(map(chr, droid.output_list))
droid.output_list.clear()
res: list[str] = []
for line in output.splitlines()[::-1]:
res.append(line)
if line.startswith("== "):
break
return res[::-1]
def gather_items(droid: Computer) -> Computer:
# Avoid changing the input droid
droid = deepcopy(droid)
room = "Starting room"
graph = Graph()
graph.add_room(room)
needs_items = True
while needs_items:
droid.run_until_input_interrupt()
output = get_last_output(droid)
room, commands, needs_items = explore_rooms(output, room, graph)
commands.append("") # Account for final new-line
droid.input_list.extend(map(ord, "\n".join(commands)))
# Finish going back to security
droid.run_until_input_interrupt()
# And drain last output
get_last_output(droid)
return droid
def get_current_items(droid: Computer) -> set[str]:
# Avoid changing the input droid
droid = deepcopy(droid)
assert not droid.input_list # Sanity check
assert not droid.output_list # Sanity check
droid.input_list = list(map(ord, "inv\n"))
droid.run_until_input_interrupt()
return {
line.removeprefix("- ")
for line in "".join(map(chr, droid.output_list)).splitlines()
if line.startswith("- ")
}
def go_through_security(droid: Computer) -> Computer:
items = get_current_items(droid)
for r in range(0, len(items)):
for keep in itertools.combinations(items, r):
drop = items - set(keep)
# Try on a temporary droid, use `droid` as a save point
tmp_droid = deepcopy(droid)
tmp_droid.input_list.extend(
map(ord, "".join(f"drop {item}\n" for item in drop))
)
# XXX: hard-coded direction of final room
tmp_droid.input_list.extend(map(ord, "west\n"))
try:
tmp_droid.run_no_output_interrupt()
except InputInterrupt:
# This set of items failed, try again
continue
# We halted, return the droid
return tmp_droid
assert False
def solve(input_str: str) -> int:
memory = [int(n) for n in input_str.split(",")]
droid = Computer(memory)
# Explore the ship and gather all items
droid = gather_items(droid)
# Go through checkpoint weight plate
droid = go_through_security(droid)
final_output = "".join(map(chr, droid.output_list))
# Most terrible parsing of the year
return int(final_output.splitlines()[-1].split("typing ")[1].split()[0])
def main() -> None:
input = sys.stdin.read()
print(solve(input))
if __name__ == "__main__":
main()

1
2019/d25/ex1/input Normal file

File diff suppressed because one or more lines are too long

9
2019/d25/ex2/ex2.py Executable file
View file

@ -0,0 +1,9 @@
#!/usr/bin/env python
def main() -> None:
print("There is no part two...")
if __name__ == "__main__":
main()

1
2019/d25/ex2/input Normal file

File diff suppressed because one or more lines are too long