1 Commits

Author SHA1 Message Date
Mikaël Capelle
1289aed19c Start fixing 2015 for new API.
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-12-08 11:57:35 +01:00
54 changed files with 1249 additions and 1294 deletions

View File

@@ -63,6 +63,17 @@ def play(
continue continue
visited.add((player, player_hp, player_mana, player_armor, boss_hp, buffs)) visited.add((player, player_hp, player_mana, player_armor, boss_hp, buffs))
if hard_mode and player == "player":
player_hp = max(0, player_hp - 1)
if player_hp == 0:
continue
if boss_hp == 0:
winning_node = spells
continue
new_buffs: list[tuple[BuffType, int]] = [] new_buffs: list[tuple[BuffType, int]] = []
for buff, length in buffs: for buff, length in buffs:
length = length - 1 length = length - 1
@@ -78,16 +89,6 @@ def play(
if length > 0: if length > 0:
new_buffs.append((buff, length)) new_buffs.append((buff, length))
if hard_mode and player == "player":
player_hp = player_hp - 1
if player_hp <= 0:
continue
if boss_hp <= 0:
winning_node = spells
continue
buffs = tuple(new_buffs) buffs = tuple(new_buffs)
if player == "boss": if player == "boss":

View File

@@ -1,17 +1,14 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
values = [int(line) for line in lines]
class Solver(BaseSolver): # part 1
def solve(self, input: str) -> Iterator[Any]: answer_1 = sum(v2 > v1 for v1, v2 in zip(values[:-1], values[1:]))
lines = input.splitlines() print(f"answer 1 is {answer_1}")
values = [int(line) for line in lines] # part 2
runnings = [sum(values[i : i + 3]) for i in range(len(values) - 2)]
# part 1 answer_2 = sum(v2 > v1 for v1, v2 in zip(runnings[:-1], runnings[1:]))
yield sum(v2 > v1 for v1, v2 in zip(values[:-1], values[1:])) print(f"answer 2 is {answer_2}")
# part 2
runnings = [sum(values[i : i + 3]) for i in range(len(values) - 2)]
yield sum(v2 > v1 for v1, v2 in zip(runnings[:-1], runnings[1:]))

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,20 +1,17 @@
import sys
from math import prod from math import prod
from typing import Any, Iterator, Literal, TypeAlias, cast from typing import Literal, TypeAlias, cast
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
Command: TypeAlias = Literal["forward", "up", "down"] Command: TypeAlias = Literal["forward", "up", "down"]
commands: list[tuple[Command, int]] = [
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
commands: list[tuple[Command, int]] = [
(cast(Command, (p := line.split())[0]), int(p[1])) for line in lines (cast(Command, (p := line.split())[0]), int(p[1])) for line in lines
] ]
def depth_and_position(use_aim: bool):
def depth_and_position(use_aim: bool):
aim, pos, depth = 0, 0, 0 aim, pos, depth = 0, 0, 0
for command, value in commands: for command, value in commands:
d_depth = 0 d_depth = 0
@@ -34,5 +31,11 @@ class Solver(BaseSolver):
return depth, pos return depth, pos
yield prod(depth_and_position(False))
yield prod(depth_and_position(True)) # part 1
answer_1 = prod(depth_and_position(False))
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = prod(depth_and_position(True))
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): # part 2
def solve(self, input: str) -> Iterator[Any]: ... answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,6 @@
import sys
from collections import Counter from collections import Counter
from typing import Any, Iterator, Literal from typing import Literal
from ..base import BaseSolver
def generator_rating( def generator_rating(
@@ -21,23 +20,20 @@ def generator_rating(
return values[0] return values[0]
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# part 1
most_and_least_common = [ # part 1
tuple( most_and_least_common = [
Counter(line[col] for line in lines).most_common(2)[m][0] tuple(Counter(line[col] for line in lines).most_common(2)[m][0] for m in range(2))
for m in range(2)
)
for col in range(len(lines[0])) for col in range(len(lines[0]))
] ]
gamma_rate = int("".join(most for most, _ in most_and_least_common), base=2) gamma_rate = int("".join(most for most, _ in most_and_least_common), base=2)
epsilon_rate = int("".join(least for _, least in most_and_least_common), base=2) epsilon_rate = int("".join(least for _, least in most_and_least_common), base=2)
yield gamma_rate * epsilon_rate print(f"answer 1 is {gamma_rate * epsilon_rate}")
# part 2 # part 2
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2) oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2) co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
yield oxygen_generator_rating * co2_scrubber_rating answer_2 = oxygen_generator_rating * co2_scrubber_rating
print(f"answer 2 is {answer_2}")

View File

@@ -1,28 +1,23 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
numbers = [int(c) for c in lines[0].split(",")]
class Solver(BaseSolver): boards = np.asarray(
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
numbers = [int(c) for c in lines[0].split(",")]
boards = np.asarray(
[ [
[[int(c) for c in line.split()] for line in lines[start : start + 5]] [[int(c) for c in line.split()] for line in lines[start : start + 5]]
for start in range(2, len(lines), 6) for start in range(2, len(lines), 6)
] ]
) )
# (round, score) for each board (-1 when not found) # (round, score) for each board (-1 when not found)
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))] winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
marked = np.zeros_like(boards, dtype=bool) marked = np.zeros_like(boards, dtype=bool)
for round, number in enumerate(numbers): for round, number in enumerate(numbers):
# mark boards # mark boards
marked[boards == number] = True marked[boards == number] = True
@@ -31,9 +26,7 @@ class Solver(BaseSolver):
if winning_rounds[index][0] > 0: if winning_rounds[index][0] > 0:
continue continue
if np.any( if np.any(np.all(marked[index], axis=0) | np.all(marked[index], axis=1)):
np.all(marked[index], axis=0) | np.all(marked[index], axis=1)
):
winning_rounds[index] = ( winning_rounds[index] = (
round, round,
number * int(np.sum(boards[index][~marked[index]])), number * int(np.sum(boards[index][~marked[index]])),
@@ -43,10 +36,10 @@ class Solver(BaseSolver):
if np.all(marked.all(axis=1) | marked.all(axis=2)): if np.all(marked.all(axis=1) | marked.all(axis=2)):
break break
# part 1 # part 1
(_, score) = min(winning_rounds, key=lambda w: w[0]) (_, score) = min(winning_rounds, key=lambda w: w[0])
yield score print(f"answer 1 is {score}")
# part 2 # part 2
(_, score) = max(winning_rounds, key=lambda w: w[0]) (_, score) = max(winning_rounds, key=lambda w: w[0])
yield score print(f"answer 2 is {score}")

View File

@@ -1,15 +1,10 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from ..base import BaseSolver lines: list[str] = sys.stdin.read().splitlines()
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
( (
( (
int(line.split(" -> ")[0].split(",")[0]), int(line.split(" -> ")[0].split(",")[0]),
@@ -21,19 +16,21 @@ class Solver(BaseSolver):
), ),
) )
for line in lines for line in lines
] ]
np_sections = np.array(sections).reshape(-1, 4) np_sections = np.array(sections).reshape(-1, 4)
x_max, y_max = ( x_min, x_max, y_min, y_max = (
min(np_sections[:, 0].min(), np_sections[:, 2].min()),
max(np_sections[:, 0].max(), np_sections[:, 2].max()), max(np_sections[:, 0].max(), np_sections[:, 2].max()),
min(np_sections[:, 1].min(), np_sections[:, 3].min()),
max(np_sections[:, 1].max(), np_sections[:, 3].max()), max(np_sections[:, 1].max(), np_sections[:, 3].max()),
) )
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int) counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
counts_2 = counts_1.copy() counts_2 = counts_1.copy()
for (x1, y1), (x2, y2) in sections: for (x1, y1), (x2, y2) in sections:
x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1) x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1)
y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1) y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1)
@@ -44,5 +41,8 @@ class Solver(BaseSolver):
for i, j in zip(y_rng, x_rng): for i, j in zip(y_rng, x_rng):
counts_2[i, j] += 1 counts_2[i, j] += 1
yield (counts_1 >= 2).sum() answer_1 = (counts_1 >= 2).sum()
yield (counts_2 >= 2).sum() print(f"answer 1 is {answer_1}")
answer_2 = (counts_2 >= 2).sum()
print(f"answer 2 is {answer_2}")

