Refactor code for API #3

Merged
mikael.capelle merged 13 commits from dev/refactor-for-ui into master 2024-12-08 13:06:42 +00:00
27 changed files with 970 additions and 876 deletions
Showing only changes of commit cd96140378 - Show all commits

View File

@ -1,7 +1,12 @@
import sys from typing import Any, Iterator
blocks = sys.stdin.read().split("\n\n") from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blocks = input.split("\n\n")
values = sorted(sum(map(int, block.split())) for block in blocks) values = sorted(sum(map(int, block.split())) for block in blocks)
print(f"answer 1 is {values[-1]}") yield values[-1]
print(f"answer 2 is {sum(values[-3:])}") yield sum(values[-3:])

View File

@ -1,10 +1,13 @@
import sys from typing import Any, Iterator
lines = sys.stdin.read().splitlines() from ..base import BaseSolver
cycle = 1
x = 1
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
cycle, x = 1, 1
values = {cycle: x} values = {cycle: x}
for line in lines: for line in lines:
@ -23,16 +26,18 @@ for line in lines:
values[cycle] = x values[cycle] = x
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40)) answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
print(f"answer 1 is {answer_1}") yield answer_1
yield (
for i in range(6): "\n"
for j in range(40): + "\n".join(
v = values[1 + i * 40 + j] "".join(
"#"
if j >= v - 1 and j <= v + 1: if j >= (v := values[1 + i * 40 + j]) - 1 and j <= v + 1
print("#", end="") else "."
else: for j in range(40)
print(".", end="") )
for i in range(6)
print() )
+ "\n"
)

View File

@ -1,7 +1,8 @@
import copy import copy
import sys
from functools import reduce from functools import reduce
from typing import Callable, Final, Mapping, Sequence from typing import Any, Callable, Final, Iterator, Mapping, Sequence
from ..base import BaseSolver
class Monkey: class Monkey:
@ -119,13 +120,14 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
return sorted_levels[-2] * sorted_levels[-1] return sorted_levels[-2] * sorted_levels[-1]
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")] class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
monkeys = [parse_monkey(block.splitlines()) for block in input.split("\n\n")]
# case 1: we simply divide the worry by 3 after applying the monkey worry operation # case 1: we simply divide the worry by 3 after applying the monkey worry operation
answer_1 = monkey_business( yield monkey_business(
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3) 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 # 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 # use the product of all "divisible by" test so that the test remains valid
@ -136,7 +138,10 @@ print(f"answer 1 is {answer_1}")
# we use the product of all test value # we use the product of all test value
# #
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1) total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
answer_2 = monkey_business( yield monkey_business(
run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value) run(
copy.deepcopy(monkeys),
10_000,
me_worry_fn=lambda w: w % total_test_value,
)
) )
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,7 @@
import heapq import heapq
import sys from typing import Any, Callable, Iterator, TypeVar
from typing import Callable, Iterator, TypeVar
from ..base import BaseSolver
Node = TypeVar("Node") Node = TypeVar("Node")
@ -68,30 +69,6 @@ def make_path(parents: dict[Node, Node], start: Node, end: Node) -> list[Node] |
return list(reversed(path)) 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( def neighbors(
grid: list[list[int]], node: tuple[int, int], up: bool grid: list[list[int]], node: tuple[int, int], up: bool
) -> Iterator[tuple[int, int]]: ) -> Iterator[tuple[int, int]]:
@ -118,7 +95,34 @@ def neighbors(
# === main code === # === 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]
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])
for row in graph:
self.logger.info("".join(row))
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
grid = [[ord(cell) - ord("a") for cell in line] for line in lines] grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
@ -145,19 +149,20 @@ assert end is not None
grid[start[0]][start[1]] = 0 grid[start[0]][start[1]] = 0
grid[end[0]][end[1]] = ord("z") - ord("a") grid[end[0]][end[1]] = ord("z") - ord("a")
lengths_1, parents_1 = dijkstra( lengths_1, parents_1 = dijkstra(
start=start, neighbors=lambda n: neighbors(grid, n, True), cost=lambda lhs, rhs: 1 start=start,
neighbors=lambda n: neighbors(grid, n, True),
cost=lambda lhs, rhs: 1,
) )
path_1 = make_path(parents_1, start, end) path_1 = make_path(parents_1, start, end)
assert path_1 is not None assert path_1 is not None
print_path(path_1, n_rows=len(grid), n_cols=len(grid[0])) self.print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
yield lengths_1[end] - 1
print(f"answer 1 is {lengths_1[end] - 1}") lengths_2, _ = dijkstra(
start=end,
lengths_2, parents_2 = dijkstra( neighbors=lambda n: neighbors(grid, n, False),
start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1 cost=lambda lhs, rhs: 1,
) )
answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s) yield min(lengths_2.get(start, float("inf")) for start in start_s)
print(f"answer 2 is {answer_2}")

View File

@ -1,11 +1,8 @@
import json import json
import sys
from functools import cmp_to_key from functools import cmp_to_key
from typing import TypeAlias, cast from typing import Any, Iterator, TypeAlias, cast
blocks = sys.stdin.read().strip().split("\n\n") from ..base import BaseSolver
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
Packet: TypeAlias = list[int | list["Packet"]] Packet: TypeAlias = list[int | list["Packet"]]
@ -28,8 +25,12 @@ def compare(lhs: Packet, rhs: Packet) -> int:
return len(rhs) - len(lhs) return len(rhs) - len(lhs)
answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0) class Solver(BaseSolver):
print(f"answer_1 is {answer_1}") 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]
yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
dividers = [[[2]], [[6]]] dividers = [[[2]], [[6]]]
@ -38,4 +39,4 @@ packets.extend(dividers)
packets = list(reversed(sorted(packets, key=cmp_to_key(compare)))) packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
d_index = [packets.index(d) + 1 for d in dividers] d_index = [packets.index(d) + 1 for d in dividers]
print(f"answer 2 is {d_index[0] * d_index[1]}") yield d_index[0] * d_index[1]

View File

@ -1,6 +1,7 @@
import sys
from enum import Enum, auto from enum import Enum, auto
from typing import Callable, cast from typing import Any, Callable, Iterator, cast
from ..base import BaseSolver
class Cell(Enum): class Cell(Enum):
@ -12,26 +13,6 @@ class Cell(Enum):
return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self] 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( def flow(
blocks: dict[tuple[int, int], Cell], blocks: dict[tuple[int, int], Cell],
stop_fn: Callable[[int, int], bool], stop_fn: Callable[[int, int], bool],
@ -84,19 +65,44 @@ def flow(
# === inputs === # === inputs ===
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def print_blocks(self, 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):
self.logger.info(
"".join(
str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1)
)
)
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
paths: list[list[tuple[int, int]]] = [] paths: list[list[tuple[int, int]]] = []
for line in lines: for line in lines:
parts = line.split(" -> ") parts = line.split(" -> ")
paths.append( paths.append(
[ [
cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(","))) cast(
tuple[int, int], tuple(int(c.strip()) for c in part.split(","))
)
for part in parts for part in parts
] ]
) )
blocks: dict[tuple[int, int], Cell] = {} blocks: dict[tuple[int, int], Cell] = {}
for path in paths: for path in paths:
for start, end in zip(path[:-1], path[1:]): for start, end in zip(path[:-1], path[1:]):
@ -109,24 +115,17 @@ for path in paths:
for y in range(y_start, y_end): for y in range(y_start, y_end):
blocks[x, y] = Cell.ROCK blocks[x, y] = Cell.ROCK
print_blocks(blocks) self.print_blocks(blocks)
print()
x_min, y_min, x_max, y_max = ( y_max = max(y for _, y in blocks)
min(x for x, _ in blocks),
0,
max(x for x, _ in blocks),
max(y for _, y in blocks),
)
# === part 1 === # === part 1 ===
blocks_1 = flow( blocks_1 = flow(
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
) )
print_blocks(blocks_1) self.print_blocks(blocks_1)
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}") yield sum(v == Cell.SAND for v in blocks_1.values())
print()
# === part 2 === # === part 2 ===
@ -136,5 +135,5 @@ blocks_2 = flow(
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK, fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
) )
blocks_2[500, 0] = Cell.SAND blocks_2[500, 0] = Cell.SAND
print_blocks(blocks_2) self.print_blocks(blocks_2)
print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}") yield sum(v == Cell.SAND for v in blocks_2.values())

View File

@ -1,4 +1,4 @@
import sys import itertools as it
from typing import Any, Iterator from typing import Any, Iterator
import numpy as np import numpy as np
@ -21,9 +21,7 @@ class Solver(BaseSolver):
no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1)) # type: ignore no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1)) # type: ignore
beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row) 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( no_beacons_row = set(it.chain(*no_beacons_row_l)).difference(beacons_at_row) # type: ignore
beacons_at_row
) # type: ignore
return len(no_beacons_row) return len(no_beacons_row)
@ -62,8 +60,9 @@ class Solver(BaseSolver):
for (sx, sy), (bx, by) in sensor_to_beacon.items(): for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by) d = abs(sx - bx) + abs(sy - by)
m.add_constraint( m.add_constraint(
m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}" m.abs(x - sx) + m.abs(y - sy) >= d + 1, # type: ignore
) # type: ignore ctname=f"ct_{sx}_{sy}",
)
m.set_objective("min", x + y) m.set_objective("min", x + y)
@ -92,5 +91,5 @@ class Solver(BaseSolver):
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max) # x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
x, y, a2 = self.part2_intervals(sensor_to_beacon, xy_max) x, y, a2 = self.part2_intervals(sensor_to_beacon, xy_max)
self.logger.info("answer 2 is {at} (x={x}, y={y})") self.logger.info(f"answer 2 is {a2} (x={x}, y={y})")
yield a2 yield a2

View File

@ -3,12 +3,13 @@ from __future__ import annotations
import heapq import heapq
import itertools import itertools
import re import re
import sys
from collections import defaultdict from collections import defaultdict
from typing import FrozenSet, NamedTuple from typing import Any, FrozenSet, Iterator, NamedTuple
from tqdm import tqdm from tqdm import tqdm
from ..base import BaseSolver
class Pipe(NamedTuple): class Pipe(NamedTuple):
name: str name: str
@ -36,8 +37,8 @@ def breadth_first_search(pipes: dict[str, Pipe], pipe: Pipe) -> dict[Pipe, int]:
Runs a BFS from the given pipe and return the shortest distance (in term of hops) Runs a BFS from the given pipe and return the shortest distance (in term of hops)
to all other pipes. to all other pipes.
""" """
queue = [(0, pipe_1)] queue = [(0, pipe)]
visited = set() visited: set[Pipe] = set()
distances: dict[Pipe, int] = {} distances: dict[Pipe, int] = {}
while len(distances) < len(pipes): while len(distances) < len(pipes):
@ -122,8 +123,9 @@ def part_2(
# === MAIN === # === MAIN ===
lines = sys.stdin.read().splitlines() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
pipes: dict[str, Pipe] = {} pipes: dict[str, Pipe] = {}
for line in lines: for line in lines:
@ -150,9 +152,8 @@ for pipe_1 in pipes.values():
# valves with flow # valves with flow
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0) relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
# 1651, 1653 # 1651, 1653
print(part_1(pipes["AA"], 30, distances, relevant_pipes)) yield part_1(pipes["AA"], 30, distances, relevant_pipes)
# 1707, 2223 # 1707, 2223
print(part_2(pipes["AA"], 26, distances, relevant_pipes)) yield part_2(pipes["AA"], 26, distances, relevant_pipes)

View File

@ -1,12 +1,16 @@
import sys from typing import Any, Iterator, Sequence, TypeAlias, TypeVar
from typing import Sequence, TypeVar
import numpy as np import numpy as np
from numpy.typing import NDArray
from ..base import BaseSolver
T = TypeVar("T") T = TypeVar("T")
Tower: TypeAlias = NDArray[np.bool]
def print_tower(tower: np.ndarray, out: str = "#"):
def print_tower(tower: Tower, out: str = "#"):
print("-" * (tower.shape[1] + 2)) print("-" * (tower.shape[1] + 2))
non_empty = False non_empty = False
for row in reversed(range(1, tower.shape[0])): for row in reversed(range(1, tower.shape[0])):
@ -17,7 +21,7 @@ def print_tower(tower: np.ndarray, out: str = "#"):
print("+" + "-" * tower.shape[1] + "+") print("+" + "-" * tower.shape[1] + "+")
def tower_height(tower: np.ndarray) -> int: def tower_height(tower: Tower) -> int:
return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1) return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1)
@ -45,8 +49,8 @@ def build_tower(
n_rocks: int, n_rocks: int,
jets: str, jets: str,
early_stop: bool = False, early_stop: bool = False,
init: np.ndarray = np.ones(WIDTH, dtype=bool), init: Tower = np.ones(WIDTH, dtype=bool),
) -> tuple[np.ndarray, int, int, dict[int, int]]: ) -> tuple[Tower, int, int, dict[int, int]]:
tower = EMPTY_BLOCKS.copy() tower = EMPTY_BLOCKS.copy()
tower[0, :] = init tower[0, :] = init
@ -95,14 +99,13 @@ def build_tower(
return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights
line = sys.stdin.read().strip() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
tower, *_ = build_tower(2022, line) tower, *_ = build_tower(2022, input)
answer_1 = tower_height(tower) yield tower_height(tower)
print(f"answer 1 is {answer_1}")
TOTAL_ROCKS = 1_000_000_000_000 TOTAL_ROCKS = 1_000_000_000_000
tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, line, True) _tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, input, True)
assert prev_1 > 0 assert prev_1 > 0
# 2767 1513 # 2767 1513
@ -116,5 +119,4 @@ remaining_height = (
heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1] heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
) )
answer_2 = base_height + (n_repeat_towers + 1) * repeat_height + remaining_height yield base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
print(f"answer 2 is {answer_2}")

View File

@ -1,11 +1,16 @@
import sys from typing import Any, Iterator
import numpy as np import numpy as np
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
xyz = np.asarray( xyz = np.asarray(
[ [
tuple(int(x) for x in row.split(",")) # type: ignore tuple(int(x) for x in row.split(",")) # type: ignore
for row in sys.stdin.read().splitlines() for row in input.splitlines()
] ]
) )
@ -14,14 +19,14 @@ xyz = xyz - xyz.min(axis=0) + 1
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool) cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
n_dims = len(cubes.shape)
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)] faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
answer_1 = sum( yield sum(
1 for x, y, z in xyz for dx, dy, dz in faces if not cubes[x + dx, y + dy, z + dz] 1
for x, y, z in xyz
for dx, dy, dz in faces
if not cubes[x + dx, y + dy, z + dz]
) )
print(f"answer 1 is {answer_1}")
visited = np.zeros_like(cubes, dtype=bool) visited = np.zeros_like(cubes, dtype=bool)
queue = [(0, 0, 0)] queue = [(0, 0, 0)]
@ -37,7 +42,9 @@ while queue:
for dx, dy, dz in faces: for dx, dy, dz in faces:
nx, ny, nz = x + dx, y + dy, z + dz nx, ny, nz = x + dx, y + dy, z + dz
if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))): if not all(
n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))
):
continue continue
if visited[nx, ny, nz]: if visited[nx, ny, nz]:
@ -47,4 +54,5 @@ while queue:
n_faces += 1 n_faces += 1
else: else:
queue.append((nx, ny, nz)) queue.append((nx, ny, nz))
print(f"answer 2 is {n_faces}")
yield n_faces

