Compare commits
24 commits
67a63a4d4d
...
5b090b2a65
| Author | SHA1 | Date | |
|---|---|---|---|
| 5b090b2a65 | |||
| b24dbd4614 | |||
| 455b1250a8 | |||
| 6d7f16f4a9 | |||
| 22ad878348 | |||
| b53a813f01 | |||
| fc82aa39d9 | |||
| 3b038197ac | |||
| 1928f14449 | |||
| 965d8ceb41 | |||
| 22593a20cd | |||
| 7ee937a858 | |||
| 6d21ad0bc0 | |||
| 7164cca51e | |||
| a4f9db4828 | |||
| e0a287938b | |||
| b74078e68d | |||
| b337e24f76 | |||
| 8a8f735399 | |||
| 9036daa682 | |||
| 31339932f8 | |||
| 1d1d7a9ef4 | |||
| e8e90e64ed | |||
| 944a4cc401 |
24 changed files with 2442 additions and 0 deletions
106
2019/d20/ex1/ex1.py
Executable file
106
2019/d20/ex1/ex1.py
Executable 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
115
2019/d20/ex1/input
Normal 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
124
2019/d20/ex2/ex2.py
Executable 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
115
2019/d20/ex2/input
Normal 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
229
2019/d21/ex1/ex1.py
Executable 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
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
231
2019/d21/ex2/ex2.py
Executable 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
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
64
2019/d22/ex1/ex1.py
Executable 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
100
2019/d22/ex1/input
Normal 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
96
2019/d22/ex2/ex2.py
Executable 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
100
2019/d22/ex2/input
Normal 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
230
2019/d23/ex1/ex1.py
Executable 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
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
252
2019/d23/ex2/ex2.py
Executable 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
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
72
2019/d24/ex1/ex1.py
Executable 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
5
2019/d24/ex1/input
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
..#.#
|
||||
.#.##
|
||||
...#.
|
||||
...##
|
||||
#.###
|
||||
97
2019/d24/ex2/ex2.py
Executable file
97
2019/d24/ex2/ex2.py
Executable 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
5
2019/d24/ex2/input
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
..#.#
|
||||
.#.##
|
||||
...#.
|
||||
...##
|
||||
#.###
|
||||
486
2019/d25/ex1/ex1.py
Executable file
486
2019/d25/ex1/ex1.py
Executable 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
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
9
2019/d25/ex2/ex2.py
Executable 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
1
2019/d25/ex2/input
Normal file
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue