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,38 +1,41 @@
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]] = [
(cast(Command, (p := line.split())[0]), int(p[1])) for line in lines
]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
commands: list[tuple[Command, int]] = [ def depth_and_position(use_aim: bool):
(cast(Command, (p := line.split())[0]), int(p[1])) for line in lines aim, pos, depth = 0, 0, 0
] for command, value in commands:
d_depth = 0
match command:
case "forward":
pos += value
depth += value * aim
case "up":
d_depth = -value
case "down":
d_depth = value
def depth_and_position(use_aim: bool): if use_aim:
aim, pos, depth = 0, 0, 0 aim += d_depth
for command, value in commands: else:
d_depth = 0 depth += value
match command:
case "forward":
pos += value
depth += value * aim
case "up":
d_depth = -value
case "down":
d_depth = value
if use_aim: return depth, pos
aim += d_depth
else:
depth += value
return depth, pos
yield prod(depth_and_position(False)) # part 1
yield prod(depth_and_position(True)) 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 = [
tuple(
Counter(line[col] for line in lines).most_common(2)[m][0]
for m in range(2)
)
for col in range(len(lines[0]))
]
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)
yield gamma_rate * epsilon_rate
# part 2 # part 1
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2) most_and_least_common = [
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2) tuple(Counter(line[col] for line in lines).most_common(2)[m][0] for m in range(2))
yield oxygen_generator_rating * co2_scrubber_rating for col in range(len(lines[0]))
]
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)
print(f"answer 1 is {gamma_rate * epsilon_rate}")
# part 2
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
answer_2 = oxygen_generator_rating * co2_scrubber_rating
print(f"answer 2 is {answer_2}")

View File

@@ -1,52 +1,45 @@
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() [[int(c) for c in line.split()] for line in lines[start : start + 5]]
for start in range(2, len(lines), 6)
]
)
numbers = [int(c) for c in lines[0].split(",")] # (round, score) for each board (-1 when not found)
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
marked = np.zeros_like(boards, dtype=bool)
boards = np.asarray( for round, number in enumerate(numbers):
[ # mark boards
[[int(c) for c in line.split()] for line in lines[start : start + 5]] marked[boards == number] = True
for start in range(2, len(lines), 6)
]
)
# (round, score) for each board (-1 when not found) # check each board for winning
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))] for index in range(len(boards)):
marked = np.zeros_like(boards, dtype=bool) if winning_rounds[index][0] > 0:
continue
for round, number in enumerate(numbers): if np.any(np.all(marked[index], axis=0) | np.all(marked[index], axis=1)):
# mark boards winning_rounds[index] = (
marked[boards == number] = True round,
number * int(np.sum(boards[index][~marked[index]])),
)
# check each board for winning # all boards are winning - break
for index in range(len(boards)): if np.all(marked.all(axis=1) | marked.all(axis=2)):
if winning_rounds[index][0] > 0: break
continue
if np.any( # part 1
np.all(marked[index], axis=0) | np.all(marked[index], axis=1) (_, score) = min(winning_rounds, key=lambda w: w[0])
): print(f"answer 1 is {score}")
winning_rounds[index] = (
round,
number * int(np.sum(boards[index][~marked[index]])),
)
# all boards are winning - break # part 2
if np.all(marked.all(axis=1) | marked.all(axis=2)): (_, score) = max(winning_rounds, key=lambda w: w[0])
break print(f"answer 2 is {score}")
# part 1
(_, score) = min(winning_rounds, key=lambda w: w[0])
yield score
# part 2
(_, score) = max(winning_rounds, key=lambda w: w[0])
yield score

View File

