Compare commits
5 Commits
6fd569aeba
...
2023/day12
Author | SHA1 | Date | |
---|---|---|---|
|
155e0e9521 | ||
|
13b15aba76 | ||
|
c7d2eb2171 | ||
|
edc50cb9c2 | ||
|
7151499835 |
12
.drone.yml
12
.drone.yml
@@ -1,12 +0,0 @@
|
||||
---
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name: tests
|
||||
image: python:3.10-slim
|
||||
commands:
|
||||
- pip install poetry
|
||||
- poetry install
|
||||
- poetry run poe lint
|
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,6 +1 @@
|
||||
# python / VS Code
|
||||
venv
|
||||
__pycache__
|
||||
.ruff_cache
|
||||
.vscode
|
||||
build
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,13 +1,12 @@
|
||||
import sys
|
||||
from math import prod
|
||||
from typing import Literal, TypeAlias, cast
|
||||
from typing import Literal, cast
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
Command: TypeAlias = Literal["forward", "up", "down"]
|
||||
|
||||
commands: list[tuple[Command, int]] = [
|
||||
(cast(Command, (p := line.split())[0]), int(p[1])) for line in lines
|
||||
commands = [
|
||||
(cast(Literal["forward", "up", "down"], (p := line.split())[0]), int(p[1]))
|
||||
for line in lines
|
||||
]
|
||||
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,4 +1,6 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
21
2021/day7.py
Normal file
21
2021/day7.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
|
||||
positions = np.asarray([int(c) for c in sys.stdin.read().strip().split(",")])
|
||||
|
||||
min_position, max_position = positions.min(), positions.max()
|
||||
|
||||
# part 1
|
||||
answer_1 = min(
|
||||
np.sum(np.abs(positions - position))
|
||||
for position in range(min_position, max_position + 1)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = min(
|
||||
np.sum(abs(positions - position) * (abs(positions - position) + 1) // 2)
|
||||
for position in range(min_position, max_position + 1)
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
13
2021/day9.py
Normal file
13
2021/day9.py
Normal file
@@ -0,0 +1,13 @@
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# part 1
|
||||
answer_1 = ...
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
answer_2 = ...
|
||||
print(f"answer 2 is {answer_2}")
|
7
2022/day1.py
Normal file
7
2022/day1.py
Normal file
@@ -0,0 +1,7 @@
|
||||
import sys
|
||||
|
||||
blocks = sys.stdin.read().split("\n\n")
|
||||
values = sorted(sum(map(int, block.split())) for block in blocks)
|
||||
|
||||
print(f"answer 1 is {values[-1]}")
|
||||
print(f"answer 2 is {sum(values[-3:])}")
|
38
2022/day10.py
Normal file
38
2022/day10.py
Normal file
@@ -0,0 +1,38 @@
|
||||
import sys
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
cycle = 1
|
||||
x = 1
|
||||
|
||||
values = {cycle: x}
|
||||
|
||||
for line in lines:
|
||||
cycle += 1
|
||||
|
||||
if line == "noop":
|
||||
pass
|
||||
else:
|
||||
r = int(line.split()[1])
|
||||
|
||||
values[cycle] = x
|
||||
|
||||
cycle += 1
|
||||
x += r
|
||||
|
||||
values[cycle] = x
|
||||
|
||||
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
|
||||
for i in range(6):
|
||||
for j in range(40):
|
||||
v = values[1 + i * 40 + j]
|
||||
|
||||
if j >= v - 1 and j <= v + 1:
|
||||
print("#", end="")
|
||||
else:
|
||||
print(".", end="")
|
||||
|
||||
print()
|
@@ -1,8 +1,7 @@
|
||||
import copy
|
||||
import sys
|
||||
from functools import reduce
|
||||
from typing import Any, Callable, Final, Iterator, Mapping, Sequence
|
||||
|
||||
from ..base import BaseSolver
|
||||
from typing import Callable, Final, Mapping, Sequence
|
||||
|
||||
|
||||
class Monkey:
|
||||
@@ -120,28 +119,24 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
|
||||
return sorted_levels[-2] * sorted_levels[-1]
|
||||
|
||||
|
||||
class Solver(BaseSolver):
|
||||
def solve(self, input: str) -> Iterator[Any]:
|
||||
monkeys = [parse_monkey(block.splitlines()) for block in input.split("\n\n")]
|
||||
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
|
||||
|
||||
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
|
||||
yield monkey_business(
|
||||
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
|
||||
)
|
||||
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
|
||||
answer_1 = monkey_business(
|
||||
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
|
||||
)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
|
||||
# use the product of all "divisible by" test so that the test remains valid
|
||||
#
|
||||
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
|
||||
#
|
||||
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
|
||||
# we use the product of all test value
|
||||
#
|
||||
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
|
||||
yield monkey_business(
|
||||
run(
|
||||
copy.deepcopy(monkeys),
|
||||
10_000,
|
||||
me_worry_fn=lambda w: w % total_test_value,
|
||||
)
|
||||
)
|
||||
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
|
||||
# use the product of all "divisible by" test so that the test remains valid
|
||||
#
|
||||
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
|
||||
#
|
||||
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
|
||||
# we use the product of all test value
|
||||
#
|
||||
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
|
||||
answer_2 = monkey_business(
|
||||
run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
|
||||
)
|
||||
print(f"answer 2 is {answer_2}")
|
@@ -1,7 +1,6 @@
|
||||
import heapq
|
||||
from typing import Any, Callable, Iterator, TypeVar
|
||||
|
||||
from ..base import BaseSolver
|
||||
import sys
|
||||
from typing import Callable, Iterator, TypeVar
|
||||
|
||||
Node = TypeVar("Node")
|
||||
|
||||
@@ -69,6 +68,30 @@ def make_path(parents: dict[Node, Node], start: Node, end: Node) -> list[Node] |
|
||||
return list(reversed(path))
|
||||
|
||||
|
||||
def print_path(path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
|
||||
end = path[-1]
|
||||
|
||||
graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
|
||||
graph[end[0]][end[1]] = "E"
|
||||
|
||||
for i in range(0, len(path) - 1):
|
||||
cr, cc = path[i]
|
||||
nr, nc = path[i + 1]
|
||||
|
||||
if cr == nr and nc == cc - 1:
|
||||
graph[cr][cc] = "<"
|
||||
elif cr == nr and nc == cc + 1:
|
||||
graph[cr][cc] = ">"
|
||||
elif cr == nr - 1 and nc == cc:
|
||||
graph[cr][cc] = "v"
|
||||
elif cr == nr + 1 and nc == cc:
|
||||
graph[cr][cc] = "^"
|
||||
else:
|
||||
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
|
||||
|
||||
print("\n".join("".join(row) for row in graph))
|
||||
|
||||
|
||||
def neighbors(
|
||||
grid: list[list[int]], node: tuple[int, int], up: bool
|
||||
) -> Iterator[tuple[int, int]]:
|
||||
@@ -95,74 +118,43 @@ def neighbors(
|
||||
|
||||
# === main code ===
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
class Solver(BaseSolver):
|
||||
def print_path(self, path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
|
||||
end = path[-1]
|
||||
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
|
||||
|
||||
graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
|
||||
graph[end[0]][end[1]] = "E"
|
||||
start: tuple[int, int]
|
||||
end: tuple[int, int]
|
||||
|
||||
for i in range(0, len(path) - 1):
|
||||
cr, cc = path[i]
|
||||
nr, nc = path[i + 1]
|
||||
# for part 2
|
||||
start_s: list[tuple[int, int]] = []
|
||||
|
||||
if cr == nr and nc == cc - 1:
|
||||
graph[cr][cc] = "<"
|
||||
elif cr == nr and nc == cc + 1:
|
||||
graph[cr][cc] = ">"
|
||||
elif cr == nr - 1 and nc == cc:
|
||||
graph[cr][cc] = "v"
|
||||
elif cr == nr + 1 and nc == cc:
|
||||
graph[cr][cc] = "^"
|
||||
else:
|
||||
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
|
||||
for i_row, row in enumerate(grid):
|
||||
for i_col, col in enumerate(row):
|
||||
if chr(col + ord("a")) == "S":
|
||||
start = (i_row, i_col)
|
||||
start_s.append(start)
|
||||
elif chr(col + ord("a")) == "E":
|
||||
end = (i_row, i_col)
|
||||
elif col == 0:
|
||||
start_s.append((i_row, i_col))
|
||||
|
||||
for row in graph:
|
||||
self.logger.info("".join(row))
|
||||
# fix values
|
||||
grid[start[0]][start[1]] = 0
|
||||
grid[end[0]][end[1]] = ord("z") - ord("a")
|
||||
|
||||
def solve(self, input: str) -> Iterator[Any]:
|
||||
lines = input.splitlines()
|
||||
|
||||
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
|
||||
lengths_1, parents_1 = dijkstra(
|
||||
start=start, neighbors=lambda n: neighbors(grid, n, True), cost=lambda lhs, rhs: 1
|
||||
)
|
||||
path_1 = make_path(parents_1, start, end)
|
||||
assert path_1 is not None
|
||||
|
||||
start: tuple[int, int] | None = None
|
||||
end: tuple[int, int] | None = None
|
||||
print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
|
||||
|
||||
# for part 2
|
||||
start_s: list[tuple[int, int]] = []
|
||||
print(f"answer 1 is {lengths_1[end] - 1}")
|
||||
|
||||
for i_row, row in enumerate(grid):
|
||||
for i_col, col in enumerate(row):
|
||||
if chr(col + ord("a")) == "S":
|
||||
start = (i_row, i_col)
|
||||
start_s.append(start)
|
||||
elif chr(col + ord("a")) == "E":
|
||||
end = (i_row, i_col)
|
||||
elif col == 0:
|
||||
start_s.append((i_row, i_col))
|
||||
|
||||
assert start is not None
|
||||
assert end is not None
|
||||
|
||||
# fix values
|
||||
grid[start[0]][start[1]] = 0
|
||||
grid[end[0]][end[1]] = ord("z") - ord("a")
|
||||
|
||||
lengths_1, parents_1 = dijkstra(
|
||||
start=start,
|
||||
neighbors=lambda n: neighbors(grid, n, True),
|
||||
cost=lambda lhs, rhs: 1,
|
||||
)
|
||||
path_1 = make_path(parents_1, start, end)
|
||||
assert path_1 is not None
|
||||
|
||||
self.print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
|
||||
yield lengths_1[end] - 1
|
||||
|
||||
lengths_2, _ = dijkstra(
|
||||
start=end,
|
||||
neighbors=lambda n: neighbors(grid, n, False),
|
||||
cost=lambda lhs, rhs: 1,
|
||||
)
|
||||
yield min(lengths_2.get(start, float("inf")) for start in start_s)
|
||||
lengths_2, parents_2 = dijkstra(
|
||||
start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1
|
||||
)
|
||||
answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s)
|
||||
print(f"answer 2 is {answer_2}")
|
@@ -1,8 +1,11 @@
|
||||
import json
|
||||
import sys
|
||||
from functools import cmp_to_key
|
||||
from typing import Any, Iterator, TypeAlias, cast
|
||||
from typing import TypeAlias, cast
|
||||
|
||||
from ..base import BaseSolver
|
||||
blocks = sys.stdin.read().strip().split("\n\n")
|
||||
|
||||
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
|
||||
|
||||
Packet: TypeAlias = list[int | list["Packet"]]
|
||||
|
||||
@@ -25,18 +28,14 @@ def compare(lhs: Packet, rhs: Packet) -> int:
|
||||
return len(rhs) - len(lhs)
|
||||
|
||||
|
||||
class Solver(BaseSolver):
|
||||
def solve(self, input: str) -> Iterator[Any]:
|
||||
blocks = input.split("\n\n")
|
||||
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
|
||||
answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
|
||||
print(f"answer_1 is {answer_1}")
|
||||
|
||||
yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
|
||||
dividers = [[[2]], [[6]]]
|
||||
|
||||
dividers = [[[2]], [[6]]]
|
||||
packets = [packet for packets in pairs for packet in packets]
|
||||
packets.extend(dividers)
|
||||
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
|
||||
|
||||
packets = [packet for packets in pairs for packet in packets]
|
||||
packets.extend(dividers)
|
||||
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
|
||||
|
||||
d_index = [packets.index(d) + 1 for d in dividers]
|
||||
yield d_index[0] * d_index[1]
|
||||
d_index = [packets.index(d) + 1 for d in dividers]
|
||||
print(f"answer 2 is {d_index[0] * d_index[1]}")
|
140
2022/day14.py
Normal file
140
2022/day14.py
Normal file
@@ -0,0 +1,140 @@
|
||||
import sys
|
||||
from enum import Enum, auto
|
||||
from typing import Callable, cast
|
||||
|
||||
|
||||
class Cell(Enum):
|
||||
AIR = auto()
|
||||
ROCK = auto()
|
||||
SAND = auto()
|
||||
|
||||
def __str__(self) -> str:
|
||||
return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self]
|
||||
|
||||
|
||||
def print_blocks(blocks: dict[tuple[int, int], Cell]):
|
||||
"""
|
||||
Print the given set of blocks on a grid.
|
||||
|
||||
Args:
|
||||
blocks: Set of blocks to print.
|
||||
"""
|
||||
x_min, y_min, x_max, y_max = (
|
||||
min(x for x, _ in blocks),
|
||||
0,
|
||||
max(x for x, _ in blocks),
|
||||
max(y for _, y in blocks),
|
||||
)
|
||||
|
||||
for y in range(y_min, y_max + 1):
|
||||
print(
|
||||
"".join(str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1))
|
||||
)
|
||||
|
||||
|
||||
def flow(
|
||||
blocks: dict[tuple[int, int], Cell],
|
||||
stop_fn: Callable[[int, int], bool],
|
||||
fill_fn: Callable[[int, int], Cell],
|
||||
) -> dict[tuple[int, int], Cell]:
|
||||
"""
|
||||
Flow sands onto the given set of blocks
|
||||
|
||||
Args:
|
||||
blocks: Blocks containing ROCK position. Modified in-place.
|
||||
stop_fn: Function called with the last (assumed) position of a grain of
|
||||
sand BEFORE adding it to blocks. If the function returns True, the grain
|
||||
is added and a new one is flowed, otherwise, the whole procedure stops
|
||||
and the function returns (without adding the final grain).
|
||||
fill_fn: Function called when the target position of a grain (during the
|
||||
flowing process) is missing from blocks.
|
||||
|
||||
Returns:
|
||||
The input blocks.
|
||||
"""
|
||||
|
||||
y_max = max(y for _, y in blocks)
|
||||
|
||||
while True:
|
||||
x, y = 500, 0
|
||||
|
||||
while y <= y_max:
|
||||
moved = False
|
||||
for cx, cy in ((x, y + 1), (x - 1, y + 1), (x + 1, y + 1)):
|
||||
if (cx, cy) not in blocks and fill_fn(cx, cy) == Cell.AIR:
|
||||
x, y = cx, cy
|
||||
moved = True
|
||||
elif blocks[cx, cy] == Cell.AIR:
|
||||
x, y = cx, cy
|
||||
moved = True
|
||||
|
||||
if moved:
|
||||
break
|
||||
|
||||
if not moved:
|
||||
break
|
||||
|
||||
if stop_fn(x, y):
|
||||
break
|
||||
|
||||
blocks[x, y] = Cell.SAND
|
||||
|
||||
return blocks
|
||||
|
||||
|
||||
# === inputs ===
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
paths: list[list[tuple[int, int]]] = []
|
||||
for line in lines:
|
||||
parts = line.split(" -> ")
|
||||
paths.append(
|
||||
[
|
||||
cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
|
||||
for part in parts
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
blocks: dict[tuple[int, int], Cell] = {}
|
||||
for path in paths:
|
||||
for start, end in zip(path[:-1], path[1:]):
|
||||
x_start = min(start[0], end[0])
|
||||
x_end = max(start[0], end[0]) + 1
|
||||
y_start = min(start[1], end[1])
|
||||
y_end = max(start[1], end[1]) + 1
|
||||
|
||||
for x in range(x_start, x_end):
|
||||
for y in range(y_start, y_end):
|
||||
blocks[x, y] = Cell.ROCK
|
||||
|
||||
print_blocks(blocks)
|
||||
print()
|
||||
|
||||
x_min, y_min, x_max, y_max = (
|
||||
min(x for x, _ in blocks),
|
||||
0,
|
||||
max(x for x, _ in blocks),
|
||||
max(y for _, y in blocks),
|
||||
)
|
||||
|
||||
# === part 1 ===
|
||||
|
||||
blocks_1 = flow(
|
||||
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
|
||||
)
|
||||
print_blocks(blocks_1)
|
||||
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
|
||||
print()
|
||||
|
||||
# === part 2 ===
|
||||
|
||||
blocks_2 = flow(
|
||||
blocks.copy(),
|
||||
stop_fn=lambda x, y: x == 500 and y == 0,
|
||||
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
|
||||
)
|
||||
blocks_2[500, 0] = Cell.SAND
|
||||
print_blocks(blocks_2)
|
||||
print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")
|
87
2022/day15.py
Normal file
87
2022/day15.py
Normal file
@@ -0,0 +1,87 @@
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import parse
|
||||
|
||||
|
||||
def part1(sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int) -> int:
|
||||
no_beacons_row_l: list[np.ndarray] = []
|
||||
|
||||
for (sx, sy), (bx, by) in sensor_to_beacon.items():
|
||||
d = abs(sx - bx) + abs(sy - by) # closest
|
||||
|
||||
no_beacons_row_l.append(sx - np.arange(0, d - abs(sy - row) + 1))
|
||||
no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1))
|
||||
|
||||
beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row)
|
||||
no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference(beacons_at_row)
|
||||
|
||||
return len(no_beacons_row)
|
||||
|
||||
|
||||
def part2_intervals(
|
||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
|
||||
) -> tuple[int, int, int]:
|
||||
from tqdm import trange
|
||||
|
||||
for y in trange(xy_max + 1):
|
||||
its: list[tuple[int, int]] = []
|
||||
for (sx, sy), (bx, by) in sensor_to_beacon.items():
|
||||
d = abs(sx - bx) + abs(sy - by)
|
||||
dx = d - abs(sy - y)
|
||||
|
||||
if dx >= 0:
|
||||
its.append((max(0, sx - dx), min(sx + dx, xy_max)))
|
||||
|
||||
its = sorted(its)
|
||||
_, e = its[0]
|
||||
|
||||
for si, ei in its[1:]:
|
||||
if si > e + 1:
|
||||
return si - 1, y, 4_000_000 * (si - 1) + y
|
||||
if ei > e:
|
||||
e = ei
|
||||
|
||||
return (0, 0, 0)
|
||||
|
||||
|
||||
def part2_cplex(
|
||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
|
||||
) -> tuple[int, int, int]:
|
||||
from docplex.mp.model import Model
|
||||
|
||||
m = Model()
|
||||
|
||||
x, y = m.continuous_var_list(2, ub=xy_max, name=["x", "y"])
|
||||
|
||||
for (sx, sy), (bx, by) in sensor_to_beacon.items():
|
||||
d = abs(sx - bx) + abs(sy - by)
|
||||
m.add_constraint(m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}")
|
||||
|
||||
m.set_objective("min", x + y)
|
||||
|
||||
s = m.solve()
|
||||
|
||||
vx = int(s.get_value(x))
|
||||
vy = int(s.get_value(y))
|
||||
return vx, vy, 4_000_000 * vx + vy
|
||||
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {}
|
||||
|
||||
for line in lines:
|
||||
r = parse.parse(
|
||||
"Sensor at x={sx}, y={sy}: closest beacon is at x={bx}, y={by}", line
|
||||
)
|
||||
sensor_to_beacon[int(r["sx"]), int(r["sy"])] = (int(r["bx"]), int(r["by"]))
|
||||
|
||||
xy_max = 4_000_000 if max(sensor_to_beacon) > (1_000, 0) else 20
|
||||
row = 2_000_000 if max(sensor_to_beacon) > (1_000, 0) else 10
|
||||
|
||||
print(f"answer 1 is {part1(sensor_to_beacon, row)}")
|
||||
|
||||
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
|
||||
x, y, a2 = part2_intervals(sensor_to_beacon, xy_max)
|
||||
print(f"answer 2 is {a2} (x={x}, y={y})")
|
@@ -5,12 +5,10 @@ import itertools
|
||||
import re
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import Any, FrozenSet, Iterator, NamedTuple
|
||||
from typing import FrozenSet, NamedTuple
|
||||
|
||||
from tqdm import tqdm
|
||||
|
||||
from ..base import BaseSolver
|
||||
|
||||
|
||||
class Pipe(NamedTuple):
|
||||
name: str
|
@@ -1,10 +1,8 @@
|
||||
import sys
|
||||
from typing import Any, Iterator, Sequence, TypeVar
|
||||
from typing import Sequence, TypeVar
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..base import BaseSolver
|
||||
|
||||
T = TypeVar("T")
|
||||
|
||||
|
@@ -1,10 +1,8 @@
|
||||
import sys
|
||||
from typing import Any, Iterator
|
||||
from typing import FrozenSet
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..base import BaseSolver
|
||||
|
||||
xyz = np.asarray(
|
||||
[
|
||||
tuple(int(x) for x in row.split(",")) # type: ignore
|
@@ -1,11 +1,9 @@
|
||||
import sys
|
||||
from typing import Any, Iterator, Literal
|
||||
from typing import Literal
|
||||
|
||||
import numpy as np
|
||||
import parse # pyright: ignore[reportMissingTypeStubs]
|
||||
from numpy.typing import NDArray
|
||||
|
||||
from ..base import BaseSolver
|
||||
import parse
|
||||
from tqdm import tqdm
|
||||
|
||||
Reagent = Literal["ore", "clay", "obsidian", "geode"]
|
||||
REAGENTS: tuple[Reagent, ...] = (
|
||||
@@ -37,7 +35,7 @@ class State:
|
||||
self.robots = robots
|
||||
self.reagents = reagents
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
def __eq__(self, other) -> bool:
|
||||
return (
|
||||
isinstance(other, State)
|
||||
and self.robots == other.robots
|
||||
@@ -68,7 +66,7 @@ lines = sys.stdin.read().splitlines()
|
||||
|
||||
blueprints: list[dict[Reagent, IntOfReagent]] = []
|
||||
for line in lines:
|
||||
r: list[int] = parse.parse( # type: ignore
|
||||
r = parse.parse(
|
||||
"Blueprint {}: "
|
||||
"Each ore robot costs {:d} ore. "
|
||||
"Each clay robot costs {:d} ore. "
|
||||
@@ -96,12 +94,11 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
|
||||
name: max(blueprint[r].get(name, 0) for r in REAGENTS) for name in REAGENTS
|
||||
}
|
||||
|
||||
state_after_t: dict[int, set[State]] = {0: {State()}}
|
||||
state_after_t: dict[int, set[State]] = {0: [State()]}
|
||||
|
||||
for t in range(1, max_time + 1):
|
||||
# list of new states at the end of step t that we are going to prune later
|
||||
states_for_t: set[State] = set()
|
||||
robots_that_can_be_built: list[Reagent]
|
||||
|
||||
for state in state_after_t[t - 1]:
|
||||
robots_that_can_be_built = [
|
||||
@@ -135,7 +132,7 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
|
||||
for robot in robots_that_can_be_built:
|
||||
robots = state.robots.copy()
|
||||
robots[robot] += 1
|
||||
reagents: IntOfReagent = {
|
||||
reagents = {
|
||||
reagent: state.reagents[reagent]
|
||||
+ state.robots[reagent]
|
||||
- blueprint[robot].get(reagent, 0)
|
||||
@@ -154,7 +151,7 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
|
||||
]
|
||||
)
|
||||
|
||||
to_keep: list[NDArray[np.integer[Any]]] = []
|
||||
to_keep = []
|
||||
while len(np_states) > 0:
|
||||
first_dom = (np_states[1:] >= np_states[0]).all(axis=1).any()
|
||||
|
@@ -1,6 +1,4 @@
|
||||
from typing import Any, Iterator
|
||||
|
||||
from ..base import BaseSolver
|
||||
import sys
|
||||
|
||||
|
||||
def score_1(ux: int, vx: int) -> int:
|
||||
@@ -35,23 +33,21 @@ def score_2(ux: int, vx: int) -> int:
|
||||
return (ux + vx - 1) % 3 + 1 + vx * 3
|
||||
|
||||
|
||||
class Solver(BaseSolver):
|
||||
def solve(self, input: str) -> Iterator[Any]:
|
||||
lines = input.splitlines()
|
||||
lines = sys.stdin.readlines()
|
||||
|
||||
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
|
||||
# modulo-3 arithmetic
|
||||
#
|
||||
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
|
||||
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
|
||||
#
|
||||
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
|
||||
# modulo-3 arithmetic
|
||||
#
|
||||
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
|
||||
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
|
||||
#
|
||||
|
||||
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
|
||||
# easier manipulation
|
||||
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
|
||||
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
|
||||
# easier manipulation
|
||||
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
|
||||
|
||||
# part 1 - 13526
|
||||
yield sum(score_1(*v) for v in values)
|
||||
# part 1 - 13526
|
||||
print(f"answer 1 is {sum(score_1(*v) for v in values)}")
|
||||
|
||||
# part 2 - 14204
|
||||
yield sum(score_2(*v) for v in values)
|
||||
# part 2 - 14204
|
||||
print(f"answer 2 is {sum(score_2(*v) for v in values)}")
|
@@ -1,9 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
from typing import Any, Iterator
|
||||
|
||||
from ..base import BaseSolver
|
||||
|
||||
|
||||
class Number:
|
@@ -1,8 +1,6 @@
|
||||
import operator
|
||||
import sys
|
||||
from typing import Any, Callable, Iterator
|
||||
|
||||
from ..base import BaseSolver
|
||||
from typing import Callable
|
||||
|
||||
|
||||
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
|
@@ -1,11 +1,9 @@
|
||||
import re
|
||||
import sys
|
||||
from typing import Any, Callable, Iterator
|
||||
from typing import Callable
|
||||
|
||||
import numpy as np
|
||||
|
||||
from ..base import BaseSolver
|
||||
|
||||
VOID, EMPTY, WALL = 0, 1, 2
|
||||
TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}
|
||||
|
@@ -1,9 +1,6 @@
|
||||
import itertools
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import Any, Iterator
|
||||
|
||||
from ..base import BaseSolver
|
||||
|
||||
Directions = list[
|
||||
tuple[
|
@@ -2,9 +2,6 @@ import heapq
|
||||
import math
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import Any, Iterator
|
||||
|
||||
from ..base import BaseSolver
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
@@ -1,7 +1,4 @@
|
||||
import sys
|
||||
from typing import Any, Iterator
|
||||
|
||||
from ..base import BaseSolver
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
23
2022/day3.py
Normal file
23
2022/day3.py
Normal file
@@ -0,0 +1,23 @@
|
||||
import string
|
||||
import sys
|
||||
|
||||
lines = [line.strip() for line in sys.stdin.readlines()]
|
||||
|
||||
# extract content of each part
|
||||
parts = [(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines]
|
||||
|
||||
# priorities
|
||||
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
|
||||
|
||||
# part 1
|
||||
part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
|
||||
print(f"answer 1 is {part1}")
|
||||
|
||||
# part 2
|
||||
n_per_group = 3
|
||||
part2 = sum(
|
||||
priorities[c]
|
||||
for i in range(0, len(lines), n_per_group)
|
||||
for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group])
|
||||
)
|
||||
print(f"answer 2 is {part2}")
|
17
2022/day4.py
Normal file
17
2022/day4.py
Normal file
@@ -0,0 +1,17 @@
|
||||
import sys
|
||||
|
||||
lines = [line.strip() for line in sys.stdin.readlines()]
|
||||
|
||||
|
||||
def make_range(value: str) -> set[int]:
|
||||
parts = value.split("-")
|
||||
return set(range(int(parts[0]), int(parts[1]) + 1))
|
||||
|
||||
|
||||
sections = [tuple(make_range(part) for part in line.split(",")) for line in lines]
|
||||
|
||||
answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections)
|
||||
print(f"answer 1 is {answer_2}")
|
41
2022/day5.py
Normal file
41
2022/day5.py
Normal file
@@ -0,0 +1,41 @@
|
||||
import copy
|
||||
import sys
|
||||
|
||||
blocks_s, moves_s = (part.splitlines() for part in sys.stdin.read().split("\n\n"))
|
||||
|
||||
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
|
||||
|
||||
# this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the
|
||||
# form of '[X] ' (including the trailing space)
|
||||
#
|
||||
for block in blocks_s[-2::-1]:
|
||||
for stack, index in zip(blocks, range(0, len(block), 4)):
|
||||
crate = block[index + 1 : index + 2].strip()
|
||||
|
||||
if crate:
|
||||
blocks[stack].append(crate)
|
||||
|
||||
# part 1 - deep copy for part 2
|
||||
blocks_1 = copy.deepcopy(blocks)
|
||||
|
||||
for move in moves_s:
|
||||
_, count_s, _, from_, _, to_ = move.strip().split()
|
||||
|
||||
for _i in range(int(count_s)):
|
||||
blocks_1[to_].append(blocks_1[from_].pop())
|
||||
|
||||
# part 2
|
||||
blocks_2 = copy.deepcopy(blocks)
|
||||
|
||||
for move in moves_s:
|
||||
_, count_s, _, from_, _, to_ = move.strip().split()
|
||||
count = int(count_s)
|
||||
|
||||
blocks_2[to_].extend(blocks_2[from_][-count:])
|
||||
del blocks_2[from_][-count:]
|
||||
|
||||
answer_1 = "".join(s[-1] for s in blocks_1.values())
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
answer_2 = "".join(s[-1] for s in blocks_2.values())
|
||||
print(f"answer 2 is {answer_2}")
|
15
2022/day6.py
Normal file
15
2022/day6.py
Normal file
@@ -0,0 +1,15 @@
|
||||
import sys
|
||||
|
||||
|
||||
def index_of_first_n_differents(data: str, n: int) -> int:
|
||||
for i in range(len(data)):
|
||||
if len(set(data[i : i + n])) == n:
|
||||
return i + n
|
||||
return -1
|
||||
|
||||
|
||||
data = sys.stdin.read().strip()
|
||||
|
||||
|
||||
print(f"answer 1 is {index_of_first_n_differents(data, 4)}")
|
||||
print(f"answer 2 is {index_of_first_n_differents(data, 14)}")
|
80
2022/day7.py
Normal file
80
2022/day7.py
Normal file
@@ -0,0 +1,80 @@
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
# we are going to use Path to create path and go up/down in the file tree since it
|
||||
# implements everything we need
|
||||
#
|
||||
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
|
||||
# on Windows but that is not an issue since only the sizes matter
|
||||
#
|
||||
|
||||
# mapping from path to list of files or directories
|
||||
trees: dict[Path, list[Path]] = {}
|
||||
|
||||
# mapping from paths to either size (for file) or -1 for directory
|
||||
sizes: dict[Path, int] = {}
|
||||
|
||||
# first line must be a cd otherwise we have no idea where we are
|
||||
assert lines[0].startswith("$ cd")
|
||||
base_path = Path(lines[0].strip("$").split()[1]).resolve()
|
||||
cur_path = base_path
|
||||
|
||||
trees[cur_path] = []
|
||||
sizes[cur_path] = -1
|
||||
|
||||
for line in lines[1:]:
|
||||
# command
|
||||
if line.startswith("$"):
|
||||
parts = line.strip("$").strip().split()
|
||||
command = parts[0]
|
||||
|
||||
if command == "cd":
|
||||
cur_path = cur_path.joinpath(parts[1]).resolve()
|
||||
|
||||
# just initialize the lis of files if not already done
|
||||
if cur_path not in trees:
|
||||
trees[cur_path] = []
|
||||
else:
|
||||
# nothing to do here
|
||||
pass
|
||||
|
||||
# fill the current path
|
||||
else:
|
||||
parts = line.split()
|
||||
name: str = parts[1]
|
||||
if line.startswith("dir"):
|
||||
size = -1
|
||||
else:
|
||||
size = int(parts[0])
|
||||
|
||||
path = cur_path.joinpath(name)
|
||||
trees[cur_path].append(path)
|
||||
sizes[path] = size
|
||||
|
||||
|
||||
def compute_size(path: Path) -> int:
|
||||
size = sizes[path]
|
||||
|
||||
if size >= 0:
|
||||
return size
|
||||
|
||||
return sum(compute_size(sub) for sub in trees[path])
|
||||
|
||||
|
||||
acc_sizes = {path: compute_size(path) for path in trees}
|
||||
|
||||
# part 1
|
||||
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
|
||||
print(f"answer 1 is {answer_1}")
|
||||
|
||||
# part 2
|
||||
total_space = 70_000_000
|
||||
update_space = 30_000_000
|
||||
free_space = total_space - acc_sizes[base_path]
|
||||
|
||||
to_free_space = update_space - free_space
|
||||
|
||||
answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space)
|
||||
print(f"answer 2 is {answer_2}")
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user