1 Commits

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,6 @@
import sys
from collections import Counter
from typing import Any, Iterator, Literal
from ..base import BaseSolver
from typing import Literal
def generator_rating(
@@ -21,23 +20,20 @@ def generator_rating(
return values[0]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().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)
)
# 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
]
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)
yield oxygen_generator_rating * co2_scrubber_rating
# 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,28 +1,23 @@
from typing import Any, Iterator
import sys
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):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
numbers = [int(c) for c in lines[0].split(",")]
boards = np.asarray(
boards = np.asarray(
[
[[int(c) for c in line.split()] for line in lines[start : start + 5]]
for start in range(2, len(lines), 6)
]
)
)
# (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)
# (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)
for round, number in enumerate(numbers):
for round, number in enumerate(numbers):
# mark boards
marked[boards == number] = True
@@ -31,9 +26,7 @@ class Solver(BaseSolver):
if winning_rounds[index][0] > 0:
continue
if np.any(
np.all(marked[index], axis=0) | np.all(marked[index], axis=1)
):
if np.any(np.all(marked[index], axis=0) | np.all(marked[index], axis=1)):
winning_rounds[index] = (
round,
number * int(np.sum(boards[index][~marked[index]])),
@@ -43,10 +36,10 @@ class Solver(BaseSolver):
if np.all(marked.all(axis=1) | marked.all(axis=2)):
break
# part 1
(_, score) = min(winning_rounds, key=lambda w: w[0])
yield score
# part 1
(_, score) = min(winning_rounds, key=lambda w: w[0])
print(f"answer 1 is {score}")
# part 2
(_, score) = max(winning_rounds, key=lambda w: w[0])
yield score
# part 2
(_, score) = max(winning_rounds, key=lambda w: w[0])
print(f"answer 2 is {score}")

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,8 @@
import itertools
from typing import Any, Iterator
import os
import sys
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
digits = {
"abcefg": 0,
@@ -16,23 +17,19 @@ digits = {
"abcdfg": 9,
}
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# part 1
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
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
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()
)
# part 2
values: list[int] = []
# part 2
values: list[int] = []
for line in lines:
for line in lines:
parts = line.split("|")
broken_digits = sorted(parts[0].strip().split(), key=len)
@@ -52,9 +49,7 @@ class Solver(BaseSolver):
bd = [u for u in per_length[4][0] if u not in cf]
# the 3 digits of length 5 have a, d and g in common
adg = [
u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])
]
adg = [u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])]
# we can remove a
dg = [u for u in adg if u != a]
@@ -82,8 +77,11 @@ class Solver(BaseSolver):
digit = "".join(sorted(mapping[c] for c in number))
value = 10 * value + digits[digit]
self.logger.info(f"value for '{line}' is {value}")
if VERBOSE:
print(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 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
for di, dj in ((-1, 0), (+1, 0), (0, -1), (0, +1)):
if 0 <= i + di < n_rows and 0 <= j + dj < n_cols:
yield (i + di, j + dj)
def basin(values: list[list[int]], start: tuple[int, int]) -> set[tuple[int, int]]:
n_rows, n_cols = len(values), len(values[0])
def basin(start: tuple[int, int]) -> set[tuple[int, int]]:
visited: set[tuple[int, int]] = set()
queue = [start]
@@ -23,25 +23,22 @@ def basin(values: list[list[int]], start: tuple[int, int]) -> set[tuple[int, int
continue
visited.add((i, j))
queue.extend(neighbors((i, j), n_rows, n_cols))
queue.extend(neighbors((i, j)))
return visited
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
values = [[int(c) for c in row] for row in input.splitlines()]
n_rows, n_cols = len(values), len(values[0])
low_points = [
low_points = [
(i, j)
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)
)
]
if all(values[ti][tj] > values[i][j] for ti, tj in neighbors((i, j)))
]
yield sum(values[i][j] + 1 for i, j in low_points)
yield prod(sorted(len(basin(values, point)) for point in low_points)[-3:])
# part 1
answer_1 = sum(values[i][j] + 1 for i, j in low_points)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = prod(sorted(len(basin(point)) for point in low_points)[-3:])
print(f"answer 2 is {answer_2}")

View File

@@ -1,12 +1,7 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
blocks = sys.stdin.read().split("\n\n")
values = sorted(sum(map(int, block.split())) for block in blocks)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blocks = input.split("\n\n")
values = sorted(sum(map(int, block.split())) for block in blocks)
yield values[-1]
yield sum(values[-3:])
print(f"answer 1 is {values[-1]}")
print(f"answer 2 is {sum(values[-3:])}")

View File

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

View File

@@ -1,8 +1,7 @@
import copy
import sys
from functools import reduce
from typing import Any, Callable, Final, Iterator, Mapping, Sequence
from ..base import BaseSolver
from typing import Callable, Final, Mapping, Sequence
class Monkey:
@@ -120,28 +119,24 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
return sorted_levels[-2] * sorted_levels[-1]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
monkeys = [parse_monkey(block.splitlines()) for block in input.split("\n\n")]
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
yield monkey_business(
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
answer_1 = monkey_business(
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
)
)
print(f"answer 1 is {answer_1}")
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
# use the product of all "divisible by" test so that the test remains valid
#
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
#
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
# we use the product of all test value
#
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
yield monkey_business(
run(
copy.deepcopy(monkeys),
10_000,
me_worry_fn=lambda w: w % total_test_value,
)
)
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
# use the product of all "divisible by" test so that the test remains valid
#
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
#
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
# we use the product of all test value
#
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
answer_2 = monkey_business(
run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
)
print(f"answer 2 is {answer_2}")

View File

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

View File

@@ -1,8 +1,11 @@
import json
import sys
from functools import cmp_to_key
from typing import Any, Iterator, TypeAlias, cast
from typing import TypeAlias, cast
from ..base import BaseSolver
blocks = sys.stdin.read().strip().split("\n\n")
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
Packet: TypeAlias = list[int | list["Packet"]]
@@ -25,18 +28,14 @@ def compare(lhs: Packet, rhs: Packet) -> int:
return len(rhs) - len(lhs)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blocks = input.split("\n\n")
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
print(f"answer_1 is {answer_1}")
yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
dividers = [[[2]], [[6]]]
dividers = [[[2]], [[6]]]
packets = [packet for packets in pairs for packet in packets]
packets.extend(dividers)
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
packets = [packet for packets in pairs for packet in packets]
packets.extend(dividers)
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
d_index = [packets.index(d) + 1 for d in dividers]
yield d_index[0] * d_index[1]
d_index = [packets.index(d) + 1 for d in dividers]
print(f"answer 2 is {d_index[0] * d_index[1]}")

View File

@@ -1,7 +1,6 @@
import sys
from enum import Enum, auto
from typing import Any, Callable, Iterator, cast
from ..base import BaseSolver
from typing import Callable, cast
class Cell(Enum):
@@ -13,6 +12,26 @@ class Cell(Enum):
return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self]
def print_blocks(blocks: dict[tuple[int, int], Cell]):
"""
Print the given set of blocks on a grid.
Args:
blocks: Set of blocks to print.
"""
x_min, y_min, x_max, y_max = (
min(x for x, _ in blocks),
0,
max(x for x, _ in blocks),
max(y for _, y in blocks),
)
for y in range(y_min, y_max + 1):
print(
"".join(str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1))
)
def flow(
blocks: dict[tuple[int, int], Cell],
stop_fn: Callable[[int, int], bool],
@@ -65,46 +84,21 @@ def flow(
# === inputs ===
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def print_blocks(self, blocks: dict[tuple[int, int], Cell]):
"""
Print the given set of blocks on a grid.
Args:
blocks: Set of blocks to print.
"""
x_min, y_min, x_max, y_max = (
min(x for x, _ in blocks),
0,
max(x for x, _ in blocks),
max(y for _, y in blocks),
)
for y in range(y_min, y_max + 1):
self.logger.info(
"".join(
str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1)
)
)
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
paths: list[list[tuple[int, int]]] = []
for line in lines:
paths: list[list[tuple[int, int]]] = []
for line in lines:
parts = line.split(" -> ")
paths.append(
[
cast(
tuple[int, int], tuple(int(c.strip()) for c in part.split(","))
)
cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
for part in parts
]
)
blocks: dict[tuple[int, int], Cell] = {}
for path in paths:
blocks: dict[tuple[int, int], Cell] = {}
for path in paths:
for start, end in zip(path[:-1], path[1:]):
x_start = min(start[0], end[0])
x_end = max(start[0], end[0]) + 1
@@ -115,25 +109,32 @@ class Solver(BaseSolver):
for y in range(y_start, y_end):
blocks[x, y] = Cell.ROCK
self.print_blocks(blocks)
print_blocks(blocks)
print()
y_max = max(y for _, y in blocks)
x_min, y_min, x_max, y_max = (
min(x for x, _ in blocks),
0,
max(x for x, _ in blocks),
max(y for _, y in blocks),
)
# === part 1 ===
# === part 1 ===
blocks_1 = flow(
blocks_1 = flow(
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
)
self.print_blocks(blocks_1)
yield sum(v == Cell.SAND for v in blocks_1.values())
)
print_blocks(blocks_1)
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
print()
# === part 2 ===
# === part 2 ===
blocks_2 = flow(
blocks_2 = flow(
blocks.copy(),
stop_fn=lambda x, y: x == 500 and y == 0,
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
)
blocks_2[500, 0] = Cell.SAND
self.print_blocks(blocks_2)
yield sum(v == Cell.SAND for v in blocks_2.values())
)
blocks_2[500, 0] = Cell.SAND
print_blocks(blocks_2)
print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")

View File

@@ -1,4 +1,4 @@
import itertools as it
import sys
from typing import Any, Iterator
import numpy as np
@@ -21,7 +21,9 @@ class Solver(BaseSolver):
no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1)) # type: ignore
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)
@@ -60,9 +62,8 @@ class Solver(BaseSolver):
for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by)
m.add_constraint(
m.abs(x - sx) + m.abs(y - sy) >= d + 1, # type: ignore
ctname=f"ct_{sx}_{sy}",
)
m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}"
) # type: ignore
m.set_objective("min", x + y)
@@ -91,5 +92,5 @@ class Solver(BaseSolver):
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
x, y, a2 = 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