View File

@ -1,10 +1,11 @@
import sys from typing import Any, Iterator, Literal
from typing import Any, Literal
import numpy as np import numpy as np
import parse # pyright: ignore[reportMissingTypeStubs] import parse # pyright: ignore[reportMissingTypeStubs]
from numpy.typing import NDArray from numpy.typing import NDArray
from ..base import BaseSolver
Reagent = Literal["ore", "clay", "obsidian", "geode"] Reagent = Literal["ore", "clay", "obsidian", "geode"]
REAGENTS: tuple[Reagent, ...] = ( REAGENTS: tuple[Reagent, ...] = (
"ore", "ore",
@ -62,29 +63,6 @@ def dominates(lhs: State, rhs: State):
) )
lines = sys.stdin.read().splitlines()
blueprints: list[dict[Reagent, IntOfReagent]] = []
for line in lines:
r: list[int] = parse.parse( # type: ignore
"Blueprint {}: "
"Each ore robot costs {:d} ore. "
"Each clay robot costs {:d} ore. "
"Each obsidian robot costs {:d} ore and {:d} clay. "
"Each geode robot costs {:d} ore and {:d} obsidian.",
line,
)
blueprints.append(
{
"ore": {"ore": r[1]},
"clay": {"ore": r[2]},
"obsidian": {"ore": r[3], "clay": r[4]},
"geode": {"ore": r[5], "obsidian": r[6]},
}
)
def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int: def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
# since we can only build one robot per time, we do not need more than X robots # since we can only build one robot per time, we do not need more than X robots
# of type K where X is the maximum number of K required among all robots, e.g., # of type K where X is the maximum number of K required among all robots, e.g.,
@ -173,11 +151,31 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
return max(state.reagents["geode"] for state in state_after_t[max_time]) return max(state.reagents["geode"] for state in state_after_t[max_time])
answer_1 = sum( class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blueprints: list[dict[Reagent, IntOfReagent]] = []
for line in input.splitlines():
r: list[int] = parse.parse( # type: ignore
"Blueprint {}: "
"Each ore robot costs {:d} ore. "
"Each clay robot costs {:d} ore. "
"Each obsidian robot costs {:d} ore and {:d} clay. "
"Each geode robot costs {:d} ore and {:d} obsidian.",
line,
)
blueprints.append(
{
"ore": {"ore": r[1]},
"clay": {"ore": r[2]},
"obsidian": {"ore": r[3], "clay": r[4]},
"geode": {"ore": r[5], "obsidian": r[6]},
}
)
yield sum(
(i_blueprint + 1) * run(blueprint, 24) (i_blueprint + 1) * run(blueprint, 24)
for i_blueprint, blueprint in enumerate(blueprints) for i_blueprint, blueprint in enumerate(blueprints)
) )
print(f"answer 1 is {answer_1}")
answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32) yield (run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32))
print(f"answer 2 is {answer_2}")

View File

@ -1,4 +1,6 @@
import sys from typing import Any, Iterator
from ..base import BaseSolver
def score_1(ux: int, vx: int) -> int: def score_1(ux: int, vx: int) -> int:
@ -33,7 +35,9 @@ def score_2(ux: int, vx: int) -> int:
return (ux + vx - 1) % 3 + 1 + vx * 3 return (ux + vx - 1) % 3 + 1 + vx * 3
lines = sys.stdin.readlines() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using # the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
# modulo-3 arithmetic # modulo-3 arithmetic
@ -47,7 +51,7 @@ lines = sys.stdin.readlines()
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines] values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
# part 1 - 13526 # part 1 - 13526
print(f"answer 1 is {sum(score_1(*v) for v in values)}") yield sum(score_1(*v) for v in values)
# part 2 - 14204 # part 2 - 14204
print(f"answer 2 is {sum(score_2(*v) for v in values)}") yield sum(score_2(*v) for v in values)

View File