@@ -1,48 +1,48 @@
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]]] = [
(
(
int(line.split(" -> ")[0].split(",")[0]),
int(line.split(" -> ")[0].split(",")[1]),
),
(
int(line.split(" -> ")[1].split(",")[0]),
int(line.split(" -> ")[1].split(",")[1]),
),
)
for line in lines
]
class Solver(BaseSolver): np_sections = np.array(sections).reshape(-1, 4)
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [ 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()),
int(line.split(" -> ")[0].split(",")[0]), min(np_sections[:, 1].min(), np_sections[:, 3].min()),
int(line.split(" -> ")[0].split(",")[1]), max(np_sections[:, 1].max(), np_sections[:, 3].max()),
), )
(
int(line.split(" -> ")[1].split(",")[0]),
int(line.split(" -> ")[1].split(",")[1]),
),
)
for line in lines
]
np_sections = np.array(sections).reshape(-1, 4) counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
counts_2 = counts_1.copy()
x_max, y_max = ( for (x1, y1), (x2, y2) in sections:
max(np_sections[:, 0].max(), np_sections[:, 2].max()), x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1)
max(np_sections[:, 1].max(), np_sections[:, 3].max()), y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1)
)
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int) if x1 == x2 or y1 == y2:
counts_2 = counts_1.copy() counts_1[list(y_rng), list(x_rng)] += 1
counts_2[list(y_rng), list(x_rng)] += 1
elif abs(x2 - x1) == abs(y2 - y1):
for i, j in zip(y_rng, x_rng):
counts_2[i, j] += 1
for (x1, y1), (x2, y2) in sections: answer_1 = (counts_1 >= 2).sum()
x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1) print(f"answer 1 is {answer_1}")
y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1)
if x1 == x2 or y1 == y2: answer_2 = (counts_2 >= 2).sum()
counts_1[list(y_rng), list(x_rng)] += 1 print(f"answer 2 is {answer_2}")
counts_2[list(y_rng), list(x_rng)] += 1
elif abs(x2 - x1) == abs(y2 - y1):
for i, j in zip(y_rng, x_rng):
counts_2[i, j] += 1
yield (counts_1 >= 2).sum()
yield (counts_2 >= 2).sum()

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

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(",")] sum(abs(p - position) for p in positions)
for position in range(min_position, max_position + 1)
)
print(f"answer 1 is {answer_1}")
min_position, max_position = min(positions), max(positions) # part 2
answer_2 = min(
# part 1 sum(abs(p - position) * (abs(p - position) + 1) // 2 for p in positions)
yield min( for position in range(min_position, max_position + 1)
sum(abs(p - position) for p in positions) )
for position in range(min_position, max_position + 1) print(f"answer 2 is {answer_2}")
)
# part 2
yield min(
sum(abs(p - position) * (abs(p - position) + 1) // 2 for p in positions)
for position in range(min_position, max_position + 1)
)

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,74 +17,71 @@ 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] = [] parts = line.split("|")
broken_digits = sorted(parts[0].strip().split(), key=len)
for line in lines: per_length = {
parts = line.split("|") k: list(v)
broken_digits = sorted(parts[0].strip().split(), key=len) for k, v in itertools.groupby(sorted(broken_digits, key=len), key=len)
}
per_length = { # a can be found immediately
k: list(v) a = next(u for u in per_length[3][0] if u not in per_length[2][0])
for k, v in itertools.groupby(sorted(broken_digits, key=len), key=len)
}
# a can be found immediately # c and f have only two possible values corresponding to the single entry of
a = next(u for u in per_length[3][0] if u not in per_length[2][0]) # length 2
cf = list(per_length[2][0])
# c and f have only two possible values corresponding to the single entry of # the only digit of length 4 contains bcdf, so we can deduce bd by removing cf
# length 2 bd = [u for u in per_length[4][0] if u not in cf]
cf = list(per_length[2][0])
# the only digit of length 4 contains bcdf, so we can deduce bd by removing cf # the 3 digits of length 5 have a, d and g in common
bd = [u for u in per_length[4][0] if u not in cf] adg = [u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])]
# the 3 digits of length 5 have a, d and g in common # we can remove a
adg = [ dg = [u for u in adg if u != a]
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 deduce d and g
dg = [u for u in adg if u != a] d = next(u for u in dg if u in bd)
g = next(u for u in dg if u != d)
# we can deduce d and g # then b
d = next(u for u in dg if u in bd) b = next(u for u in bd if u != d)
g = next(u for u in dg if u != d)
# then b # f is in the three 6-length digits, while c is only in 2
b = next(u for u in bd if u != d) f = next(u for u in cf if all(u in p for p in per_length[6]))
# f is in the three 6-length digits, while c is only in 2 # c is not f
f = next(u for u in cf if all(u in p for p in per_length[6])) c = next(u for u in cf if u != f)
# c is not f # e is the last one
c = next(u for u in cf if u != f) e = next(u for u in "abcdefg" if u not in {a, b, c, d, f, g})
# e is the last one mapping = dict(zip((a, b, c, d, e, f, g), "abcdefg"))
e = next(u for u in "abcdefg" if u not in {a, b, c, d, f, g})
mapping = dict(zip((a, b, c, d, e, f, g), "abcdefg")) value = 0
for number in parts[1].strip().split():
digit = "".join(sorted(mapping[c] for c in number))
value = 10 * value + digits[digit]
value = 0 if VERBOSE:
for number in parts[1].strip().split(): print(value)
digit = "".join(sorted(mapping[c] for c in number))
value = 10 * value + digits[digit]
self.logger.info(f"value for '{line}' is {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]: (i, j)
values = [[int(c) for c in row] for row in input.splitlines()] for i in range(n_rows)
n_rows, n_cols = len(values), len(values[0]) for j in range(n_cols)
if all(values[ti][tj] > values[i][j] for ti, tj in neighbors((i, j)))
]
low_points = [ # part 1
(i, j) answer_1 = sum(values[i][j] + 1 for i, j in low_points)
for i in range(n_rows) print(f"answer 1 is {answer_1}")
for j in range(n_cols)
if all(
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 2
yield prod(sorted(len(basin(values, point)) for point in low_points)[-3:]) 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,43 +1,38 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
cycle = 1
x = 1
values = {cycle: x}
for line in lines:
cycle += 1
if line == "noop":
pass
else:
r = int(line.split()[1])
values[cycle] = x
cycle += 1
x += r
values[cycle] = x
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver): for i in range(6):
def solve(self, input: str) -> Iterator[Any]: for j in range(40):
lines = [line.strip() for line in input.splitlines()] v = values[1 + i * 40 + j]
cycle, x = 1, 1 if j >= v - 1 and j <= v + 1:
values = {cycle: x} print("#", end="")
else:
print(".", end="")
for line in lines: print()
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,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,74 +118,46 @@ 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] = "<" for i_col, col in enumerate(row):
elif cr == nr and nc == cc + 1: if chr(col + ord("a")) == "S":
graph[cr][cc] = ">" start = (i_row, i_col)
elif cr == nr - 1 and nc == cc: start_s.append(start)
graph[cr][cc] = "v" elif chr(col + ord("a")) == "E":
elif cr == nr + 1 and nc == cc: end = (i_row, i_col)
graph[cr][cc] = "^" elif col == 0:
else: start_s.append((i_row, i_col))
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
for row in graph: assert start is not None
self.logger.info("".join(row)) assert end is not None
def solve(self, input: str) -> Iterator[Any]: # fix values
lines = input.splitlines() grid[start[0]][start[1]] = 0
grid[end[0]][end[1]] = ord("z") - ord("a")
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
start: tuple[int, int] | None = None lengths_1, parents_1 = dijkstra(
end: tuple[int, int] | None = None 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
# for part 2 print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
start_s: list[tuple[int, int]] = []
for i_row, row in enumerate(grid): print(f"answer 1 is {lengths_1[end] - 1}")
for i_col, col in enumerate(row):
if chr(col + ord("a")) == "S":
start = (i_row, i_col)
start_s.append(start)
elif chr(col + ord("a")) == "E":
end = (i_row, i_col)
elif col == 0:
start_s.append((i_row, i_col))
assert start is not None lengths_2, parents_2 = dijkstra(
assert end is not None start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1
)
# fix values answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s)
grid[start[0]][start[1]] = 0 print(f"answer 2 is {answer_2}")
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,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,75 +84,57 @@ 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:
""" parts = line.split(" -> ")
Print the given set of blocks on a grid. paths.append(
[
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),
)
for y in range(y_min, y_max + 1): blocks: dict[tuple[int, int], Cell] = {}
self.logger.info( for path in paths:
"".join( for start, end in zip(path[:-1], path[1:]):
str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1) x_start = min(start[0], end[0])
) x_end = max(start[0], end[0]) + 1
) y_start = min(start[1], end[1])
y_end = max(start[1], end[1]) + 1
def solve(self, input: str) -> Iterator[Any]: for x in range(x_start, x_end):
lines = [line.strip() for line in input.splitlines()] for y in range(y_start, y_end):
blocks[x, y] = Cell.ROCK
paths: list[list[tuple[int, int]]] = [] print_blocks(blocks)
for line in lines: print()
parts = line.split(" -> ")
paths.append(
[
cast(
tuple[int, int], tuple(int(c.strip()) for c in part.split(","))
)
for part in parts
]
)
blocks: dict[tuple[int, int], Cell] = {} x_min, y_min, x_max, y_max = (
for path in paths: min(x for x, _ in blocks),
for start, end in zip(path[:-1], path[1:]): 0,
x_start = min(start[0], end[0]) max(x for x, _ in blocks),
x_end = max(start[0], end[0]) + 1 max(y for _, y in blocks),
y_start = min(start[1], end[1]) )
y_end = max(start[1], end[1]) + 1
for x in range(x_start, x_end): # === part 1 ===
for y in range(y_start, y_end):
blocks[x, y] = Cell.ROCK
self.print_blocks(blocks) blocks_1 = flow(
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
)
print_blocks(blocks_1)
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
print()
y_max = max(y for _, y in blocks) # === part 2 ===
# === part 1 === blocks_2 = flow(
blocks.copy(),
blocks_1 = flow( stop_fn=lambda x, y: x == 500 and y == 0,
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
) )
self.print_blocks(blocks_1) blocks_2[500, 0] = Cell.SAND
yield sum(v == Cell.SAND for v in blocks_1.values()) print_blocks(blocks_2)
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,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,37 +122,37 @@ 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:
r = re.match(
R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
line,
)
assert r
g = r.groups() pipes: dict[str, Pipe] = {}
for line in lines:
r = re.match(
R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
line,
)
assert r
pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", ")) g = r.groups()
# compute distances from one valve to any other pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", "))
distances: dict[tuple[Pipe, Pipe], int] = {}
for pipe_1 in pipes.values():
distances.update(
{
(pipe_1, pipe_2): distance
for pipe_2, distance in breadth_first_search(pipes, pipe_1).items()
}
)
# valves with flow # compute distances from one valve to any other
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0) distances: dict[tuple[Pipe, Pipe], int] = {}
for pipe_1 in pipes.values():
distances.update(
{
(pipe_1, pipe_2): distance
for pipe_2, distance in breadth_first_search(pipes, pipe_1).items()
}
)
# 1651, 1653 # valves with flow
yield part_1(pipes["AA"], 30, distances, relevant_pipes) relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
# 1707, 2223
yield part_2(pipes["AA"], 26, distances, relevant_pipes) # 1651, 1653
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
heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1] n_repeat_towers = remaining_rocks // n_repeat_rocks
)
yield base_height + (n_repeat_towers + 1) * repeat_height + remaining_height 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]
)
answer_2 = base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
print(f"answer 2 is {answer_2}")

View File

@@ -1,58 +1,50 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from ..base import BaseSolver xyz = np.asarray(
[
tuple(int(x) for x in row.split(",")) # type: ignore
for row in sys.stdin.read().splitlines()
]
)
xyz = xyz - xyz.min(axis=0) + 1
class Solver(BaseSolver): cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
def solve(self, input: str) -> Iterator[Any]: cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
xyz = np.asarray(
[
tuple(int(x) for x in row.split(",")) # type: ignore
for row in input.splitlines()
]
)
xyz = xyz - xyz.min(axis=0) + 1 n_dims = len(cubes.shape)
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool) faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
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)] answer_1 = sum(
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}")
yield sum( visited = np.zeros_like(cubes, dtype=bool)
1 queue = [(0, 0, 0)]
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) n_faces = 0
queue = [(0, 0, 0)] while queue:
x, y, z = queue.pop(0)
n_faces = 0 if visited[x, y, z]:
while queue: continue
x, y, z = queue.pop(0)
if visited[x, y, z]: visited[x, y, z] = True
continue
visited[x, y, z] = True for dx, dy, dz in faces:
nx, ny, nz = x + dx, y + dy, z + dz
if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))):
continue
for dx, dy, dz in faces: if visited[nx, ny, nz]:
nx, ny, nz = x + dx, y + dy, z + dz continue
if not all(
n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))
):
continue
if visited[nx, ny, nz]: if cubes[nx, ny, nz]:
continue n_faces += 1
else:
if cubes[nx, ny, nz]: queue.append((nx, ny, nz))
n_faces += 1 print(f"answer 2 is {n_faces}")
else:
queue.append((nx, ny, nz))
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]: (i_blueprint + 1) * run(blueprint, 24)
blueprints: list[dict[Reagent, IntOfReagent]] = [] for i_blueprint, blueprint in enumerate(blueprints)
for line in input.splitlines(): )
r: list[int] = parse.parse( # type: ignore print(f"answer 1 is {answer_1}")
"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( answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)
{ print(f"answer 2 is {answer_2}")
"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)
for i_blueprint, blueprint in enumerate(blueprints)
)
yield (run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32))

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,31 +77,31 @@ 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()
try: try:
value = int(parts[1].strip()) value = int(parts[1].strip())
monkeys[name] = value monkeys[name] = value
except ValueError: except ValueError:
op1, ope, op2 = parts[1].strip().split() op1, ope, op2 = parts[1].strip().split()
monkeys[name] = (op1, ope, op2) monkeys[name] = (op1, ope, op2)
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,243 +1,223 @@
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
for col in range(row_first_non_void[row], row_last_non_void[row], size): col_first_non_void = np.argmax(board != VOID, axis=0)
faces[row : row + size, col : col + size] = faces.max() + 1 col_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
SIZE = np.gcd(*board.shape)
# TODO: deduce this from the actual cube... faces = np.zeros_like(board)
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]] 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):
faces[row : row + size, col : col + size] = faces.max() + 1
if board.shape == (12, 16): # example SIZE = np.gcd(*board.shape)
faces_wrap = {
1: {
"W": lambda y, x: (4, 4 + y, "S"), # 3N
"N": lambda y, x: (4, 11 - x, "S"), # 2N
"E": lambda y, x: (11 - y, 15, "W"), # 6E
},
2: {
"W": lambda y, x: (11, 19 - y, "N"), # 6S
"N": lambda y, x: (0, 11 - y, "S"), # 1N
"S": lambda y, x: (11, 11 - x, "N"), # 5S
},
3: {
"N": lambda y, x: (x - 4, 8, "E"), # 1W
"S": lambda y, x: (15 - x, 8, "E"), # 5W
},
4: {"E": lambda y, x: (8, 19 - y, "S")}, # 6N
5: {
"W": lambda y, x: (7, 15 - y, "N"), # 3S
"S": lambda y, x: (7, 11 - x, "N"), # 2S
},
6: {
"N": lambda y, x: (19 - x, 11, "W"), # 4E
"E": lambda y, x: (11 - y, 11, "W"), # 1E
"S": lambda y, x: (19 - x, 0, "E"), # 2W
},
}
# TODO: deduce this from the actual cube...
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
if board.shape == (12, 16): # example
faces_wrap = {
1: {
"W": lambda y, x: (4, 4 + y, "S"), # 3N
"N": lambda y, x: (4, 11 - x, "S"), # 2N
"E": lambda y, x: (11 - y, 15, "W"), # 6E
},
2: {
"W": lambda y, x: (11, 19 - y, "N"), # 6S
"N": lambda y, x: (0, 11 - y, "S"), # 1N
"S": lambda y, x: (11, 11 - x, "N"), # 5S
},
3: {
"N": lambda y, x: (x - 4, 8, "E"), # 1W
"S": lambda y, x: (15 - x, 8, "E"), # 5W
},
4: {"E": lambda y, x: (8, 19 - y, "S")}, # 6N
5: {
"W": lambda y, x: (7, 15 - y, "N"), # 3S
"S": lambda y, x: (7, 11 - x, "N"), # 2S
},
6: {
"N": lambda y, x: (19 - x, 11, "W"), # 4E
"E": lambda y, x: (11 - y, 11, "W"), # 1E
"S": lambda y, x: (19 - x, 0, "E"), # 2W
},
}
else:
faces_wrap = {
1: {
"W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W
"N": lambda y, x: (2 * SIZE + x, 0, "E"), # 6W
},
2: {
"N": lambda y, x: (4 * SIZE - 1, x - 2 * SIZE, "N"), # 6S
"E": lambda y, x: (3 * SIZE - y - 1, 2 * SIZE - 1, "W"), # 5E
"S": lambda y, x: (x - SIZE, 2 * SIZE - 1, "W"), # 3E
},
3: {
"W": lambda y, x: (2 * SIZE, y - SIZE, "S"), # 4N
"E": lambda y, x: (SIZE - 1, SIZE + y, "N"), # 2S
},
4: {
"W": lambda y, x: (3 * SIZE - y - 1, SIZE, "E"), # 1W
"N": lambda y, x: (SIZE + x, SIZE, "E"), # 3W
},
5: {
"E": lambda y, x: (3 * SIZE - y - 1, 3 * SIZE - 1, "W"), # 2E
"S": lambda y, x: (2 * SIZE + x, SIZE - 1, "W"), # 6E
},
6: {
"W": lambda y, x: (0, y - 2 * SIZE, "S"), # 1N
"E": lambda y, x: (3 * SIZE - 1, y - 2 * SIZE, "N"), # 5S
"S": lambda y, x: (0, x + 2 * SIZE, "S"), # 2N
},
}
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
if r0 == "E":
return y0, row_first_non_void[y0], r0
elif r0 == "S":
return col_first_non_void[x0], x0, r0
elif r0 == "W":
return y0, row_last_non_void[y0], r0
elif r0 == "N":
return col_last_non_void[x0], x0, r0
assert False
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
cube = faces[y0, x0]
assert r0 in faces_wrap[cube]
return faces_wrap[cube][r0](y0, x0)
def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int, str]:
y0 = 0
x0 = np.where(board[0] == EMPTY)[0][0]
r0 = "E"
for direction in directions:
if isinstance(direction, int):
while direction > 0:
if r0 == "E":
xi = np.where(board[y0, x0 + 1 : x0 + direction + 1] == WALL)[0]
if len(xi):
x0 = x0 + xi[0]
direction = 0
elif (
x0 + direction < board.shape[1]
and board[y0, x0 + direction] == EMPTY
):
x0 = x0 + direction
direction = 0
else:
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
if board[y0_t, x0_t] == WALL:
x0 = row_last_non_void[y0]
direction = 0
else:
direction = direction - (row_last_non_void[y0] - x0) - 1
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "S":
yi = np.where(board[y0 + 1 : y0 + direction + 1, x0] == WALL)[0]
if len(yi):
y0 = y0 + yi[0]
direction = 0
elif (
y0 + direction < board.shape[0]
and board[y0 + direction, x0] == EMPTY
):
y0 = y0 + direction
direction = 0
else:
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
if board[y0_t, x0_t] == WALL:
y0 = col_last_non_void[x0]
direction = 0
else:
direction = direction - (col_last_non_void[x0] - y0) - 1
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "W":
left = max(x0 - direction - 1, 0)
xi = np.where(board[y0, left:x0] == WALL)[0]
if len(xi):
x0 = left + xi[-1] + 1
direction = 0
elif x0 - direction >= 0 and board[y0, x0 - direction] == EMPTY:
x0 = x0 - direction
direction = 0
else:
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
if board[y0_t, x0_t] == WALL:
x0 = row_first_non_void[y0]
direction = 0
else:
direction = direction - (x0 - row_first_non_void[y0]) - 1
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "N":
top = max(y0 - direction - 1, 0)
yi = np.where(board[top:y0, x0] == WALL)[0]
if len(yi):
y0 = top + yi[-1] + 1
direction = 0
elif y0 - direction >= 0 and board[y0 - direction, x0] == EMPTY:
y0 = y0 - direction
direction = 0
else:
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
if board[y0_t, x0_t] == WALL:
y0 = col_first_non_void[x0]
direction = 0
else:
direction = direction - (y0 - col_first_non_void[x0]) - 1
y0, x0, r0 = y0_t, x0_t, r0_t
else: else:
faces_wrap = { r0 = {
1: { "E": {"L": "N", "R": "S"},
"W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W "N": {"L": "W", "R": "E"},
"N": lambda y, x: (2 * SIZE + x, 0, "E"), # 6W "W": {"L": "S", "R": "N"},
}, "S": {"L": "E", "R": "W"},
2: { }[r0][direction]
"N": lambda y, x: (4 * SIZE - 1, x - 2 * SIZE, "N"), # 6S
"E": lambda y, x: (3 * SIZE - y - 1, 2 * SIZE - 1, "W"), # 5E
"S": lambda y, x: (x - SIZE, 2 * SIZE - 1, "W"), # 3E
},
3: {
"W": lambda y, x: (2 * SIZE, y - SIZE, "S"), # 4N
"E": lambda y, x: (SIZE - 1, SIZE + y, "N"), # 2S
},
4: {
"W": lambda y, x: (3 * SIZE - y - 1, SIZE, "E"), # 1W
"N": lambda y, x: (SIZE + x, SIZE, "E"), # 3W
},
5: {
"E": lambda y, x: (3 * SIZE - y - 1, 3 * SIZE - 1, "W"), # 2E
"S": lambda y, x: (2 * SIZE + x, SIZE - 1, "W"), # 6E
},
6: {
"W": lambda y, x: (0, y - 2 * SIZE, "S"), # 1N
"E": lambda y, x: (3 * SIZE - 1, y - 2 * SIZE, "N"), # 5S
"S": lambda y, x: (0, x + 2 * SIZE, "S"), # 2N
},
}
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]: return y0, x0, r0
if r0 == "E":
return y0, row_first_non_void[y0], r0
elif r0 == "S":
return col_first_non_void[x0], x0, r0
elif r0 == "W":
return y0, row_last_non_void[y0], r0
elif r0 == "N":
return col_last_non_void[x0], x0, r0
assert False
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]: y1, x1, r1 = run(wrap_part_1)
cube = faces[y0, x0] answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
assert r0 in faces_wrap[cube] print(f"answer 1 is {answer_1}")
return faces_wrap[cube][r0](y0, x0)
def run( y2, x2, r2 = run(wrap_part_2)
wrap: Callable[[int, int, str], tuple[int, int, str]], answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
) -> tuple[int, int, str]: print(f"answer 2 is {answer_2}")
y0 = 0
x0 = np.where(board[0] == EMPTY)[0][0]
r0 = "E"
for direction in directions:
if isinstance(direction, int):
while direction > 0:
if r0 == "E":
xi = np.where(
board[y0, x0 + 1 : x0 + direction + 1] == WALL
)[0]
if len(xi):
x0 = x0 + xi[0]
direction = 0
elif (
x0 + direction < board.shape[1]
and board[y0, x0 + direction] == EMPTY
):
x0 = x0 + direction
direction = 0
else:
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
if board[y0_t, x0_t] == WALL:
x0 = row_last_non_void[y0]
direction = 0
else:
direction = (
direction - (row_last_non_void[y0] - x0) - 1
)
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "S":
yi = np.where(
board[y0 + 1 : y0 + direction + 1, x0] == WALL
)[0]
if len(yi):
y0 = y0 + yi[0]
direction = 0
elif (
y0 + direction < board.shape[0]
and board[y0 + direction, x0] == EMPTY
):
y0 = y0 + direction
direction = 0
else:
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
if board[y0_t, x0_t] == WALL:
y0 = col_last_non_void[x0]
direction = 0
else:
direction = (
direction - (col_last_non_void[x0] - y0) - 1
)
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "W":
left = max(x0 - direction - 1, 0)
xi = np.where(board[y0, left:x0] == WALL)[0]
if len(xi):
x0 = left + xi[-1] + 1
direction = 0
elif (
x0 - direction >= 0
and board[y0, x0 - direction] == EMPTY
):
x0 = x0 - direction
direction = 0
else:
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
if board[y0_t, x0_t] == WALL:
x0 = row_first_non_void[y0]
direction = 0
else:
direction = (
direction - (x0 - row_first_non_void[y0]) - 1
)
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "N":
top = max(y0 - direction - 1, 0)
yi = np.where(board[top:y0, x0] == WALL)[0]
if len(yi):
y0 = top + yi[-1] + 1
direction = 0
elif (
y0 - direction >= 0
and board[y0 - direction, x0] == EMPTY
):
y0 = y0 - direction
direction = 0
else:
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
if board[y0_t, x0_t] == WALL:
y0 = col_first_non_void[x0]
direction = 0
else:
direction = (
direction - (y0 - col_first_non_void[x0]) - 1
)
y0, x0, r0 = y0_t, x0_t, r0_t
else:
r0 = {
"E": {"L": "N", "R": "S"},
"N": {"L": "W", "R": "E"},
"W": {"L": "S", "R": "N"},
"S": {"L": "E", "R": "W"},
}[r0][direction]
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)
yield 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]

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,38 +69,35 @@ def round(
directions.append(directions.pop(0)) directions.append(directions.pop(0))
class Solver(BaseSolver): POSITIONS = {
def solve(self, input: str) -> Iterator[Any]: (i, j)
POSITIONS = { for i, row in enumerate(sys.stdin.read().splitlines())
(i, j) for j, col in enumerate(row)
for i, row in enumerate(input.splitlines()) if col == "#"
for j, col in enumerate(row) }
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)
if backup == p2: if backup == p2:
break break
yield answer_2 print(f"answer 2 is {answer_2}")

View File

@@ -1,117 +1,98 @@
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 = {
(i - 1, j - 1, lines[i][j])
for i in range(1, len(lines) - 1)
for j in range(1, len(lines[i]) - 1)
if lines[i][j] != "."
}
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
CYCLE = math.lcm(n_rows, n_cols)
east_winds = [{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)]
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)
]
south_winds = [
{i for i in range(n_rows) if (i, j, "v") in winds} for j in range(n_cols)
]
class Solver(BaseSolver): def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
def solve(self, input: str) -> Iterator[Any]: def heuristic(y: int, x: int) -> int:
lines = [line.strip() for line in input.splitlines()] return abs(end[0] - y) + abs(end[1] - x)
winds = { # (distance + heuristic, distance, (start_pos, cycle))
(i - 1, j - 1, lines[i][j]) queue = [(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))]
for i in range(1, len(lines) - 1) visited: set[tuple[tuple[int, int], int]] = set()
for j in range(1, len(lines[i]) - 1) distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {})
if lines[i][j] != "."
}
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2 while queue:
CYCLE = math.lcm(n_rows, n_cols) _, distance, ((y, x), cycle) = heapq.heappop(queue)
east_winds = [ if ((y, x), cycle) in visited:
{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows) continue
]
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)
]
south_winds = [
{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]): distances[y, x][cycle] = distance
def heuristic(y: int, x: int) -> int:
return abs(end[0] - y) + abs(end[1] - x)
# (distance + heuristic, distance, (start_pos, cycle)) visited.add(((y, x), cycle))
queue = [
(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))
]
visited: set[tuple[tuple[int, int], int]] = set()
distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {})
while queue: if (y, x) == (end[0], end[1]):
_, distance, ((y, x), cycle) = heapq.heappop(queue) break
if ((y, x), cycle) in visited: for dy, dx in (0, 0), (-1, 0), (1, 0), (0, -1), (0, 1):
ty = y + dy
tx = x + dx
n_cycle = (cycle + 1) % CYCLE
if (ty, tx) == end:
heapq.heappush(queue, (distance + 1, distance + 1, ((ty, tx), n_cycle)))
break
if ((ty, tx), n_cycle) in visited:
continue
if (ty, tx) != start and (ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols):
continue
if (ty, tx) != start:
if (ty - n_cycle) % n_rows in south_winds[tx]:
continue
if (ty + n_cycle) % n_rows in north_winds[tx]:
continue
if (tx + n_cycle) % n_cols in west_winds[ty]:
continue
if (tx - n_cycle) % n_cols in east_winds[ty]:
continue continue
distances[y, x][cycle] = distance heapq.heappush(
queue,
((heuristic(ty, tx) + distance + 1, distance + 1, ((ty, tx), n_cycle))),
)
visited.add(((y, x), cycle)) return distances, next(iter(distances[end].values()))
if (y, x) == (end[0], end[1]):
break
for dy, dx in (0, 0), (-1, 0), (1, 0), (0, -1), (0, 1): start = (
ty = y + dy -1,
tx = x + dx next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
)
end = (
n_rows,
next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1,
)
n_cycle = (cycle + 1) % CYCLE distances_1, forward_1 = run(start, 0, end)
print(f"answer 1 is {forward_1}")
if (ty, tx) == end: distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start)
heapq.heappush( distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
queue, (distance + 1, distance + 1, ((ty, tx), n_cycle)) print(f"answer 2 is {forward_1 + return_1 + forward_2}")
)
break
if ((ty, tx), n_cycle) in visited:
continue
if (ty, tx) != start and (
ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols
):
continue
if (ty, tx) != start:
if (ty - n_cycle) % n_rows in south_winds[tx]:
continue
if (ty + n_cycle) % n_rows in north_winds[tx]:
continue
if (tx + n_cycle) % n_cols in west_winds[ty]:
continue
if (tx - n_cycle) % n_cols in east_winds[ty]:
continue
heapq.heappush(
queue,
(
(
heuristic(ty, tx) + distance + 1,
distance + 1,
((ty, tx), n_cycle),
)
),
)
return distances, next(iter(distances[end].values()))
start = (
-1,
next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
)
end = (
n_rows,
next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1,
)
distances_1, forward_1 = run(start, 0, end)
yield forward_1
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)
yield forward_1 + return_1 + forward_2

View File

@@ -1,28 +1,27 @@
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]: value = 0
lines = [line.strip() for line in input.splitlines()] for c in number:
value *= 5
value += coeffs[c]
return value
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
def snafu2number(number: str) -> int: def number2snafu(number: int) -> str:
value = 0 values = ["0", "1", "2", "=", "-"]
for c in number: res = ""
value *= 5 while number > 0:
value += coeffs[c] mod = number % 5
return value res = res + values[mod]
number = number // 5 + int(mod >= 3)
return "".join(reversed(res))
def number2snafu(number: int) -> str:
values = ["0", "1", "2", "=", "-"]
res = ""
while number > 0:
mod = number % 5
res = res + values[mod]
number = number // 5 + int(mod >= 3)
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 priorities[c]
yield sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2)) for i in range(0, len(lines), n_per_group)
for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group])
# part 2 )
n_per_group = 3 print(f"answer 2 is {part2}")
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 @@
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]:
for stack, index in zip(blocks, range(0, len(block), 4)):
crate = block[index + 1 : index + 2].strip()
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()} if crate:
blocks[stack].append(crate)
# this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the # part 1 - deep copy for part 2
# form of '[X] ' (including the trailing space) blocks_1 = copy.deepcopy(blocks)
#
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: for move in moves_s:
blocks[stack].append(crate) _, count_s, _, from_, _, to_ = move.strip().split()
# part 1 - deep copy for part 2 for _i in range(int(count_s)):
blocks_1 = copy.deepcopy(blocks) blocks_1[to_].append(blocks_1[from_].pop())
for move in moves_s: # part 2
_, count_s, _, from_, _, to_ = move.strip().split() blocks_2 = copy.deepcopy(blocks)
for _i in range(int(count_s)): for move in moves_s:
blocks_1[to_].append(blocks_1[from_].pop()) _, count_s, _, from_, _, to_ = move.strip().split()
count = int(count_s)
# part 2 blocks_2[to_].extend(blocks_2[from_][-count:])
blocks_2 = copy.deepcopy(blocks) del blocks_2[from_][-count:]
for move in moves_s: answer_1 = "".join(s[-1] for s in blocks_1.values())
_, count_s, _, from_, _, to_ = move.strip().split() print(f"answer 1 is {answer_1}")
count = int(count_s)
blocks_2[to_].extend(blocks_2[from_][-count:]) answer_2 = "".join(s[-1] for s in blocks_2.values())
del blocks_2[from_][-count:] print(f"answer 2 is {answer_2}")
yield "".join(s[-1] for s in blocks_1.values())
yield "".join(s[-1] for s in blocks_2.values())

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,81 +1,80 @@
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
#
# 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
class Solver(BaseSolver): def compute_size(path: Path) -> int:
def solve(self, input: str) -> Iterator[Any]: size = sizes[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 if size >= 0:
# implements everything we need return size
#
# 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 return sum(compute_size(sub) for sub in trees[path])
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 acc_sizes = {path: compute_size(path) for path in trees}
assert lines[0].startswith("$ cd")
base_path = Path(lines[0].strip("$").split()[1]).resolve()
cur_path = base_path
trees[cur_path] = [] # part 1
sizes[cur_path] = -1 answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
print(f"answer 1 is {answer_1}")
for line in lines[1:]: # part 2
# command total_space = 70_000_000
if line.startswith("$"): update_space = 30_000_000
parts = line.strip("$").strip().split() free_space = total_space - acc_sizes[base_path]
command = parts[0]
if command == "cd": to_free_space = update_space - free_space
cur_path = cur_path.joinpath(parts[1]).resolve()
# just initialize the lis of files if not already done answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space)
if cur_path not in trees: print(f"answer 2 is {answer_2}")
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,54 +1,53 @@
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]) [
trees[:i, j].max(),
# answer 1 trees[i + 1 :, j].max(),
highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1 trees[i, :j].max(),
highest_trees[1:-1, 1:-1] = [ trees[i, j + 1 :].max(),
[
[
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)
]
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:
w = np.where(row_of_trees >= value)[0]
if not w.size: def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
return len(row_of_trees) w = np.where(row_of_trees >= value)[0]
return w[0] + 1 if not w.size:
return len(row_of_trees)
# answer 2 return w[0] + 1
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)
viewing_distance(trees[i - 1 :: -1, j], trees[i, j]), v_distances[1:-1, 1:-1, :] = [
viewing_distance(trees[i, j - 1 :: -1], trees[i, j]), [
viewing_distance(trees[i, j + 1 :], trees[i, j]), [
viewing_distance(trees[i + 1 :, j], trees[i, j]), viewing_distance(trees[i - 1 :: -1, j], trees[i, j]),
] viewing_distance(trees[i, j - 1 :: -1], trees[i, j]),
for j in range(1, trees.shape[1] - 1) viewing_distance(trees[i, j + 1 :], trees[i, j]),
] viewing_distance(trees[i + 1 :, j], trees[i, j]),
for i in range(1, trees.shape[0] - 1)
] ]
yield np.prod(v_distances, axis=2).max() for j in range(1, trees.shape[1] - 1)
]
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,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:
...