View File

@@ -1,21 +1,21 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver values = [int(c) for c in sys.stdin.read().strip().split(",")]
days = 256
class Solver(BaseSolver): lanterns = {day: 0 for day in range(days)}
def solve(self, input: str) -> Iterator[Any]: for value in values:
values = [int(c) for c in input.split(",")]
days = 256
lanterns = {day: 0 for day in range(days)}
for value in values:
for day in range(value, days, 7): for day in range(value, days, 7):
lanterns[day] += 1 lanterns[day] += 1
for day in range(days): for day in range(days):
for day2 in range(day + 9, days, 7): for day2 in range(day + 9, days, 7):
lanterns[day2] += lanterns[day] lanterns[day2] += lanterns[day]
yield sum(v for k, v in lanterns.items() if k < 80) + len(values) # part 1
yield sum(lanterns.values()) + len(values) answer_1 = sum(v for k, v in lanterns.items() if k < 80) + len(values)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = sum(lanterns.values()) + len(values)
print(f"answer 2 is {answer_2}")

View File

@@ -1,22 +1,19 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver positions = [int(c) for c in sys.stdin.read().strip().split(",")]
min_position, max_position = min(positions), max(positions)
class Solver(BaseSolver): # part 1
def solve(self, input: str) -> Iterator[Any]: answer_1 = min(
positions = [int(c) for c in input.split(",")]
min_position, max_position = min(positions), max(positions)
# part 1
yield min(
sum(abs(p - position) for p in positions) sum(abs(p - position) for p in positions)
for position in range(min_position, max_position + 1) for position in range(min_position, max_position + 1)
) )
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield min( answer_2 = min(
sum(abs(p - position) * (abs(p - position) + 1) // 2 for p in positions) sum(abs(p - position) * (abs(p - position) + 1) // 2 for p in positions)
for position in range(min_position, max_position + 1) for position in range(min_position, max_position + 1)
) )
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,8 @@
import itertools import itertools
from typing import Any, Iterator import os
import sys
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
digits = { digits = {
"abcefg": 0, "abcefg": 0,
@@ -16,23 +17,19 @@ digits = {
"abcdfg": 9, "abcdfg": 9,
} }
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver): # part 1
def solve(self, input: str) -> Iterator[Any]: lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
lines = input.splitlines() answer_1 = sum(
len(p) in lengths for line in lines for p in line.split("|")[1].strip().split()
)
print(f"answer 1 is {answer_1}")
# part 1 # part 2
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)} values: list[int] = []
yield sum(
len(p) in lengths
for line in lines
for p in line.split("|")[1].strip().split()
)
# part 2 for line in lines:
values: list[int] = []
for line in lines:
parts = line.split("|") parts = line.split("|")
broken_digits = sorted(parts[0].strip().split(), key=len) broken_digits = sorted(parts[0].strip().split(), key=len)
@@ -52,9 +49,7 @@ class Solver(BaseSolver):
bd = [u for u in per_length[4][0] if u not in cf] bd = [u for u in per_length[4][0] if u not in cf]
# the 3 digits of length 5 have a, d and g in common # the 3 digits of length 5 have a, d and g in common
adg = [ adg = [u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])]
u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])
]
# we can remove a # we can remove a
dg = [u for u in adg if u != a] dg = [u for u in adg if u != a]
@@ -82,8 +77,11 @@ class Solver(BaseSolver):
digit = "".join(sorted(mapping[c] for c in number)) digit = "".join(sorted(mapping[c] for c in number))
value = 10 * value + digits[digit] value = 10 * value + digits[digit]
self.logger.info(f"value for '{line}' is {value}") if VERBOSE:
print(value)
values.append(value) values.append(value)
yield sum(values)
answer_2 = sum(values)
print(f"answer 2 is {answer_2}")