@ -1,6 +1,8 @@
from __future__ import annotations from __future__ import annotations
import sys from typing import Any, Iterator
from ..base import BaseSolver
class Number: class Number:
@ -65,10 +67,9 @@ def decrypt(numbers: list[Number], key: int, rounds: int) -> int:
) )
numbers = [Number(int(x)) for i, x in enumerate(sys.stdin.readlines())] class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
numbers = [Number(int(x)) for x in input.splitlines()]
answer_1 = decrypt(numbers, 1, 1) yield decrypt(numbers, 1, 1)
print(f"answer 1 is {answer_1}") yield decrypt(numbers, 811589153, 10)
answer_2 = decrypt(numbers, 811589153, 10)
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,7 @@
import operator import operator
import sys from typing import Any, Callable, Iterator
from typing import Callable
from ..base import BaseSolver
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int: def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
@ -77,7 +78,9 @@ def invert(
return monkeys return monkeys
lines = sys.stdin.read().splitlines() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
monkeys: dict[str, int | tuple[str, str, str]] = {} monkeys: dict[str, int | tuple[str, str, str]] = {}
@ -96,12 +99,10 @@ for line in lines:
op_monkeys.add(name) op_monkeys.add(name)
yield compute(monkeys.copy(), "root")
answer_1 = compute(monkeys.copy(), "root")
print(f"answer 1 is {answer_1}")
# assume the second operand of 'root' can be computed, and the first one depends on # assume the second operand of 'root' can be computed, and the first one depends on
# humn, which is the case is my input and the test input # humn, which is the case is my input and the test input
assert isinstance(monkeys["root"], tuple)
p1, _, p2 = monkeys["root"] # type: ignore p1, _, p2 = monkeys["root"] # type: ignore
answer_2 = compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn") yield compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
print(f"answer 2 is {answer_2}")

View File

@ -1,16 +1,19 @@
import re import re
import sys from typing import Any, Callable, Iterator
from typing import Callable
import numpy as np import numpy as np
from ..base import BaseSolver
VOID, EMPTY, WALL = 0, 1, 2 VOID, EMPTY, WALL = 0, 1, 2
TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL} TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}
SCORES = {"E": 0, "S": 1, "W": 2, "N": 3} SCORES = {"E": 0, "S": 1, "W": 2, "N": 3}
board_map_s, direction_s = sys.stdin.read().split("\n\n") class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
board_map_s, direction_s = input.split("\n\n")
# board # board
board_lines = board_map_s.splitlines() board_lines = board_map_s.splitlines()
@ -23,16 +26,19 @@ board = np.array(
) )
directions = [ directions = [
int(p1) if p2 else p1 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s) int(p1) if p2 else p1
for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
] ]
# find on each row and column the first and last non-void # find on each row and column the first and last non-void
row_first_non_void = np.argmax(board != VOID, axis=1) row_first_non_void = np.argmax(board != VOID, axis=1)
row_last_non_void = board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1 row_last_non_void = (
board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1
)
col_first_non_void = np.argmax(board != VOID, axis=0) col_first_non_void = np.argmax(board != VOID, axis=0)
col_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1 col_last_non_void = (
board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
)
faces = np.zeros_like(board) faces = np.zeros_like(board)
size = np.gcd(board.shape[0], board.shape[1]) size = np.gcd(board.shape[0], board.shape[1])
@ -103,7 +109,6 @@ else:
}, },
} }
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]: def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
if r0 == "E": if r0 == "E":
return y0, row_first_non_void[y0], r0 return y0, row_first_non_void[y0], r0
@ -116,14 +121,14 @@ def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
assert False assert False
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]: def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
cube = faces[y0, x0] cube = faces[y0, x0]
assert r0 in faces_wrap[cube] assert r0 in faces_wrap[cube]
return faces_wrap[cube][r0](y0, x0) return faces_wrap[cube][r0](y0, x0)
def run(
def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int, str]: wrap: Callable[[int, int, str], tuple[int, int, str]],
) -> tuple[int, int, str]:
y0 = 0 y0 = 0
x0 = np.where(board[0] == EMPTY)[0][0] x0 = np.where(board[0] == EMPTY)[0][0]
r0 = "E" r0 = "E"
@ -132,7 +137,9 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
if isinstance(direction, int): if isinstance(direction, int):
while direction > 0: while direction > 0:
if r0 == "E": if r0 == "E":
xi = np.where(board[y0, x0 + 1 : x0 + direction + 1] == WALL)[0] xi = np.where(
board[y0, x0 + 1 : x0 + direction + 1] == WALL
)[0]
if len(xi): if len(xi):
x0 = x0 + xi[0] x0 = x0 + xi[0]
direction = 0 direction = 0
@ -148,10 +155,14 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
x0 = row_last_non_void[y0] x0 = row_last_non_void[y0]
direction = 0 direction = 0
else: else:
direction = direction - (row_last_non_void[y0] - x0) - 1 direction = (
direction - (row_last_non_void[y0] - x0) - 1
)
y0, x0, r0 = y0_t, x0_t, r0_t y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "S": elif r0 == "S":
yi = np.where(board[y0 + 1 : y0 + direction + 1, x0] == WALL)[0] yi = np.where(
board[y0 + 1 : y0 + direction + 1, x0] == WALL
)[0]
if len(yi): if len(yi):
y0 = y0 + yi[0] y0 = y0 + yi[0]
direction = 0 direction = 0
@ -167,7 +178,9 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
y0 = col_last_non_void[x0] y0 = col_last_non_void[x0]
direction = 0 direction = 0
else: else:
direction = direction - (col_last_non_void[x0] - y0) - 1 direction = (
direction - (col_last_non_void[x0] - y0) - 1
)
y0, x0, r0 = y0_t, x0_t, r0_t y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "W": elif r0 == "W":
left = max(x0 - direction - 1, 0) left = max(x0 - direction - 1, 0)
@ -175,7 +188,10 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
if len(xi): if len(xi):
x0 = left + xi[-1] + 1 x0 = left + xi[-1] + 1
direction = 0 direction = 0
elif x0 - direction >= 0 and board[y0, x0 - direction] == EMPTY: elif (
x0 - direction >= 0
and board[y0, x0 - direction] == EMPTY
):
x0 = x0 - direction x0 = x0 - direction
direction = 0 direction = 0
else: else:
@ -184,7 +200,9 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
x0 = row_first_non_void[y0] x0 = row_first_non_void[y0]
direction = 0 direction = 0
else: else:
direction = direction - (x0 - row_first_non_void[y0]) - 1 direction = (
direction - (x0 - row_first_non_void[y0]) - 1
)
y0, x0, r0 = y0_t, x0_t, r0_t y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "N": elif r0 == "N":
top = max(y0 - direction - 1, 0) top = max(y0 - direction - 1, 0)
@ -192,7 +210,10 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
if len(yi): if len(yi):
y0 = top + yi[-1] + 1 y0 = top + yi[-1] + 1
direction = 0 direction = 0
elif y0 - direction >= 0 and board[y0 - direction, x0] == EMPTY: elif (
y0 - direction >= 0
and board[y0 - direction, x0] == EMPTY
):
y0 = y0 - direction y0 = y0 - direction
direction = 0 direction = 0
else: else:
@ -201,7 +222,9 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
y0 = col_first_non_void[x0] y0 = col_first_non_void[x0]
direction = 0 direction = 0
else: else:
direction = direction - (y0 - col_first_non_void[x0]) - 1 direction = (
direction - (y0 - col_first_non_void[x0]) - 1
)
y0, x0, r0 = y0_t, x0_t, r0_t y0, x0, r0 = y0_t, x0_t, r0_t
else: else:
r0 = { r0 = {
@ -213,11 +236,8 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
return y0, x0, r0 return y0, x0, r0
y1, x1, r1 = run(wrap_part_1) y1, x1, r1 = run(wrap_part_1)
answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1] yield 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
print(f"answer 1 is {answer_1}")
y2, x2, r2 = run(wrap_part_2) y2, x2, r2 = run(wrap_part_2)
answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2] yield 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,8 @@
import itertools import itertools
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
Directions = list[ Directions = list[
tuple[ tuple[
@ -18,7 +20,7 @@ DIRECTIONS: Directions = [
def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]: def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]:
ys, xs = {y for y, x in positions}, {x for y, x in positions} ys, xs = {y for y, _x in positions}, {x for _y, x in positions}
return min(ys), min(xs), max(ys), max(xs) return min(ys), min(xs), max(ys), max(xs)
@ -69,9 +71,11 @@ def round(
directions.append(directions.pop(0)) directions.append(directions.pop(0))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
POSITIONS = { POSITIONS = {
(i, j) (i, j)
for i, row in enumerate(sys.stdin.read().splitlines()) for i, row in enumerate(input.splitlines())
for j, col in enumerate(row) for j, col in enumerate(row)
if col == "#" if col == "#"
} }
@ -79,14 +83,15 @@ POSITIONS = {
# === part 1 === # === part 1 ===
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy() p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
for r in range(10): for _ in range(10):
round(p1, d1) round(p1, d1)
min_y, min_x, max_y, max_x = min_max_yx(p1) min_y, min_x, max_y, max_x = min_max_yx(p1)
answer_1 = sum( yield sum(
(y, x) not in p1 for y in range(min_y, max_y + 1) for x in range(min_x, max_x + 1) (y, x) not in p1
for y in range(min_y, max_y + 1)
for x in range(min_x, max_x + 1)
) )
print(f"answer 1 is {answer_1}")
# === part 2 === # === part 2 ===
@ -100,4 +105,4 @@ while True:
if backup == p2: if backup == p2:
break break
print(f"answer 2 is {answer_2}") yield answer_2

View File

@ -1,9 +1,14 @@
import heapq import heapq
import math import math
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
lines = sys.stdin.read().splitlines() from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
winds = { winds = {
(i - 1, j - 1, lines[i][j]) (i - 1, j - 1, lines[i][j])
@ -15,8 +20,12 @@ winds = {
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2 n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
CYCLE = math.lcm(n_rows, n_cols) CYCLE = math.lcm(n_rows, n_cols)
east_winds = [{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)] east_winds = [
west_winds = [{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)] {j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)
]
west_winds = [
{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)
]
north_winds = [ north_winds = [
{i for i in range(n_rows) if (i, j, "^") in winds} for j in range(n_cols) {i for i in range(n_rows) if (i, j, "^") in winds} for j in range(n_cols)
] ]
@ -24,13 +33,14 @@ south_winds = [
{i for i in range(n_rows) if (i, j, "v") in winds} for j in range(n_cols) {i for i in range(n_rows) if (i, j, "v") in winds} for j in range(n_cols)
] ]
def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]): def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
def heuristic(y: int, x: int) -> int: def heuristic(y: int, x: int) -> int:
return abs(end[0] - y) + abs(end[1] - x) return abs(end[0] - y) + abs(end[1] - x)
# (distance + heuristic, distance, (start_pos, cycle)) # (distance + heuristic, distance, (start_pos, cycle))
queue = [(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))] queue = [
(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))
]
visited: set[tuple[tuple[int, int], int]] = set() visited: set[tuple[tuple[int, int], int]] = set()
distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {}) distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {})
@ -54,13 +64,17 @@ def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
n_cycle = (cycle + 1) % CYCLE n_cycle = (cycle + 1) % CYCLE
if (ty, tx) == end: if (ty, tx) == end:
heapq.heappush(queue, (distance + 1, distance + 1, ((ty, tx), n_cycle))) heapq.heappush(
queue, (distance + 1, distance + 1, ((ty, tx), n_cycle))
)
break break
if ((ty, tx), n_cycle) in visited: if ((ty, tx), n_cycle) in visited:
continue continue
if (ty, tx) != start and (ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols): if (ty, tx) != start and (
ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols
):
continue continue
if (ty, tx) != start: if (ty, tx) != start:
@ -75,12 +89,17 @@ def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
heapq.heappush( heapq.heappush(
queue, queue,
((heuristic(ty, tx) + distance + 1, distance + 1, ((ty, tx), n_cycle))), (
(
heuristic(ty, tx) + distance + 1,
distance + 1,
((ty, tx), n_cycle),
)
),
) )
return distances, next(iter(distances[end].values())) return distances, next(iter(distances[end].values()))
start = ( start = (
-1, -1,
next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1, next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
@ -91,8 +110,8 @@ end = (
) )
distances_1, forward_1 = run(start, 0, end) distances_1, forward_1 = run(start, 0, end)
print(f"answer 1 is {forward_1}") yield forward_1
distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start) distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start)
distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end) _distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
print(f"answer 2 is {forward_1 + return_1 + forward_2}") yield forward_1 + return_1 + forward_2