View File

@@ -3,13 +3,12 @@ from __future__ import annotations
import heapq
import itertools
import re
import sys
from collections import defaultdict
from typing import Any, FrozenSet, Iterator, NamedTuple
from typing import FrozenSet, NamedTuple
from tqdm import tqdm
from ..base import BaseSolver
class Pipe(NamedTuple):
name: str
@@ -37,8 +36,8 @@ def breadth_first_search(pipes: dict[str, Pipe], pipe: Pipe) -> dict[Pipe, int]:
Runs a BFS from the given pipe and return the shortest distance (in term of hops)
to all other pipes.
"""
queue = [(0, pipe)]
visited: set[Pipe] = set()
queue = [(0, pipe_1)]
visited = set()
distances: dict[Pipe, int] = {}
while len(distances) < len(pipes):
@@ -123,12 +122,11 @@ def part_2(
# === MAIN ===
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
lines = sys.stdin.read().splitlines()
pipes: dict[str, Pipe] = {}
for line in lines:
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,
@@ -139,9 +137,9 @@ class Solver(BaseSolver):
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():
# 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
@@ -149,11 +147,12 @@ class Solver(BaseSolver):
}
)
# valves with flow
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
# valves with flow
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
# 1651, 1653
yield part_1(pipes["AA"], 30, distances, relevant_pipes)
# 1707, 2223
yield part_2(pipes["AA"], 26, distances, relevant_pipes)
# 1651, 1653
print(part_1(pipes["AA"], 30, distances, relevant_pipes))
# 1707, 2223
print(part_2(pipes["AA"], 26, distances, relevant_pipes))

View File

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

View File

@@ -1,38 +1,33 @@
from typing import Any, Iterator
import sys
import numpy as np
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
xyz = np.asarray(
xyz = np.asarray(
[
tuple(int(x) for x in row.split(",")) # type: ignore
for row in input.splitlines()
for row in sys.stdin.read().splitlines()
]
)
)
xyz = xyz - xyz.min(axis=0) + 1
xyz = xyz - xyz.min(axis=0) + 1
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
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)]
n_dims = len(cubes.shape)
yield sum(
1
for x, y, z in xyz
for dx, dy, dz in faces
if not cubes[x + dx, y + dy, z + dz]
)
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
visited = np.zeros_like(cubes, dtype=bool)
queue = [(0, 0, 0)]
answer_1 = sum(
1 for x, y, z in xyz for dx, dy, dz in faces if not cubes[x + dx, y + dy, z + dz]
)
print(f"answer 1 is {answer_1}")
n_faces = 0
while queue:
visited = np.zeros_like(cubes, dtype=bool)
queue = [(0, 0, 0)]
n_faces = 0
while queue:
x, y, z = queue.pop(0)
if visited[x, y, z]:
@@ -42,9 +37,7 @@ class Solver(BaseSolver):
for dx, dy, dz in faces:
nx, ny, nz = x + dx, y + dy, z + dz
if not all(
n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))
):
if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))):
continue
if visited[nx, ny, nz]:
@@ -54,5 +47,4 @@ class Solver(BaseSolver):
n_faces += 1
else:
queue.append((nx, ny, nz))
yield n_faces
print(f"answer 2 is {n_faces}")

View File

@@ -1,11 +1,10 @@
from typing import Any, Iterator, Literal
import sys
from typing import Any, Literal
import numpy as np
import parse # pyright: ignore[reportMissingTypeStubs]
from numpy.typing import NDArray
from ..base import BaseSolver
Reagent = Literal["ore", "clay", "obsidian", "geode"]
REAGENTS: tuple[Reagent, ...] = (
"ore",
@@ -63,6 +62,29 @@ def dominates(lhs: State, rhs: State):
)
lines = sys.stdin.read().splitlines()
blueprints: list[dict[Reagent, IntOfReagent]] = []
for line in lines:
r: list[int] = parse.parse( # type: ignore
"Blueprint {}: "
"Each ore robot costs {:d} ore. "
"Each clay robot costs {:d} ore. "
"Each obsidian robot costs {:d} ore and {:d} clay. "
"Each geode robot costs {:d} ore and {:d} obsidian.",
line,
)
blueprints.append(
{
"ore": {"ore": r[1]},
"clay": {"ore": r[2]},
"obsidian": {"ore": r[3], "clay": r[4]},
"geode": {"ore": r[5], "obsidian": r[6]},
}
)
def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
# 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.,
@@ -151,31 +173,11 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
return max(state.reagents["geode"] for state in state_after_t[max_time])
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blueprints: list[dict[Reagent, IntOfReagent]] = []
for line in input.splitlines():
r: list[int] = parse.parse( # type: ignore
"Blueprint {}: "
"Each ore robot costs {:d} ore. "
"Each clay robot costs {:d} ore. "
"Each obsidian robot costs {:d} ore and {:d} clay. "
"Each geode robot costs {:d} ore and {:d} obsidian.",
line,
)
blueprints.append(
{
"ore": {"ore": r[1]},
"clay": {"ore": r[2]},
"obsidian": {"ore": r[3], "clay": r[4]},
"geode": {"ore": r[5], "obsidian": r[6]},
}
)
yield sum(
answer_1 = sum(
(i_blueprint + 1) * run(blueprint, 24)
for i_blueprint, blueprint in enumerate(blueprints)
)
)
print(f"answer 1 is {answer_1}")
yield (run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32))
answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)
print(f"answer 2 is {answer_2}")

View File

@@ -1,6 +1,4 @@
from typing import Any, Iterator
from ..base import BaseSolver
import sys
def score_1(ux: int, vx: int) -> int:
@@ -35,23 +33,21 @@ def score_2(ux: int, vx: int) -> int:
return (ux + vx - 1) % 3 + 1 + vx * 3
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.readlines()
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
# modulo-3 arithmetic
#
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
#
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
# modulo-3 arithmetic
#
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
#
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
# easier manipulation
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
# easier manipulation
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
# part 1 - 13526
yield sum(score_1(*v) for v in values)
# part 1 - 13526
print(f"answer 1 is {sum(score_1(*v) for v in values)}")
# part 2 - 14204
yield sum(score_2(*v) for v in values)
# part 2 - 14204
print(f"answer 2 is {sum(score_2(*v) for v in values)}")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,22 +1,19 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
class Solver(BaseSolver):
def 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
for c in number:
value *= 5
value += coeffs[c]
return value
def number2snafu(number: int) -> str:
def number2snafu(number: int) -> str:
values = ["0", "1", "2", "=", "-"]
res = ""
while number > 0:
@@ -25,4 +22,6 @@ class Solver(BaseSolver):
number = number // 5 + int(mod >= 3)
return "".join(reversed(res))
yield number2snafu(sum(map(snafu2number, lines)))
answer_1 = number2snafu(sum(map(snafu2number, lines)))
print(f"answer 1 is {answer_1}")

View File

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

View File

@@ -1,6 +1,6 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = [line.strip() for line in sys.stdin.readlines()]
def make_range(value: str) -> set[int]:
@@ -8,13 +8,10 @@ def make_range(value: str) -> set[int]:
return set(range(int(parts[0]), int(parts[1]) + 1))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
sections = [tuple(make_range(part) for part in line.split(",")) for line in lines]
sections = [
tuple(make_range(part) for part in line.split(",")) for line in lines
]
answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
print(f"answer 1 is {answer_1}")
yield sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
yield sum(bool(s1.intersection(s2)) for s1, s2 in sections)
answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections)
print(f"answer 1 is {answer_2}")

View File

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

View File

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

View File

@@ -1,35 +1,30 @@
import sys
from pathlib import Path
from typing import Any, Iterator
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# we are going to use Path to create path and go up/down in the file tree since it
# implements everything we need
#
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
# on Windows but that is not an issue since only the sizes matter
#
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# mapping from path to list of files or directories
trees: dict[Path, list[Path]] = {}
# we are going to use Path to create path and go up/down in the file tree since it
# implements everything we need
#
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
# on Windows but that is not an issue since only the sizes matter
#
# mapping from paths to either size (for file) or -1 for directory
sizes: dict[Path, int] = {}
# mapping from path to list of files or directories
trees: dict[Path, list[Path]] = {}
# first line must be a cd otherwise we have no idea where we are
assert lines[0].startswith("$ cd")
base_path = Path(lines[0].strip("$").split()[1]).resolve()
cur_path = base_path
# mapping from paths to either size (for file) or -1 for directory
sizes: dict[Path, int] = {}
trees[cur_path] = []
sizes[cur_path] = -1
# first line must be a cd otherwise we have no idea where we are
assert lines[0].startswith("$ cd")
base_path = Path(lines[0].strip("$").split()[1]).resolve()
cur_path = base_path
trees[cur_path] = []
sizes[cur_path] = -1
for line in lines[1:]:
for line in lines[1:]:
# command
if line.startswith("$"):
parts = line.strip("$").strip().split()
@@ -58,7 +53,8 @@ class Solver(BaseSolver):
trees[cur_path].append(path)
sizes[path] = size
def compute_size(path: Path) -> int:
def compute_size(path: Path) -> int:
size = sizes[path]
if size >= 0:
@@ -66,16 +62,19 @@ class Solver(BaseSolver):
return sum(compute_size(sub) for sub in trees[path])
acc_sizes = {path: compute_size(path) for path in trees}
# part 1
yield sum(size for size in acc_sizes.values() if size <= 100_000)
acc_sizes = {path: compute_size(path) for path in trees}
# part 2
total_space = 70_000_000
update_space = 30_000_000
free_space = total_space - acc_sizes[base_path]
# part 1
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
print(f"answer 1 is {answer_1}")
to_free_space = update_space - free_space
# part 2
total_space = 70_000_000
update_space = 30_000_000
free_space = total_space - acc_sizes[base_path]
yield min(size for size in acc_sizes.values() if size >= to_free_space)
to_free_space = update_space - free_space
answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space)
print(f"answer 2 is {answer_2}")

View File

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

View File

@@ -1,10 +1,7 @@
import itertools as it
from typing import Any, Iterator
import sys
import numpy as np
from ..base import BaseSolver
def move(head: tuple[int, int], command: str) -> tuple[int, int]:
h_col, h_row = head
@@ -46,14 +43,17 @@ def run(commands: list[str], n_blocks: int) -> list[tuple[int, int]]:
return visited
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
lines = sys.stdin.read().splitlines()
# flatten the commands
commands = list(
it.chain(*(p[0] * int(p[1]) for line in lines if (p := line.split())))
)
# flatten the commands
commands: list[str] = []
for line in lines:
d, c = line.split()
commands.extend(d * int(c))
yield len(set(run(commands, n_blocks=2)))
yield len(set(run(commands, n_blocks=10)))
visited_1 = run(commands, n_blocks=2)
print(f"answer 1 is {len(set(visited_1))}")
visited_2 = run(commands, n_blocks=10)
print(f"answer 2 is {len(set(visited_2))}")

View File

@@ -1,5 +1,3 @@
# pyright: reportUnknownMemberType=false
from typing import Any, Iterator
import networkx as nx

View File

@@ -179,7 +179,7 @@ def main():
start = datetime.now()
last = start
it = solver.solve(data.rstrip())
it = solver.solve(data.strip())
if it is None:
solver.logger.error(f"no implementation for {year} day {day}")
@@ -193,7 +193,7 @@ def main():
"answer",
{
"answer": i_answer + 1,
"value": str(answer),
"value": answer,
"answerTime_s": (current - last).total_seconds(),
"totalTime_s": (current - start).total_seconds(),
},

View File

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