Compare commits

..

1 Commits

Author SHA1 Message Date
Mikaël Capelle
6fd569aeba Start fixing 2022 for new API.
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-12-08 13:24:48 +01:00
37 changed files with 836 additions and 821 deletions

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,4 +1,3 @@
import itertools as it
from typing import Any, Iterator from typing import Any, Iterator
import numpy as np import numpy as np
@ -21,7 +20,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)
@ -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,6 +3,7 @@ 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
@ -37,8 +38,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 +124,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,14 @@
from typing import Any, Iterator, Sequence, TypeAlias, TypeVar import sys
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])):
@ -21,7 +19,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 +47,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 +97,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,53 @@
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()
]
)
class Solver(BaseSolver): xyz = xyz - xyz.min(axis=0) + 1
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()
]
)
xyz = xyz - xyz.min(axis=0) + 1 cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool) n_dims = len(cubes.shape)
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)]
yield sum( answer_1 = sum(
1 1 for x, y, z in xyz for dx, dy, dz in faces if not cubes[x + dx, y + dy, z + dz]
for x, y, z in xyz )
for dx, dy, dz in faces print(f"answer 1 is {answer_1}")
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( if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))):
n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz)) continue
):
continue
if visited[nx, ny, nz]: if visited[nx, ny, nz]:
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,3 +1,4 @@
import sys
from typing import Any, Iterator, Literal from typing import Any, Iterator, Literal
import numpy as np import numpy as np
@ -63,6 +64,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 +175,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,5 +1,6 @@
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
@ -67,9 +68,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,4 +1,5 @@
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
@ -78,31 +79,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,4 +1,5 @@
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
@ -11,233 +12,214 @@ 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,4 +1,5 @@
import itertools import itertools
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator from typing import Any, Iterator
@ -20,7 +21,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 +72,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,101 @@
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()
class Solver(BaseSolver): winds = {
def solve(self, input: str) -> Iterator[Any]: (i - 1, j - 1, lines[i][j])
lines = [line.strip() for line in input.splitlines()] for i in range(1, len(lines) - 1)
for j in range(1, len(lines[i]) - 1)
if lines[i][j] != "."
}
winds = { n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
(i - 1, j - 1, lines[i][j]) CYCLE = math.lcm(n_rows, n_cols)
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 east_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) 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)
]
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 = [ queue = [(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))]
(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle)) visited: set[tuple[tuple[int, int], int]] = set()
] 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
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,30 @@
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()
class Solver(BaseSolver): coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
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))
yield number2snafu(sum(map(snafu2number, lines))) 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))
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.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}")