View File

@ -1,10 +1,14 @@
import sys from typing import Any, Iterator
lines = sys.stdin.read().splitlines() from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2} coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
def snafu2number(number: str) -> int: def snafu2number(number: str) -> int:
value = 0 value = 0
for c in number: for c in number:
@ -12,7 +16,6 @@ def snafu2number(number: str) -> int:
value += coeffs[c] value += coeffs[c]
return value return value
def number2snafu(number: int) -> str: def number2snafu(number: int) -> str:
values = ["0", "1", "2", "=", "-"] values = ["0", "1", "2", "=", "-"]
res = "" res = ""
@ -22,6 +25,4 @@ def number2snafu(number: int) -> str:
number = number // 5 + int(mod >= 3) number = number // 5 + int(mod >= 3)
return "".join(reversed(res)) return "".join(reversed(res))
yield number2snafu(sum(map(snafu2number, lines)))
answer_1 = number2snafu(sum(map(snafu2number, lines)))
print(f"answer 1 is {answer_1}")

View File

@ -1,23 +1,28 @@
import string import string
import sys from typing import Any, Iterator
lines = [line.strip() for line in sys.stdin.readlines()] from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# extract content of each part # extract content of each part
parts = [(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines] parts = [
(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines
]
# priorities # priorities
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)} priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
# part 1 # part 1
part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2)) yield sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
print(f"answer 1 is {part1}")
# part 2 # part 2
n_per_group = 3 n_per_group = 3
part2 = sum( yield sum(
priorities[c] priorities[c]
for i in range(0, len(lines), n_per_group) for i in range(0, len(lines), n_per_group)
for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group]) for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group])
) )
print(f"answer 2 is {part2}")