View File

@@ -1,18 +1,18 @@
import sys
from math import prod from math import prod
from typing import Any, Iterator
from ..base import BaseSolver values = [[int(c) for c in row] for row in sys.stdin.read().splitlines()]
n_rows, n_cols = len(values), len(values[0])
def neighbors(point: tuple[int, int], n_rows: int, n_cols: int): def neighbors(point: tuple[int, int]):
i, j = point i, j = point
for di, dj in ((-1, 0), (+1, 0), (0, -1), (0, +1)): for di, dj in ((-1, 0), (+1, 0), (0, -1), (0, +1)):
if 0 <= i + di < n_rows and 0 <= j + dj < n_cols: if 0 <= i + di < n_rows and 0 <= j + dj < n_cols:
yield (i + di, j + dj) yield (i + di, j + dj)
def basin(values: list[list[int]], start: tuple[int, int]) -> set[tuple[int, int]]: def basin(start: tuple[int, int]) -> set[tuple[int, int]]:
n_rows, n_cols = len(values), len(values[0])
visited: set[tuple[int, int]] = set() visited: set[tuple[int, int]] = set()
queue = [start] queue = [start]
@@ -23,25 +23,22 @@ def basin(values: list[list[int]], start: tuple[int, int]) -> set[tuple[int, int
continue continue
visited.add((i, j)) visited.add((i, j))
queue.extend(neighbors((i, j), n_rows, n_cols)) queue.extend(neighbors((i, j)))
return visited return visited
class Solver(BaseSolver): low_points = [
def solve(self, input: str) -> Iterator[Any]:
values = [[int(c) for c in row] for row in input.splitlines()]
n_rows, n_cols = len(values), len(values[0])
low_points = [
(i, j) (i, j)
for i in range(n_rows) for i in range(n_rows)
for j in range(n_cols) for j in range(n_cols)
if all( if all(values[ti][tj] > values[i][j] for ti, tj in neighbors((i, j)))
values[ti][tj] > values[i][j] ]
for ti, tj in neighbors((i, j), n_rows, n_cols)
)
]
yield sum(values[i][j] + 1 for i, j in low_points) # part 1
yield prod(sorted(len(basin(values, point)) for point in low_points)[-3:]) answer_1 = sum(values[i][j] + 1 for i, j in low_points)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = prod(sorted(len(basin(point)) for point in low_points)[-3:])
print(f"answer 2 is {answer_2}")

View File

@@ -1,12 +1,7 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver 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]}")
class Solver(BaseSolver): print(f"answer 2 is {sum(values[-3:])}")
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,16 +1,13 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
cycle = 1
x = 1
class Solver(BaseSolver): values = {cycle: x}
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
cycle, x = 1, 1 for line in lines:
values = {cycle: x}
for line in lines:
cycle += 1 cycle += 1
if line == "noop": if line == "noop":
@@ -25,19 +22,17 @@ class Solver(BaseSolver):
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))
yield answer_1 print(f"answer 1 is {answer_1}")
yield (
"\n" for i in range(6):
+ "\n".join( for j in range(40):
"".join( v = values[1 + i * 40 + j]
"#"
if j >= (v := values[1 + i * 40 + j]) - 1 and j <= v + 1 if j >= v - 1 and j <= v + 1:
else "." print("#", end="")
for j in range(40) else:
) print(".", end="")
for i in range(6)
) print()
+ "\n"
)

View File

@@ -1,8 +1,7 @@
import copy import copy
import sys
from functools import reduce from functools import reduce
from typing import Any, Callable, Final, Iterator, Mapping, Sequence from typing import Callable, Final, Mapping, Sequence
from ..base import BaseSolver
class Monkey: class Monkey:
@@ -120,28 +119,24 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
return sorted_levels[-2] * sorted_levels[-1] return sorted_levels[-2] * sorted_levels[-1]
class Solver(BaseSolver): monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
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
yield monkey_business( answer_1 = 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)
yield monkey_business( answer_2 = monkey_business(
run( run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
copy.deepcopy(monkeys), )
10_000, print(f"answer 2 is {answer_2}")
me_worry_fn=lambda w: w % total_test_value,
)
)

