Compare commits

..

2 Commits

Author SHA1 Message Date
Mikaël Capelle
1b4dd32898 Refactor 2021 for new UI.
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-12-08 14:03:34 +01:00
Mikaël Capelle
cd96140378 Start fixing 2022 for new API. 2024-12-08 13:55:25 +01:00
37 changed files with 821 additions and 836 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import sys
from collections import Counter from collections import Counter
from typing import Literal from typing import Any, Iterator, Literal
from ..base import BaseSolver
def generator_rating( def generator_rating(
@ -20,20 +21,23 @@ def generator_rating(
return values[0] return values[0]
lines = sys.stdin.read().splitlines() class Solver(BaseSolver):
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 1 # part 2
most_and_least_common = [ oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
tuple(Counter(line[col] for line in lines).most_common(2)[m][0] for m in range(2)) co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
for col in range(len(lines[0])) yield oxygen_generator_rating * co2_scrubber_rating
]
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,45 +1,52 @@
import sys from typing import Any, Iterator
import numpy as np import numpy as np
lines = sys.stdin.read().splitlines() from ..base import BaseSolver
numbers = [int(c) for c in lines[0].split(",")]
boards = np.asarray( class Solver(BaseSolver):
[ def solve(self, input: str) -> Iterator[Any]:
[[int(c) for c in line.split()] for line in lines[start : start + 5]] lines = input.splitlines()
for start in range(2, len(lines), 6)
]
)
# (round, score) for each board (-1 when not found) numbers = [int(c) for c in lines[0].split(",")]
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
marked = np.zeros_like(boards, dtype=bool)
for round, number in enumerate(numbers): boards = np.asarray(
# mark boards [
marked[boards == number] = True [[int(c) for c in line.split()] for line in lines[start : start + 5]]
for start in range(2, len(lines), 6)
]
)
# check each board for winning # (round, score) for each board (-1 when not found)
for index in range(len(boards)): winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
if winning_rounds[index][0] > 0: marked = np.zeros_like(boards, dtype=bool)
continue
if np.any(np.all(marked[index], axis=0) | np.all(marked[index], axis=1)): for round, number in enumerate(numbers):
winning_rounds[index] = ( # mark boards
round, marked[boards == number] = True
number * int(np.sum(boards[index][~marked[index]])),
)
# all boards are winning - break # check each board for winning
if np.all(marked.all(axis=1) | marked.all(axis=2)): for index in range(len(boards)):
break if winning_rounds[index][0] > 0:
continue
# part 1 if np.any(
(_, score) = min(winning_rounds, key=lambda w: w[0]) np.all(marked[index], axis=0) | np.all(marked[index], axis=1)
print(f"answer 1 is {score}") ):
winning_rounds[index] = (
round,
number * int(np.sum(boards[index][~marked[index]])),
)
# part 2 # all boards are winning - break
(_, score) = max(winning_rounds, key=lambda w: w[0]) if np.all(marked.all(axis=1) | marked.all(axis=2)):
print(f"answer 2 is {score}") break
# 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 @@
import sys from typing import Any, Iterator
import numpy as np import numpy as np
lines: list[str] = sys.stdin.read().splitlines() from ..base import BaseSolver
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
]
np_sections = np.array(sections).reshape(-1, 4) class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
x_min, x_max, y_min, y_max = ( sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
min(np_sections[:, 0].min(), np_sections[:, 2].min()), (
max(np_sections[:, 0].max(), np_sections[:, 2].max()), (
min(np_sections[:, 1].min(), np_sections[:, 3].min()), int(line.split(" -> ")[0].split(",")[0]),
max(np_sections[:, 1].max(), np_sections[:, 3].max()), int(line.split(" -> ")[0].split(",")[1]),
) ),
(
int(line.split(" -> ")[1].split(",")[0]),
int(line.split(" -> ")[1].split(",")[1]),
),
)
for line in lines
]
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int) np_sections = np.array(sections).reshape(-1, 4)
counts_2 = counts_1.copy()
for (x1, y1), (x2, y2) in sections: x_max, y_max = (
x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1) max(np_sections[:, 0].max(), np_sections[:, 2].max()),
y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1) max(np_sections[:, 1].max(), np_sections[:, 3].max()),
)
if x1 == x2 or y1 == y2: counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
counts_1[list(y_rng), list(x_rng)] += 1 counts_2 = counts_1.copy()
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
answer_1 = (counts_1 >= 2).sum() for (x1, y1), (x2, y2) in sections:
print(f"answer 1 is {answer_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)
answer_2 = (counts_2 >= 2).sum() if x1 == x2 or y1 == y2:
print(f"answer 2 is {answer_2}") 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
yield (counts_1 >= 2).sum()
yield (counts_2 >= 2).sum()

View File

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

View File

@ -1,19 +1,22 @@
import sys from typing import Any, Iterator
positions = [int(c) for c in sys.stdin.read().strip().split(",")] from ..base import BaseSolver
min_position, max_position = min(positions), max(positions)
# part 1 class Solver(BaseSolver):
answer_1 = min( def solve(self, input: str) -> Iterator[Any]:
sum(abs(p - position) for p in positions) positions = [int(c) for c in input.split(",")]
for position in range(min_position, max_position + 1)
)
print(f"answer 1 is {answer_1}")
# part 2 min_position, max_position = min(positions), max(positions)
answer_2 = min(
sum(abs(p - position) * (abs(p - position) + 1) // 2 for p in positions) # part 1
for position in range(min_position, max_position + 1) yield min(
) sum(abs(p - position) for p in positions)
print(f"answer 2 is {answer_2}") for position in range(min_position, max_position + 1)
)
# 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,8 +1,7 @@
import itertools import itertools
import os from typing import Any, Iterator
import sys
VERBOSE = os.getenv("AOC_VERBOSE") == "True" from ..base import BaseSolver
digits = { digits = {
"abcefg": 0, "abcefg": 0,
@ -17,71 +16,74 @@ digits = {
"abcdfg": 9, "abcdfg": 9,
} }
lines = sys.stdin.read().splitlines()
# part 1 class Solver(BaseSolver):
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)} def solve(self, input: str) -> Iterator[Any]:
answer_1 = sum( lines = input.splitlines()
len(p) in lengths for line in lines for p in line.split("|")[1].strip().split()
)
print(f"answer 1 is {answer_1}")
# part 2 # part 1
values: list[int] = [] lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
yield sum(
len(p) in lengths
for line in lines
for p in line.split("|")[1].strip().split()
)
for line in lines: # part 2
parts = line.split("|") values: list[int] = []
broken_digits = sorted(parts[0].strip().split(), key=len)
per_length = { for line in lines:
k: list(v) parts = line.split("|")
for k, v in itertools.groupby(sorted(broken_digits, key=len), key=len) broken_digits = sorted(parts[0].strip().split(), key=len)
}
# a can be found immediately per_length = {
a = next(u for u in per_length[3][0] if u not in per_length[2][0]) k: list(v)
for k, v in itertools.groupby(sorted(broken_digits, key=len), key=len)
}
# c and f have only two possible values corresponding to the single entry of # a can be found immediately
# length 2 a = next(u for u in per_length[3][0] if u not in per_length[2][0])
cf = list(per_length[2][0])
# the only digit of length 4 contains bcdf, so we can deduce bd by removing cf # c and f have only two possible values corresponding to the single entry of
bd = [u for u in per_length[4][0] if u not in cf] # length 2
cf = list(per_length[2][0])
# the 3 digits of length 5 have a, d and g in common # the only digit of length 4 contains bcdf, so we can deduce bd by removing cf
adg = [u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])] bd = [u for u in per_length[4][0] if u not in cf]
# we can remove a # the 3 digits of length 5 have a, d and g in common
dg = [u for u in adg if u != a] adg = [
u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])
]
# we can deduce d and g # we can remove a
d = next(u for u in dg if u in bd) dg = [u for u in adg if u != a]
g = next(u for u in dg if u != d)
# then b # we can deduce d and g
b = next(u for u in bd if u != d) d = next(u for u in dg if u in bd)
g = next(u for u in dg if u != d)
# f is in the three 6-length digits, while c is only in 2 # then b
f = next(u for u in cf if all(u in p for p in per_length[6])) b = next(u for u in bd if u != d)
# c is not f # f is in the three 6-length digits, while c is only in 2
c = next(u for u in cf if u != f) f = next(u for u in cf if all(u in p for p in per_length[6]))
# e is the last one # c is not f
e = next(u for u in "abcdefg" if u not in {a, b, c, d, f, g}) c = next(u for u in cf if u != f)
mapping = dict(zip((a, b, c, d, e, f, g), "abcdefg")) # e is the last one
e = next(u for u in "abcdefg" if u not in {a, b, c, d, f, g})
value = 0 mapping = dict(zip((a, b, c, d, e, f, g), "abcdefg"))
for number in parts[1].strip().split():
digit = "".join(sorted(mapping[c] for c in number))
value = 10 * value + digits[digit]
if VERBOSE: value = 0
print(value) for number in parts[1].strip().split():
digit = "".join(sorted(mapping[c] for c in number))
value = 10 * value + digits[digit]
values.append(value) self.logger.info(f"value for '{line}' is {value}")
values.append(value)
answer_2 = sum(values) yield 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
values = [[int(c) for c in row] for row in sys.stdin.read().splitlines()] from ..base import BaseSolver
n_rows, n_cols = len(values), len(values[0])
def neighbors(point: tuple[int, int]): def neighbors(point: tuple[int, int], n_rows: int, n_cols: 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(start: tuple[int, int]) -> set[tuple[int, int]]: def basin(values: list[list[int]], 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,22 +23,25 @@ def basin(start: tuple[int, int]) -> set[tuple[int, int]]:
continue continue
visited.add((i, j)) visited.add((i, j))
queue.extend(neighbors((i, j))) queue.extend(neighbors((i, j), n_rows, n_cols))
return visited return visited
low_points = [ class Solver(BaseSolver):
(i, j) def solve(self, input: str) -> Iterator[Any]:
for i in range(n_rows) values = [[int(c) for c in row] for row in input.splitlines()]
for j in range(n_cols) n_rows, n_cols = len(values), len(values[0])
if all(values[ti][tj] > values[i][j] for ti, tj in neighbors((i, j)))
]
# part 1 low_points = [
answer_1 = sum(values[i][j] + 1 for i, j in low_points) (i, j)
print(f"answer 1 is {answer_1}") for i in range(n_rows)
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)
)
]
# part 2 yield sum(values[i][j] + 1 for i, j in low_points)
answer_2 = prod(sorted(len(basin(point)) for point in low_points)[-3:]) yield prod(sorted(len(basin(values, point)) for point in low_points)[-3:])
print(f"answer 2 is {answer_2}")