View File

@ -1,6 +1,6 @@
import sys from typing import Any, Iterator
lines = [line.strip() for line in sys.stdin.readlines()] from ..base import BaseSolver
def make_range(value: str) -> set[int]: def make_range(value: str) -> set[int]:
@ -8,10 +8,13 @@ def make_range(value: str) -> set[int]:
return set(range(int(parts[0]), int(parts[1]) + 1)) return set(range(int(parts[0]), int(parts[1]) + 1))
sections = [tuple(make_range(part) for part in line.split(",")) for line in lines] class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections) sections = [
print(f"answer 1 is {answer_1}") tuple(make_range(part) for part in line.split(",")) for line in lines
]
answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections) yield sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
print(f"answer 1 is {answer_2}") yield sum(bool(s1.intersection(s2)) for s1, s2 in sections)

View File

@ -1,7 +1,12 @@
import copy import copy
import sys from typing import Any, Iterator
blocks_s, moves_s = (part.splitlines() for part in sys.stdin.read().split("\n\n")) from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blocks_s, moves_s = (part.splitlines() for part in input.split("\n\n"))
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()} blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
@ -34,8 +39,5 @@ for move in moves_s:
blocks_2[to_].extend(blocks_2[from_][-count:]) blocks_2[to_].extend(blocks_2[from_][-count:])
del blocks_2[from_][-count:] del blocks_2[from_][-count:]
answer_1 = "".join(s[-1] for s in blocks_1.values()) yield "".join(s[-1] for s in blocks_1.values())
print(f"answer 1 is {answer_1}") yield "".join(s[-1] for s in blocks_2.values())
answer_2 = "".join(s[-1] for s in blocks_2.values())
print(f"answer 2 is {answer_2}")

