Start fixing 2022 for new API.
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing

This commit is contained in:
Mikaël Capelle 2024-12-08 13:24:48 +01:00
parent 377e501d34
commit 6fd569aeba
26 changed files with 462 additions and 398 deletions

View File

@ -1,7 +1,12 @@
import sys from typing import Any, Iterator
blocks = sys.stdin.read().split("\n\n") from ..base import BaseSolver
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:])}") 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)
yield values[-1]
yield sum(values[-3:])

View File

@ -1,38 +1,43 @@
import sys from typing import Any, Iterator
lines = sys.stdin.read().splitlines() from ..base import BaseSolver
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): class Solver(BaseSolver):
for j in range(40): def solve(self, input: str) -> Iterator[Any]:
v = values[1 + i * 40 + j] lines = [line.strip() for line in input.splitlines()]
if j >= v - 1 and j <= v + 1: cycle, x = 1, 1
print("#", end="") values = {cycle: x}
else:
print(".", end="")
print() 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))
yield answer_1
yield (
"\n"
+ "\n".join(
"".join(
"#"
if j >= (v := values[1 + i * 40 + j]) - 1 and j <= v + 1
else "."
for j in range(40)
)
for i in range(6)
)
+ "\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,24 +120,28 @@ 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
# #
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value # (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 # (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 # 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),
print(f"answer 2 is {answer_2}") 10_000,
me_worry_fn=lambda w: w % total_test_value,
)
)

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,46 +95,74 @@ def neighbors(
# === main code === # === main code ===
lines = sys.stdin.read().splitlines()
grid = [[ord(cell) - ord("a") for cell in line] for line in lines] class Solver(BaseSolver):
def print_path(self, path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
end = path[-1]
start: tuple[int, int] | None = None graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
end: tuple[int, int] | None = None graph[end[0]][end[1]] = "E"
# for part 2 for i in range(0, len(path) - 1):
start_s: list[tuple[int, int]] = [] cr, cc = path[i]
nr, nc = path[i + 1]
for i_row, row in enumerate(grid): if cr == nr and nc == cc - 1:
for i_col, col in enumerate(row): graph[cr][cc] = "<"
if chr(col + ord("a")) == "S": elif cr == nr and nc == cc + 1:
start = (i_row, i_col) graph[cr][cc] = ">"
start_s.append(start) elif cr == nr - 1 and nc == cc:
elif chr(col + ord("a")) == "E": graph[cr][cc] = "v"
end = (i_row, i_col) elif cr == nr + 1 and nc == cc:
elif col == 0: graph[cr][cc] = "^"
start_s.append((i_row, i_col)) else:
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
assert start is not None for row in graph:
assert end is not None self.logger.info("".join(row))
# fix values def solve(self, input: str) -> Iterator[Any]:
grid[start[0]][start[1]] = 0 lines = input.splitlines()
grid[end[0]][end[1]] = ord("z") - ord("a")
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
lengths_1, parents_1 = dijkstra( start: tuple[int, int] | None = None
start=start, neighbors=lambda n: neighbors(grid, n, True), cost=lambda lhs, rhs: 1 end: tuple[int, int] | None = None
)
path_1 = make_path(parents_1, start, end)
assert path_1 is not 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))
lengths_2, parents_2 = dijkstra( assert start is not None
start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1 assert end is not None
)
answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s) # fix values
print(f"answer 2 is {answer_2}") 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)

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,14 +25,18 @@ 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]
dividers = [[[2]], [[6]]] yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
packets = [packet for packets in pairs for packet in packets] dividers = [[[2]], [[6]]]
packets.extend(dividers)
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
d_index = [packets.index(d) + 1 for d in dividers] packets = [packet for packets in pairs for packet in packets]
print(f"answer 2 is {d_index[0] * d_index[1]}") 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]

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,57 +65,75 @@ def flow(
# === inputs === # === inputs ===
lines = sys.stdin.read().splitlines()
paths: list[list[tuple[int, int]]] = [] class Solver(BaseSolver):
for line in lines: def print_blocks(self, blocks: dict[tuple[int, int], Cell]):
parts = line.split(" -> ") """
paths.append( Print the given set of blocks on a grid.
[
cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
for part in parts
]
)
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),
)
blocks: dict[tuple[int, int], Cell] = {} for y in range(y_min, y_max + 1):
for path in paths: self.logger.info(
for start, end in zip(path[:-1], path[1:]): "".join(
x_start = min(start[0], end[0]) str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1)
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): def solve(self, input: str) -> Iterator[Any]:
for y in range(y_start, y_end): lines = [line.strip() for line in input.splitlines()]
blocks[x, y] = Cell.ROCK
print_blocks(blocks) paths: list[list[tuple[int, int]]] = []
print() 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
]
)
x_min, y_min, x_max, y_max = ( blocks: dict[tuple[int, int], Cell] = {}
min(x for x, _ in blocks), for path in paths:
0, for start, end in zip(path[:-1], path[1:]):
max(x for x, _ in blocks), x_start = min(start[0], end[0])
max(y for _, y in blocks), x_end = max(start[0], end[0]) + 1
) y_start = min(start[1], end[1])
y_end = max(start[1], end[1]) + 1
# === part 1 === for x in range(x_start, x_end):
for y in range(y_start, y_end):
blocks[x, y] = Cell.ROCK
blocks_1 = flow( self.print_blocks(blocks)
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 === y_max = max(y for _, y in blocks)
blocks_2 = flow( # === part 1 ===
blocks.copy(),
stop_fn=lambda x, y: x == 500 and y == 0, blocks_1 = flow(
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK, blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
) )
blocks_2[500, 0] = Cell.SAND self.print_blocks(blocks_1)
print_blocks(blocks_2) yield sum(v == Cell.SAND for v in blocks_1.values())
print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")
# === 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
self.print_blocks(blocks_2)
yield sum(v == Cell.SAND for v in blocks_2.values())

View File

@ -1,4 +1,3 @@
import sys
from typing import Any, Iterator from typing import Any, Iterator
import numpy as np import numpy as np
@ -62,8 +61,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)

View File

@ -5,10 +5,12 @@ import itertools
import re import re
import sys 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

View File

@ -1,8 +1,10 @@
import sys import sys
from typing import Sequence, TypeVar from typing import Any, Iterator, Sequence, TypeVar
import numpy as np import numpy as np
from ..base import BaseSolver
T = TypeVar("T") T = TypeVar("T")

View File

@ -1,7 +1,10 @@
import sys import sys
from typing import Any, Iterator
import numpy as np import numpy as np
from ..base import BaseSolver
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

View File

@ -1,10 +1,12 @@
import sys import sys
from typing import Any, Literal from typing import Any, Iterator, 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",

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,21 +35,23 @@ 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
# #
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move # 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))) # 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 # 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 # easier manipulation
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,9 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from typing import Any, Iterator
from ..base import BaseSolver
class Number: class Number:

View File

@ -1,6 +1,8 @@
import operator import operator
import sys import sys
from typing import Callable from typing import Any, Callable, Iterator
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:

View File

@ -1,9 +1,11 @@
import re import re
import sys import sys
from typing import Callable from typing import Any, Callable, Iterator
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}

View File

@ -1,6 +1,9 @@
import itertools import itertools
import sys 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[

View File

@ -2,6 +2,9 @@ import heapq
import math import math
import sys import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
lines = sys.stdin.read().splitlines() lines = sys.stdin.read().splitlines()

View File

@ -1,4 +1,7 @@
import sys import sys
from typing import Any, Iterator
from ..base import BaseSolver
lines = sys.stdin.read().splitlines() lines = sys.stdin.read().splitlines()

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

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,41 +1,43 @@
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
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 class Solver(BaseSolver):
# form of '[X] ' (including the trailing space) def solve(self, input: str) -> Iterator[Any]:
# blocks_s, moves_s = (part.splitlines() for part in input.split("\n\n"))
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: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
blocks[stack].append(crate)
# part 1 - deep copy for part 2 # this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the
blocks_1 = copy.deepcopy(blocks) # 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()
for move in moves_s: if crate:
_, count_s, _, from_, _, to_ = move.strip().split() blocks[stack].append(crate)
for _i in range(int(count_s)): # part 1 - deep copy for part 2
blocks_1[to_].append(blocks_1[from_].pop()) blocks_1 = copy.deepcopy(blocks)
# part 2 for move in moves_s:
blocks_2 = copy.deepcopy(blocks) _, count_s, _, from_, _, to_ = move.strip().split()
for move in moves_s: for _i in range(int(count_s)):
_, count_s, _, from_, _, to_ = move.strip().split() blocks_1[to_].append(blocks_1[from_].pop())
count = int(count_s)
blocks_2[to_].extend(blocks_2[from_][-count:]) # part 2
del blocks_2[from_][-count:] blocks_2 = copy.deepcopy(blocks)
answer_1 = "".join(s[-1] for s in blocks_1.values()) for move in moves_s:
print(f"answer 1 is {answer_1}") _, count_s, _, from_, _, to_ = move.strip().split()
count = int(count_s)
answer_2 = "".join(s[-1] for s in blocks_2.values()) blocks_2[to_].extend(blocks_2[from_][-count:])
print(f"answer 2 is {answer_2}") del blocks_2[from_][-count:]
yield "".join(s[-1] for s in blocks_1.values())
yield "".join(s[-1] for s in blocks_2.values())

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,80 +1,81 @@
import sys
from pathlib import Path from pathlib import Path
from typing import Any, Iterator
lines = sys.stdin.read().splitlines() from ..base import BaseSolver
# 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: class Solver(BaseSolver):
size = sizes[path] def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
if size >= 0: # we are going to use Path to create path and go up/down in the file tree since it
return size # 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
#
return sum(compute_size(sub) for sub in trees[path]) # 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] = {}
acc_sizes = {path: compute_size(path) for path in trees} # 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
# part 1 trees[cur_path] = []
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000) sizes[cur_path] = -1
print(f"answer 1 is {answer_1}")
# part 2 for line in lines[1:]:
total_space = 70_000_000 # command
update_space = 30_000_000 if line.startswith("$"):
free_space = total_space - acc_sizes[base_path] parts = line.strip("$").strip().split()
command = parts[0]
to_free_space = update_space - free_space if command == "cd":
cur_path = cur_path.joinpath(parts[1]).resolve()
answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space) # just initialize the lis of files if not already done
print(f"answer 2 is {answer_2}") 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
yield sum(size for size in acc_sizes.values() if size <= 100_000)
# 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
yield min(size for size in acc_sizes.values() if size >= to_free_space)

View File

@ -1,53 +1,54 @@
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
trees = np.array([[int(x) for x in row] for row in lines])
# answer 1 class Solver(BaseSolver):
highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1 def solve(self, input: str) -> Iterator[Any]:
highest_trees[1:-1, 1:-1] = [ lines = [line.strip() for line in input.splitlines()]
[
[ trees = np.array([[int(x) for x in row] for row in lines])
trees[:i, j].max(),
trees[i + 1 :, j].max(), # answer 1
trees[i, :j].max(), highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1
trees[i, j + 1 :].max(), highest_trees[1:-1, 1:-1] = [
[
[
trees[:i, j].max(),
trees[i + 1 :, j].max(),
trees[i, :j].max(),
trees[i, j + 1 :].max(),
]
for j in range(1, trees.shape[1] - 1)
]
for i in range(1, trees.shape[0] - 1)
] ]
for j in range(1, trees.shape[1] - 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:
w = np.where(row_of_trees >= value)[0]
def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int: if not w.size:
w = np.where(row_of_trees >= value)[0] return len(row_of_trees)
if not w.size: return w[0] + 1
return len(row_of_trees)
return w[0] + 1 # answer 2
v_distances = np.zeros(trees.shape + (4,), dtype=int)
v_distances[1:-1, 1:-1, :] = [
# answer 2 [
v_distances = np.zeros(trees.shape + (4,), dtype=int) [
v_distances[1:-1, 1:-1, :] = [ viewing_distance(trees[i - 1 :: -1, j], trees[i, j]),
[ viewing_distance(trees[i, j - 1 :: -1], trees[i, j]),
[ viewing_distance(trees[i, j + 1 :], trees[i, j]),
viewing_distance(trees[i - 1 :: -1, j], trees[i, j]), viewing_distance(trees[i + 1 :, j], trees[i, j]),
viewing_distance(trees[i, j - 1 :: -1], trees[i, j]), ]
viewing_distance(trees[i, j + 1 :], trees[i, j]), for j in range(1, trees.shape[1] - 1)
viewing_distance(trees[i + 1 :, j], trees[i, j]), ]
for i in range(1, trees.shape[0] - 1)
] ]
for j in range(1, trees.shape[1] - 1) yield np.prod(v_distances, axis=2).max()
]
for i in range(1, trees.shape[0] - 1)
]
answer_2 = 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