View File

@ -1,3 +1,4 @@
import itertools as it
from typing import Any, Iterator from typing import Any, Iterator
import numpy as np import numpy as np
@ -20,9 +21,7 @@ class Solver(BaseSolver):
no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1)) # type: ignore no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1)) # type: ignore
beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row) beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row)
no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference( no_beacons_row = set(it.chain(*no_beacons_row_l)).difference(beacons_at_row) # type: ignore
beacons_at_row
) # type: ignore
return len(no_beacons_row) return len(no_beacons_row)
@ -92,5 +91,5 @@ class Solver(BaseSolver):
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max) # x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
x, y, a2 = self.part2_intervals(sensor_to_beacon, xy_max) x, y, a2 = self.part2_intervals(sensor_to_beacon, xy_max)
self.logger.info("answer 2 is {at} (x={x}, y={y})") self.logger.info(f"answer 2 is {a2} (x={x}, y={y})")
yield a2 yield a2

View File

@ -3,7 +3,6 @@ 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 Any, FrozenSet, Iterator, NamedTuple
@ -38,8 +37,8 @@ def breadth_first_search(pipes: dict[str, Pipe], pipe: Pipe) -> dict[Pipe, int]:
Runs a BFS from the given pipe and return the shortest distance (in term of hops) Runs a BFS from the given pipe and return the shortest distance (in term of hops)
to all other pipes. to all other pipes.
""" """
queue = [(0, pipe_1)] queue = [(0, pipe)]
visited = set() visited: set[Pipe] = set()
distances: dict[Pipe, int] = {} distances: dict[Pipe, int] = {}
while len(distances) < len(pipes): while len(distances) < len(pipes):
@ -124,37 +123,37 @@ def part_2(
# === MAIN === # === MAIN ===
lines = sys.stdin.read().splitlines() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
pipes: dict[str, Pipe] = {}
for line in lines:
r = re.match(
R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
line,
)
assert r
pipes: dict[str, Pipe] = {} g = r.groups()
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[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
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()
}
)
# compute distances from one valve to any other # valves with flow
distances: dict[tuple[Pipe, Pipe], int] = {} relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
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 # 1651, 1653
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0) 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,14 +1,16 @@
import sys from typing import Any, Iterator, Sequence, TypeAlias, TypeVar
from typing import Any, Iterator, Sequence, TypeVar
import numpy as np import numpy as np
from numpy.typing import NDArray
from ..base import BaseSolver 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])):
@ -19,7 +21,7 @@ def print_tower(tower: np.ndarray, out: str = "#"):
print("+" + "-" * tower.shape[1] + "+") print("+" + "-" * tower.shape[1] + "+")
def tower_height(tower: np.ndarray) -> int: def tower_height(tower: Tower) -> int:
return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1) return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1)
@ -47,8 +49,8 @@ def build_tower(
n_rocks: int, n_rocks: int,
jets: str, jets: str,
early_stop: bool = False, early_stop: bool = False,
init: np.ndarray = np.ones(WIDTH, dtype=bool), init: Tower = np.ones(WIDTH, dtype=bool),
) -> tuple[np.ndarray, int, int, dict[int, int]]: ) -> tuple[Tower, int, int, dict[int, int]]:
tower = EMPTY_BLOCKS.copy() tower = EMPTY_BLOCKS.copy()
tower[0, :] = init tower[0, :] = init
@ -97,26 +99,24 @@ def build_tower(
return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights
line = sys.stdin.read().strip() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
tower, *_ = build_tower(2022, input)
yield tower_height(tower)
tower, *_ = build_tower(2022, line) TOTAL_ROCKS = 1_000_000_000_000
answer_1 = tower_height(tower) _tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, input, True)
print(f"answer 1 is {answer_1}") assert prev_1 > 0
TOTAL_ROCKS = 1_000_000_000_000 # 2767 1513
tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, line, True) remaining_rocks = TOTAL_ROCKS - n_rocks_1
assert prev_1 > 0 n_repeat_rocks = n_rocks_1 - prev_1
n_repeat_towers = remaining_rocks // n_repeat_rocks
# 2767 1513 base_height = heights_1[prev_1]
remaining_rocks = TOTAL_ROCKS - n_rocks_1 repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
n_repeat_rocks = n_rocks_1 - prev_1 remaining_height = (
n_repeat_towers = remaining_rocks // n_repeat_rocks heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
)
base_height = heights_1[prev_1] yield base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
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,53 +1,58 @@
import sys
from typing import Any, Iterator from typing import Any, Iterator
import numpy as np import numpy as np
from ..base import BaseSolver 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):
def solve(self, input: str) -> Iterator[Any]:
xyz = np.asarray(
[
tuple(int(x) for x in row.split(",")) # type: ignore
for row in input.splitlines()
]
)
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool) xyz = xyz - xyz.min(axis=0) + 1
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
n_dims = len(cubes.shape) cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
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)] faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
answer_1 = sum( yield sum(
1 for x, y, z in xyz for dx, dy, dz in faces if not cubes[x + dx, y + dy, z + dz] 1
) for x, y, z in xyz
print(f"answer 1 is {answer_1}") for dx, dy, dz in faces
if not cubes[x + dx, y + dy, z + dz]
)
visited = np.zeros_like(cubes, dtype=bool) visited = np.zeros_like(cubes, dtype=bool)
queue = [(0, 0, 0)] queue = [(0, 0, 0)]
n_faces = 0 n_faces = 0
while queue: while queue:
x, y, z = queue.pop(0) x, y, z = queue.pop(0)
if visited[x, y, z]: if visited[x, y, z]:
continue continue
visited[x, y, z] = True visited[x, y, z] = True
for dx, dy, dz in faces: for dx, dy, dz in faces:
nx, ny, nz = x + dx, y + dy, z + dz nx, ny, nz = x + dx, y + dy, z + dz
if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))): if not all(
continue n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))
):
continue
if visited[nx, ny, nz]: if visited[nx, ny, nz]:
continue continue
if cubes[nx, ny, nz]: if cubes[nx, ny, nz]:
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,4 +1,3 @@
import sys
from typing import Any, Iterator, Literal from typing import Any, Iterator, Literal
import numpy as np import numpy as np
@ -64,29 +63,6 @@ def dominates(lhs: State, rhs: State):
) )
lines = sys.stdin.read().splitlines()
blueprints: list[dict[Reagent, IntOfReagent]] = []
for line in lines:
r: list[int] = parse.parse( # type: ignore
"Blueprint {}: "
"Each ore robot costs {:d} ore. "
"Each clay robot costs {:d} ore. "
"Each obsidian robot costs {:d} ore and {:d} clay. "
"Each geode robot costs {:d} ore and {:d} obsidian.",
line,
)
blueprints.append(
{
"ore": {"ore": r[1]},
"clay": {"ore": r[2]},
"obsidian": {"ore": r[3], "clay": r[4]},
"geode": {"ore": r[5], "obsidian": r[6]},
}
)
def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int: def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
# since we can only build one robot per time, we do not need more than X robots # since we can only build one robot per time, we do not need more than X robots
# of type K where X is the maximum number of K required among all robots, e.g., # of type K where X is the maximum number of K required among all robots, e.g.,
@ -175,11 +151,31 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
return max(state.reagents["geode"] for state in state_after_t[max_time]) return max(state.reagents["geode"] for state in state_after_t[max_time])
answer_1 = sum( class Solver(BaseSolver):
(i_blueprint + 1) * run(blueprint, 24) def solve(self, input: str) -> Iterator[Any]:
for i_blueprint, blueprint in enumerate(blueprints) blueprints: list[dict[Reagent, IntOfReagent]] = []
) for line in input.splitlines():
print(f"answer 1 is {answer_1}") 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,
)
answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32) blueprints.append(
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,5 @@
from __future__ import annotations from __future__ import annotations
import sys
from typing import Any, Iterator from typing import Any, Iterator
from ..base import BaseSolver from ..base import BaseSolver
@ -68,10 +67,9 @@ def decrypt(numbers: list[Number], key: int, rounds: int) -> int:
) )
numbers = [Number(int(x)) for i, x in enumerate(sys.stdin.readlines())] class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
numbers = [Number(int(x)) for x in input.splitlines()]
answer_1 = decrypt(numbers, 1, 1) yield decrypt(numbers, 1, 1)
print(f"answer 1 is {answer_1}") yield decrypt(numbers, 811589153, 10)
answer_2 = decrypt(numbers, 811589153, 10)
print(f"answer 2 is {answer_2}")

View File

@ -1,5 +1,4 @@
import operator import operator
import sys
from typing import Any, Callable, Iterator from typing import Any, Callable, Iterator
from ..base import BaseSolver from ..base import BaseSolver
@ -79,31 +78,31 @@ def invert(
return monkeys return monkeys
lines = sys.stdin.read().splitlines() class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
monkeys: dict[str, int | tuple[str, str, str]] = {} monkeys: dict[str, int | tuple[str, str, str]] = {}
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")
answer_1 = compute(monkeys.copy(), "root") # assume the second operand of 'root' can be computed, and the first one depends on
print(f"answer 1 is {answer_1}") # humn, which is the case is my input and the test input
assert isinstance(monkeys["root"], tuple)
# assume the second operand of 'root' can be computed, and the first one depends on p1, _, p2 = monkeys["root"] # type: ignore
# humn, which is the case is my input and the test input yield compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
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,5 +1,4 @@
import re import re
import sys
from typing import Any, Callable, Iterator from typing import Any, Callable, Iterator
import numpy as np import numpy as np
@ -12,214 +11,233 @@ TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}
SCORES = {"E": 0, "S": 1, "W": 2, "N": 3} SCORES = {"E": 0, "S": 1, "W": 2, "N": 3}
board_map_s, direction_s = sys.stdin.read().split("\n\n") class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
board_map_s, direction_s = input.split("\n\n")
# board # board
board_lines = board_map_s.splitlines() board_lines = board_map_s.splitlines()
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 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s) int(p1) if p2 else p1
] for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
]
# find on each row and column the first and last non-void
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
)
# find on each row and column the first and last non-void faces = np.zeros_like(board)
row_first_non_void = np.argmax(board != VOID, axis=1) size = np.gcd(board.shape[0], board.shape[1])
row_last_non_void = board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1 for row in range(0, board.shape[0], size):
col_first_non_void = np.argmax(board != VOID, axis=0) for col in range(row_first_non_void[row], row_last_non_void[row], size):
col_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1 faces[row : row + size, col : col + size] = faces.max() + 1
SIZE = np.gcd(*board.shape)
faces = np.zeros_like(board) # TODO: deduce this from the actual cube...
size = np.gcd(board.shape[0], board.shape[1]) faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
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
SIZE = np.gcd(*board.shape) 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
},
}
# 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:
r0 = { faces_wrap = {
"E": {"L": "N", "R": "S"}, 1: {
"N": {"L": "W", "R": "E"}, "W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W
"W": {"L": "S", "R": "N"}, "N": lambda y, x: (2 * SIZE + x, 0, "E"), # 6W
"S": {"L": "E", "R": "W"}, },
}[r0][direction] 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
},
}
return y0, x0, r0 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
y1, x1, r1 = run(wrap_part_1) def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1] cube = faces[y0, x0]
print(f"answer 1 is {answer_1}") assert r0 in faces_wrap[cube]
return faces_wrap[cube][r0](y0, x0)
y2, x2, r2 = run(wrap_part_2) def run(
answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2] wrap: Callable[[int, int, str], tuple[int, int, str]],
print(f"answer 2 is {answer_2}") ) -> 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:
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,5 +1,4 @@
import itertools import itertools
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator from typing import Any, Iterator
@ -21,7 +20,7 @@ DIRECTIONS: Directions = [
def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]: def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]:
ys, xs = {y for y, x in positions}, {x for y, x in positions} ys, xs = {y for y, _x in positions}, {x for _y, x in positions}
return min(ys), min(xs), max(ys), max(xs) return min(ys), min(xs), max(ys), max(xs)
@ -72,35 +71,38 @@ def round(
directions.append(directions.pop(0)) directions.append(directions.pop(0))
POSITIONS = { class Solver(BaseSolver):
(i, j) def solve(self, input: str) -> Iterator[Any]:
for i, row in enumerate(sys.stdin.read().splitlines()) POSITIONS = {
for j, col in enumerate(row) (i, j)
if col == "#" for i, row in enumerate(input.splitlines())
} 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 r in range(10): for _ in range(10):
round(p1, d1) round(p1, d1)
min_y, min_x, max_y, max_x = min_max_yx(p1) min_y, min_x, max_y, max_x = min_max_yx(p1)
answer_1 = sum( yield sum(
(y, x) not in p1 for y in range(min_y, max_y + 1) for x in range(min_x, max_x + 1) (y, x) not in p1
) for y in range(min_y, max_y + 1)
print(f"answer 1 is {answer_1}") for x in range(min_x, max_x + 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
print(f"answer 2 is {answer_2}") yield answer_2

View File

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

View File

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