View File

@ -1,4 +1,6 @@
import sys from typing import Any, Iterator
from ..base import BaseSolver
def index_of_first_n_differents(data: str, n: int) -> int: def index_of_first_n_differents(data: str, n: int) -> int:
@ -8,8 +10,7 @@ def index_of_first_n_differents(data: str, n: int) -> int:
return -1 return -1
data = sys.stdin.read().strip() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
yield index_of_first_n_differents(input, 4)
print(f"answer 1 is {index_of_first_n_differents(data, 4)}") yield index_of_first_n_differents(input, 14)
print(f"answer 2 is {index_of_first_n_differents(data, 14)}")

View File

@ -1,7 +1,12 @@
import sys
from pathlib import Path from pathlib import Path
from typing import Any, Iterator
lines = sys.stdin.read().splitlines() from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# we are going to use Path to create path and go up/down in the file tree since it # we are going to use Path to create path and go up/down in the file tree since it
# implements everything we need # implements everything we need
@ -53,7 +58,6 @@ for line in lines[1:]:
trees[cur_path].append(path) trees[cur_path].append(path)
sizes[path] = size sizes[path] = size
def compute_size(path: Path) -> int: def compute_size(path: Path) -> int:
size = sizes[path] size = sizes[path]
@ -62,12 +66,10 @@ def compute_size(path: Path) -> int:
return sum(compute_size(sub) for sub in trees[path]) return sum(compute_size(sub) for sub in trees[path])
acc_sizes = {path: compute_size(path) for path in trees} acc_sizes = {path: compute_size(path) for path in trees}
# part 1 # part 1
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000) yield sum(size for size in acc_sizes.values() if size <= 100_000)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
total_space = 70_000_000 total_space = 70_000_000
@ -76,5 +78,4 @@ free_space = total_space - acc_sizes[base_path]
to_free_space = update_space - free_space to_free_space = update_space - free_space
answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space) yield min(size for size in acc_sizes.values() if size >= to_free_space)
print(f"answer 2 is {answer_2}")

View File

@ -1,9 +1,14 @@
import sys from typing import Any, Iterator
import numpy as np import numpy as np
from numpy.typing import NDArray from numpy.typing import NDArray
lines = sys.stdin.read().splitlines() from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
trees = np.array([[int(x) for x in row] for row in lines]) trees = np.array([[int(x) for x in row] for row in lines])
@ -22,9 +27,7 @@ highest_trees[1:-1, 1:-1] = [
for i in range(1, trees.shape[0] - 1) for i in range(1, trees.shape[0] - 1)
] ]
answer_1 = (highest_trees.min(axis=2) < trees).sum() yield (highest_trees.min(axis=2) < trees).sum()
print(f"answer 1 is {answer_1}")
def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int: def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
w = np.where(row_of_trees >= value)[0] w = np.where(row_of_trees >= value)[0]
@ -34,7 +37,6 @@ def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
return w[0] + 1 return w[0] + 1
# answer 2 # answer 2
v_distances = np.zeros(trees.shape + (4,), dtype=int) v_distances = np.zeros(trees.shape + (4,), dtype=int)
v_distances[1:-1, 1:-1, :] = [ v_distances[1:-1, 1:-1, :] = [
@ -49,5 +51,4 @@ v_distances[1:-1, 1:-1, :] = [
] ]
for i in range(1, trees.shape[0] - 1) for i in range(1, trees.shape[0] - 1)
] ]
answer_2 = np.prod(v_distances, axis=2).max() yield np.prod(v_distances, axis=2).max()
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,10 @@
import sys import itertools as it
from typing import Any, Iterator
import numpy as np import numpy as np
from ..base import BaseSolver
def move(head: tuple[int, int], command: str) -> tuple[int, int]: def move(head: tuple[int, int], command: str) -> tuple[int, int]:
h_col, h_row = head h_col, h_row = head
@ -43,17 +46,14 @@ def run(commands: list[str], n_blocks: int) -> list[tuple[int, int]]:
return visited return visited
lines = sys.stdin.read().splitlines() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# flatten the commands # flatten the commands
commands: list[str] = [] commands = list(
for line in lines: it.chain(*(p[0] * int(p[1]) for line in lines if (p := line.split())))
d, c = line.split() )
commands.extend(d * int(c))
yield len(set(run(commands, n_blocks=2)))
visited_1 = run(commands, n_blocks=2) yield len(set(run(commands, n_blocks=10)))
print(f"answer 1 is {len(set(visited_1))}")
visited_2 = run(commands, n_blocks=10)
print(f"answer 2 is {len(set(visited_2))}")

View File

@ -1,3 +1,5 @@
# pyright: reportUnknownMemberType=false
from typing import Any, Iterator from typing import Any, Iterator
import networkx as nx import networkx as nx

View File

@ -179,7 +179,7 @@ def main():
start = datetime.now() start = datetime.now()
last = start last = start
it = solver.solve(data.strip()) it = solver.solve(data.rstrip())
if it is None: if it is None:
solver.logger.error(f"no implementation for {year} day {day}") solver.logger.error(f"no implementation for {year} day {day}")