View File

@@ -1,7 +1,6 @@
import heapq import heapq
from typing import Any, Callable, Iterator, TypeVar import sys
from typing import Callable, Iterator, TypeVar
from ..base import BaseSolver
Node = TypeVar("Node") Node = TypeVar("Node")
@@ -69,6 +68,30 @@ 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]]:
@@ -95,44 +118,17 @@ def neighbors(
# === main code === # === main code ===
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver): grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
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)] start: tuple[int, int] | None = None
graph[end[0]][end[1]] = "E" end: tuple[int, int] | None = None
for i in range(0, len(path) - 1): # for part 2
cr, cc = path[i] start_s: list[tuple[int, int]] = []
nr, nc = path[i + 1]
if cr == nr and nc == cc - 1: for i_row, row in enumerate(grid):
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]
start: tuple[int, int] | None = None
end: tuple[int, int] | None = None
# for part 2
start_s: list[tuple[int, int]] = []
for i_row, row in enumerate(grid):
for i_col, col in enumerate(row): for i_col, col in enumerate(row):
if chr(col + ord("a")) == "S": if chr(col + ord("a")) == "S":
start = (i_row, i_col) start = (i_row, i_col)
@@ -142,27 +138,26 @@ class Solver(BaseSolver):
elif col == 0: elif col == 0:
start_s.append((i_row, i_col)) start_s.append((i_row, i_col))
assert start is not None assert start is not None
assert end is not None assert end is not None
# fix values # fix values
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(
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])) lengths_1, parents_1 = dijkstra(
yield lengths_1[end] - 1 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
lengths_2, _ = dijkstra( print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
start=end,
neighbors=lambda n: neighbors(grid, n, False), print(f"answer 1 is {lengths_1[end] - 1}")
cost=lambda lhs, rhs: 1,
) lengths_2, parents_2 = dijkstra(
yield min(lengths_2.get(start, float("inf")) for start in start_s) 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}")

View File

@@ -1,8 +1,11 @@
import json import json
import sys
from functools import cmp_to_key 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"]] Packet: TypeAlias = list[int | list["Packet"]]
@@ -25,18 +28,14 @@ def compare(lhs: Packet, rhs: Packet) -> int:
return len(rhs) - len(lhs) return len(rhs) - len(lhs)
class Solver(BaseSolver): answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
def solve(self, input: str) -> Iterator[Any]: print(f"answer_1 is {answer_1}")
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]]] 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] d_index = [packets.index(d) + 1 for d in dividers]
packets.extend(dividers) print(f"answer 2 is {d_index[0] * d_index[1]}")
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,7 +1,6 @@
import sys
from enum import Enum, auto from enum import Enum, auto
from typing import Any, Callable, Iterator, cast from typing import Callable, cast
from ..base import BaseSolver
class Cell(Enum): class Cell(Enum):
@@ -13,6 +12,26 @@ 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],
@@ -65,46 +84,21 @@ def flow(
# === inputs === # === inputs ===
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver): paths: list[list[tuple[int, int]]] = []
def print_blocks(self, blocks: dict[tuple[int, int], Cell]): for line in lines:
"""
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]]] = []
for line in lines:
parts = line.split(" -> ") parts = line.split(" -> ")
paths.append( paths.append(
[ [
cast( cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
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] = {}
for path in paths: blocks: dict[tuple[int, int], Cell] = {}
for path in paths:
for start, end in zip(path[:-1], path[1:]): for start, end in zip(path[:-1], path[1:]):
x_start = min(start[0], end[0]) x_start = min(start[0], end[0])
x_end = max(start[0], end[0]) + 1 x_end = max(start[0], end[0]) + 1
@@ -115,25 +109,32 @@ class Solver(BaseSolver):
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
self.print_blocks(blocks) print_blocks(blocks)
print()
y_max = max(y for _, y in blocks) 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 === # === 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
) )
self.print_blocks(blocks_1) print_blocks(blocks_1)
yield sum(v == Cell.SAND for v in blocks_1.values()) print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
print()
# === part 2 === # === part 2 ===
blocks_2 = flow( blocks_2 = flow(
blocks.copy(), blocks.copy(),
stop_fn=lambda x, y: x == 500 and y == 0, 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, 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
self.print_blocks(blocks_2) print_blocks(blocks_2)
yield sum(v == Cell.SAND for v in blocks_2.values()) print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")

View File

@@ -1,4 +1,4 @@
import itertools as it import sys
from typing import Any, Iterator from typing import Any, Iterator
import numpy as np import numpy as np
@@ -21,7 +21,9 @@ 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(it.chain(*no_beacons_row_l)).difference(beacons_at_row) # type: ignore no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference(
beacons_at_row
) # type: ignore
return len(no_beacons_row) return len(no_beacons_row)
@@ -60,9 +62,8 @@ 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, # type: ignore m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}"
ctname=f"ct_{sx}_{sy}", ) # type: ignore
)
m.set_objective("min", x + y) m.set_objective("min", x + y)
@@ -91,5 +92,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(f"answer 2 is {a2} (x={x}, y={y})") self.logger.info("answer 2 is {at} (x={x}, y={y})")
yield a2 yield a2

View File

@@ -3,13 +3,12 @@ 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 Any, FrozenSet, Iterator, NamedTuple from typing import FrozenSet, NamedTuple
from tqdm import tqdm from tqdm import tqdm
from ..base import BaseSolver
class Pipe(NamedTuple): class Pipe(NamedTuple):
name: str name: str
@@ -37,8 +36,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)] queue = [(0, pipe_1)]
visited: set[Pipe] = set() visited = set()
distances: dict[Pipe, int] = {} distances: dict[Pipe, int] = {}
while len(distances) < len(pipes): while len(distances) < len(pipes):
@@ -123,12 +122,11 @@ def part_2(
# === MAIN === # === MAIN ===
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
pipes: dict[str, Pipe] = {}
for line in lines: pipes: dict[str, Pipe] = {}
for line in lines:
r = re.match( r = re.match(
R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)", R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
line, line,
@@ -139,9 +137,9 @@ class Solver(BaseSolver):
pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", ")) pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", "))
# compute distances from one valve to any other # compute distances from one valve to any other
distances: dict[tuple[Pipe, Pipe], int] = {} distances: dict[tuple[Pipe, Pipe], int] = {}
for pipe_1 in pipes.values(): for pipe_1 in pipes.values():
distances.update( distances.update(
{ {
(pipe_1, pipe_2): distance (pipe_1, pipe_2): distance
@@ -149,11 +147,12 @@ class Solver(BaseSolver):
} }
) )
# 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
yield part_1(pipes["AA"], 30, distances, relevant_pipes)
# 1707, 2223 # 1651, 1653
yield part_2(pipes["AA"], 26, distances, relevant_pipes) print(part_1(pipes["AA"], 30, distances, relevant_pipes))
# 1707, 2223
print(part_2(pipes["AA"], 26, distances, relevant_pipes))

View File

@@ -1,16 +1,12 @@
from typing import Any, Iterator, Sequence, TypeAlias, TypeVar import sys
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])):
@@ -21,7 +17,7 @@ def print_tower(tower: Tower, out: str = "#"):
print("+" + "-" * tower.shape[1] + "+") print("+" + "-" * tower.shape[1] + "+")
def tower_height(tower: Tower) -> int: def tower_height(tower: np.ndarray) -> 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)
@@ -49,8 +45,8 @@ def build_tower(
n_rocks: int, n_rocks: int,
jets: str, jets: str,
early_stop: bool = False, early_stop: bool = False,
init: Tower = np.ones(WIDTH, dtype=bool), init: np.ndarray = np.ones(WIDTH, dtype=bool),
) -> tuple[Tower, int, int, dict[int, int]]: ) -> tuple[np.ndarray, int, int, dict[int, int]]:
tower = EMPTY_BLOCKS.copy() tower = EMPTY_BLOCKS.copy()
tower[0, :] = init tower[0, :] = init
@@ -99,24 +95,26 @@ 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
class Solver(BaseSolver): line = sys.stdin.read().strip()
def solve(self, input: str) -> Iterator[Any]:
tower, *_ = build_tower(2022, input)
yield tower_height(tower)
TOTAL_ROCKS = 1_000_000_000_000 tower, *_ = build_tower(2022, line)
_tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, input, True) answer_1 = tower_height(tower)
assert prev_1 > 0 print(f"answer 1 is {answer_1}")
# 2767 1513 TOTAL_ROCKS = 1_000_000_000_000
remaining_rocks = TOTAL_ROCKS - n_rocks_1 tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, line, True)
n_repeat_rocks = n_rocks_1 - prev_1 assert prev_1 > 0
n_repeat_towers = remaining_rocks // n_repeat_rocks
base_height = heights_1[prev_1] # 2767 1513
repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1] remaining_rocks = TOTAL_ROCKS - n_rocks_1
remaining_height = ( n_repeat_rocks = n_rocks_1 - prev_1
n_repeat_towers = remaining_rocks // n_repeat_rocks
base_height = heights_1[prev_1]
repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
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]
) )
yield base_height + (n_repeat_towers + 1) * repeat_height + remaining_height answer_2 = base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
print(f"answer 2 is {answer_2}")

View File

@@ -1,38 +1,33 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from ..base import BaseSolver xyz = np.asarray(
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
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 input.splitlines() for row in sys.stdin.read().splitlines()
] ]
) )
xyz = xyz - xyz.min(axis=0) + 1 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
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)] n_dims = len(cubes.shape)
yield sum( faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
1
for x, y, z in xyz
for dx, dy, dz in faces
if not cubes[x + dx, y + dy, z + dz]
)
visited = np.zeros_like(cubes, dtype=bool) answer_1 = sum(
queue = [(0, 0, 0)] 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}")
n_faces = 0 visited = np.zeros_like(cubes, dtype=bool)
while queue: queue = [(0, 0, 0)]
n_faces = 0
while queue:
x, y, z = queue.pop(0) x, y, z = queue.pop(0)
if visited[x, y, z]: if visited[x, y, z]:
@@ -42,9 +37,7 @@ class Solver(BaseSolver):
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( if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))):
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]:
@@ -54,5 +47,4 @@ class Solver(BaseSolver):
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,11 +1,10 @@
from typing import Any, Iterator, Literal import sys
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",
@@ -63,6 +62,29 @@ 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.,
@@ -151,31 +173,11 @@ 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])
class Solver(BaseSolver): answer_1 = sum(
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}")
yield (run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)) answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)
print(f"answer 2 is {answer_2}")

View File

@@ -1,6 +1,4 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver
def score_1(ux: int, vx: int) -> int: 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 return (ux + vx - 1) % 3 + 1 + vx * 3
class Solver(BaseSolver): lines = sys.stdin.readlines()
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
yield sum(score_1(*v) for v in values) print(f"answer 1 is {sum(score_1(*v) for v in values)}")
# part 2 - 14204 # part 2 - 14204
yield sum(score_2(*v) for v in values) print(f"answer 2 is {sum(score_2(*v) for v in values)}")

View File

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

View File

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

View File

@@ -1,57 +1,51 @@
import re import re
from typing import Any, Callable, Iterator import sys
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}
class Solver(BaseSolver): board_map_s, direction_s = sys.stdin.read().split("\n\n")
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()
max_line = max(len(line) for line in board_lines) max_line = max(len(line) for line in board_lines)
board = np.array( board = np.array(
[ [
[TILE_FROM_CHAR[c] for c in row] + [VOID] * (max_line - len(row)) [TILE_FROM_CHAR[c] for c in row] + [VOID] * (max_line - len(row))
for row in board_map_s.splitlines() for row in board_map_s.splitlines()
] ]
) )
directions = [ directions = [
int(p1) if p2 else p1 int(p1) if p2 else p1 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
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
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
)
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
)
faces = np.zeros_like(board) # find on each row and column the first and last non-void
size = np.gcd(board.shape[0], board.shape[1]) row_first_non_void = np.argmax(board != VOID, axis=1)
for row in range(0, board.shape[0], size): 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_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
faces = np.zeros_like(board)
size = np.gcd(board.shape[0], board.shape[1])
for row in range(0, board.shape[0], size):
for col in range(row_first_non_void[row], row_last_non_void[row], size): for col in range(row_first_non_void[row], row_last_non_void[row], size):
faces[row : row + size, col : col + size] = faces.max() + 1 faces[row : row + size, col : col + size] = faces.max() + 1
SIZE = np.gcd(*board.shape) SIZE = np.gcd(*board.shape)
# TODO: deduce this from the actual cube... # TODO: deduce this from the actual cube...
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]] faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
if board.shape == (12, 16): # example if board.shape == (12, 16): # example
faces_wrap = { faces_wrap = {
1: { 1: {
"W": lambda y, x: (4, 4 + y, "S"), # 3N "W": lambda y, x: (4, 4 + y, "S"), # 3N
@@ -79,7 +73,7 @@ class Solver(BaseSolver):
}, },
} }
else: else:
faces_wrap = { faces_wrap = {
1: { 1: {
"W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W "W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W
@@ -109,7 +103,8 @@ class Solver(BaseSolver):
}, },
} }
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
elif r0 == "S": elif r0 == "S":
@@ -121,14 +116,14 @@ class Solver(BaseSolver):
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(
wrap: Callable[[int, int, str], tuple[int, int, str]], def run(wrap: Callable[[int, int, str], tuple[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"
@@ -137,9 +132,7 @@ class Solver(BaseSolver):
if isinstance(direction, int): if isinstance(direction, int):
while direction > 0: while direction > 0:
if r0 == "E": if r0 == "E":
xi = np.where( xi = np.where(board[y0, x0 + 1 : x0 + direction + 1] == WALL)[0]
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
@@ -155,14 +148,10 @@ class Solver(BaseSolver):
x0 = row_last_non_void[y0] x0 = row_last_non_void[y0]
direction = 0 direction = 0
else: else:
direction = ( direction = direction - (row_last_non_void[y0] - x0) - 1
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( yi = np.where(board[y0 + 1 : y0 + direction + 1, x0] == WALL)[0]
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
@@ -178,9 +167,7 @@ class Solver(BaseSolver):
y0 = col_last_non_void[x0] y0 = col_last_non_void[x0]
direction = 0 direction = 0
else: else:
direction = ( direction = direction - (col_last_non_void[x0] - y0) - 1
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)
@@ -188,10 +175,7 @@ class Solver(BaseSolver):
if len(xi): if len(xi):
x0 = left + xi[-1] + 1 x0 = left + xi[-1] + 1
direction = 0 direction = 0
elif ( elif x0 - direction >= 0 and board[y0, x0 - direction] == EMPTY:
x0 - direction >= 0
and board[y0, x0 - direction] == EMPTY
):
x0 = x0 - direction x0 = x0 - direction
direction = 0 direction = 0
else: else:
@@ -200,9 +184,7 @@ class Solver(BaseSolver):
x0 = row_first_non_void[y0] x0 = row_first_non_void[y0]
direction = 0 direction = 0
else: else:
direction = ( direction = direction - (x0 - row_first_non_void[y0]) - 1
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)
@@ -210,10 +192,7 @@ class Solver(BaseSolver):
if len(yi): if len(yi):
y0 = top + yi[-1] + 1 y0 = top + yi[-1] + 1
direction = 0 direction = 0
elif ( elif y0 - direction >= 0 and board[y0 - direction, x0] == EMPTY:
y0 - direction >= 0
and board[y0 - direction, x0] == EMPTY
):
y0 = y0 - direction y0 = y0 - direction
direction = 0 direction = 0
else: else:
@@ -222,9 +201,7 @@ class Solver(BaseSolver):
y0 = col_first_non_void[x0] y0 = col_first_non_void[x0]
direction = 0 direction = 0
else: else:
direction = ( direction = direction - (y0 - col_first_non_void[x0]) - 1
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 = {
@@ -236,8 +213,11 @@ class Solver(BaseSolver):
return y0, x0, r0 return y0, x0, r0
y1, x1, r1 = run(wrap_part_1)
yield 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
y2, x2, r2 = run(wrap_part_2) y1, x1, r1 = run(wrap_part_1)
yield 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2] answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
print(f"answer 1 is {answer_1}")
y2, x2, r2 = run(wrap_part_2)
answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
print(f"answer 2 is {answer_2}")

View File

@@ -1,8 +1,6 @@
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[
@@ -20,7 +18,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)
@@ -71,33 +69,30 @@ def round(
directions.append(directions.pop(0)) directions.append(directions.pop(0))
class Solver(BaseSolver): POSITIONS = {
def solve(self, input: str) -> Iterator[Any]:
POSITIONS = {
(i, j) (i, j)
for i, row in enumerate(input.splitlines()) for i, row in enumerate(sys.stdin.read().splitlines())
for j, col in enumerate(row) for j, col in enumerate(row)
if col == "#" if col == "#"
} }
# === part 1 === # === part 1 ===
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy() p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
for _ in range(10): for r 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)
yield sum( answer_1 = sum(
(y, x) not in p1 (y, x) not in p1 for y in range(min_y, max_y + 1) for x in range(min_x, max_x + 1)
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 ===
p2, d2 = POSITIONS.copy(), DIRECTIONS.copy() p2, d2 = POSITIONS.copy(), DIRECTIONS.copy()
answer_2 = 0 answer_2 = 0
while True: while True:
answer_2 += 1 answer_2 += 1
backup = p2.copy() backup = p2.copy()
round(p2, d2) round(p2, d2)
@@ -105,4 +100,4 @@ class Solver(BaseSolver):
if backup == p2: if backup == p2:
break break
yield answer_2 print(f"answer 2 is {answer_2}")

View File

@@ -1,46 +1,36 @@
import heapq import heapq
import math import math
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
winds = {
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
winds = {
(i - 1, j - 1, lines[i][j]) (i - 1, j - 1, lines[i][j])
for i in range(1, len(lines) - 1) for i in range(1, len(lines) - 1)
for j in range(1, len(lines[i]) - 1) for j in range(1, len(lines[i]) - 1)
if lines[i][j] != "." if lines[i][j] != "."
} }
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 = [ east_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 = [
west_winds = [
{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)
]
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)
] ]
south_winds = [ 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 = [ queue = [(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))]
(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: {})
@@ -64,17 +54,13 @@ class Solver(BaseSolver):
n_cycle = (cycle + 1) % CYCLE n_cycle = (cycle + 1) % CYCLE
if (ty, tx) == end: if (ty, tx) == end:
heapq.heappush( heapq.heappush(queue, (distance + 1, distance + 1, ((ty, tx), n_cycle)))
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 ( if (ty, tx) != start and (ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols):
ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols
):
continue continue
if (ty, tx) != start: if (ty, tx) != start:
@@ -89,29 +75,24 @@ class Solver(BaseSolver):
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,
) )
end = ( end = (
n_rows, n_rows,
next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1, next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1,
) )
distances_1, forward_1 = run(start, 0, end) distances_1, forward_1 = run(start, 0, end)
yield forward_1 print(f"answer 1 is {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)
yield forward_1 + return_1 + forward_2 print(f"answer 2 is {forward_1 + return_1 + forward_2}")

View File

@@ -1,22 +1,19 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
class Solver(BaseSolver): def snafu2number(number: str) -> int:
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
def snafu2number(number: str) -> int:
value = 0 value = 0
for c in number: for c in number:
value *= 5 value *= 5
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 = ""
while number > 0: while number > 0:
@@ -25,4 +22,6 @@ class Solver(BaseSolver):
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,28 +1,23 @@
import string import string
from typing import Any, Iterator import sys
from ..base import BaseSolver 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]
class Solver(BaseSolver): # priorities
def solve(self, input: str) -> Iterator[Any]: priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
lines = [line.strip() for line in input.splitlines()]
# extract content of each part # part 1
parts = [ part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines print(f"answer 1 is {part1}")
]
# priorities # part 2
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)} n_per_group = 3
part2 = sum(
# part 1
yield sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
# part 2
n_per_group = 3
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 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = [line.strip() for line in sys.stdin.readlines()]
def make_range(value: str) -> set[int]: def make_range(value: str) -> set[int]:
@@ -8,13 +8,10 @@ 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))
class Solver(BaseSolver): sections = [tuple(make_range(part) for part in line.split(",")) for line in lines]
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
sections = [ answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
tuple(make_range(part) for part in line.split(",")) for line in lines print(f"answer 1 is {answer_1}")
]
yield sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections) answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections)
yield sum(bool(s1.intersection(s2)) for s1, s2 in sections) print(f"answer 1 is {answer_2}")

View File

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

View File

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

View File

@@ -1,35 +1,30 @@
import sys
from pathlib import Path from pathlib import Path
from typing import Any, Iterator
from ..base import BaseSolver 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
#
class Solver(BaseSolver): # mapping from path to list of files or directories
def solve(self, input: str) -> Iterator[Any]: trees: dict[Path, list[Path]] = {}
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 # mapping from paths to either size (for file) or -1 for directory
# implements everything we need sizes: dict[Path, int] = {}
#
# 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 # first line must be a cd otherwise we have no idea where we are
trees: dict[Path, list[Path]] = {} assert lines[0].startswith("$ cd")
base_path = Path(lines[0].strip("$").split()[1]).resolve()
cur_path = base_path
# mapping from paths to either size (for file) or -1 for directory trees[cur_path] = []
sizes: dict[Path, int] = {} sizes[cur_path] = -1
# first line must be a cd otherwise we have no idea where we are for line in lines[1:]:
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 # command
if line.startswith("$"): if line.startswith("$"):
parts = line.strip("$").strip().split() parts = line.strip("$").strip().split()
@@ -58,7 +53,8 @@ class Solver(BaseSolver):
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]
if size >= 0: if size >= 0:
@@ -66,16 +62,19 @@ class Solver(BaseSolver):
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}
# part 1 acc_sizes = {path: compute_size(path) for path in trees}
yield sum(size for size in acc_sizes.values() if size <= 100_000)
# part 2 # part 1
total_space = 70_000_000 answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
update_space = 30_000_000 print(f"answer 1 is {answer_1}")
free_space = total_space - acc_sizes[base_path]
to_free_space = update_space - free_space # part 2
total_space = 70_000_000
update_space = 30_000_000
free_space = total_space - acc_sizes[base_path]
yield min(size for size in acc_sizes.values() if size >= to_free_space) 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}")

View File

@@ -1,20 +1,15 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from numpy.typing import NDArray from numpy.typing import NDArray
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
trees = np.array([[int(x) for x in row] for row in lines])
class Solver(BaseSolver): # answer 1
def solve(self, input: str) -> Iterator[Any]: highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1
lines = [line.strip() for line in input.splitlines()] highest_trees[1:-1, 1:-1] = [
trees = np.array([[int(x) for x in row] for row in lines])
# answer 1
highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1
highest_trees[1:-1, 1:-1] = [
[ [
[ [
trees[:i, j].max(), trees[:i, j].max(),
@@ -25,11 +20,13 @@ class Solver(BaseSolver):
for j in range(1, trees.shape[1] - 1) for j in range(1, trees.shape[1] - 1)
] ]
for i in range(1, trees.shape[0] - 1) for i in range(1, trees.shape[0] - 1)
] ]
yield (highest_trees.min(axis=2) < trees).sum() answer_1 = (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]
if not w.size: if not w.size:
@@ -37,9 +34,10 @@ class Solver(BaseSolver):
return w[0] + 1 return w[0] + 1
# answer 2
v_distances = np.zeros(trees.shape + (4,), dtype=int) # answer 2
v_distances[1:-1, 1:-1, :] = [ 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 - 1 :: -1, j], trees[i, j]),
@@ -50,5 +48,6 @@ class Solver(BaseSolver):
for j in range(1, trees.shape[1] - 1) for j in range(1, trees.shape[1] - 1)
] ]
for i in range(1, trees.shape[0] - 1) for i in range(1, trees.shape[0] - 1)
] ]
yield np.prod(v_distances, axis=2).max() answer_2 = np.prod(v_distances, axis=2).max()
print(f"answer 2 is {answer_2}")

View File

@@ -1,10 +1,7 @@
import itertools as it import sys
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
@@ -46,14 +43,17 @@ def run(commands: list[str], n_blocks: int) -> list[tuple[int, int]]:
return visited return visited
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# flatten the commands # flatten the commands
commands = list( commands: list[str] = []
it.chain(*(p[0] * int(p[1]) for line in lines if (p := line.split()))) for line in lines:
) d, c = line.split()
commands.extend(d * int(c))
yield len(set(run(commands, n_blocks=2)))
yield len(set(run(commands, n_blocks=10))) visited_1 = run(commands, n_blocks=2)
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,5 +1,3 @@
# 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.rstrip()) it = solver.solve(data.strip())
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}")
@@ -193,7 +193,7 @@ def main():
"answer", "answer",
{ {
"answer": i_answer + 1, "answer": i_answer + 1,
"value": str(answer), "value": answer,
"answerTime_s": (current - last).total_seconds(), "answerTime_s": (current - last).total_seconds(),
"totalTime_s": (current - start).total_seconds(), "totalTime_s": (current - start).total_seconds(),
}, },

View File

@@ -7,10 +7,12 @@ _T = TypeVar("_T")
class ProgressHandler(Protocol): class ProgressHandler(Protocol):
@overload @overload
def wrap(self, values: Sequence[_T]) -> Iterator[_T]: ... def wrap(self, values: Sequence[_T]) -> Iterator[_T]:
...
@overload @overload
def wrap(self, values: Iterable[_T], total: int) -> Iterator[_T]: ... def wrap(self, values: Iterable[_T], total: int) -> Iterator[_T]:
...
class BaseSolver: class BaseSolver:
@@ -31,4 +33,5 @@ class BaseSolver:
self.outputs = outputs self.outputs = outputs
@abstractmethod @abstractmethod
def solve(self, input: str) -> Iterator[Any] | None: ... def solve(self, input: str) -> Iterator[Any] | None:
...