5 Commits

Author SHA1 Message Date
Mikaël Capelle
c69694da7e Force string type for answer value.
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-08 14:34:04 +01:00
Mikaël Capelle
d7c5b1b658 Apply formatting.
All checks were successful
continuous-integration/drone/push Build is passing
continuous-integration/drone/pr Build is passing
2024-12-08 14:05:14 +01:00
Mikaël Capelle
1b4dd32898 Refactor 2021 for new UI.
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-12-08 14:03:34 +01:00
Mikaël Capelle
cd96140378 Start fixing 2022 for new API. 2024-12-08 13:55:25 +01:00
Mikaël Capelle
377e501d34 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 12:13:41 +01:00
75 changed files with 1805 additions and 1734 deletions

View File

@@ -1,10 +1,12 @@
import sys
from typing import Any, Iterator
line = sys.stdin.read().strip()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
floor = 0
floors = [(floor := floor + (1 if c == "(" else -1)) for c in line]
floors = [(floor := floor + (1 if c == "(" else -1)) for c in input]
print(f"answer 1 is {floors[-1]}")
print(f"answer 2 is {floors.index(-1)}")
yield floors[-1]
yield floors.index(-1)

View File

@@ -1,7 +1,7 @@
import itertools
import sys
from typing import Any, Iterator
line = sys.stdin.read().strip()
from ..base import BaseSolver
# see http://www.se16.info/js/lands2.htm for the explanation of 'atoms' (or elements)
#
@@ -9,7 +9,7 @@ line = sys.stdin.read().strip()
# CodeGolf answer https://codegolf.stackexchange.com/a/8479/42148
# fmt: off
atoms = [
ATOMS: list[tuple[str, tuple[int, ...]]] = [
("22", (0, )), # 0
("13112221133211322112211213322112", (71, 90, 0, 19, 2, )), # 1
("312211322212221121123222112", (1, )), # 2
@@ -105,7 +105,7 @@ atoms = [
]
# fmt: on
starters = [
STARTERS = [
"1",
"11",
"21",
@@ -122,27 +122,26 @@ def look_and_say_length(s: str, n: int) -> int:
if n == 0:
return len(s)
if s in starters:
if s in STARTERS:
return look_and_say_length(
"".join(f"{len(list(g))}{k}" for k, g in itertools.groupby(s)), n - 1
)
counts = {i: 0 for i in range(len(atoms))}
idx = next(i for i, (a, _) in enumerate(atoms) if s == a)
counts = {i: 0 for i in range(len(ATOMS))}
idx = next(i for i, (a, _) in enumerate(ATOMS) if s == a)
counts[idx] = 1
for _ in range(n):
c2 = {i: 0 for i in range(len(atoms))}
c2 = {i: 0 for i in range(len(ATOMS))}
for i in counts:
for j in atoms[i][1]:
for j in ATOMS[i][1]:
c2[j] += counts[i]
counts = c2
return sum(counts[i] * len(a[0]) for i, a in enumerate(atoms))
return sum(counts[i] * len(a[0]) for i, a in enumerate(ATOMS))
answer_1 = look_and_say_length(line, 40)
print(f"answer 1 is {answer_1}")
answer_2 = look_and_say_length(line, 50)
print(f"answer 2 is {answer_2}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any] | None:
yield look_and_say_length(input, 40)
yield look_and_say_length(input, 50)

View File

@@ -1,5 +1,7 @@
import itertools
import sys
from typing import Any, Iterator
from ..base import BaseSolver
def is_valid(p: str) -> bool:
@@ -40,10 +42,8 @@ def find_next_password(p: str) -> str:
return p
line = sys.stdin.read().strip()
answer_1 = find_next_password(line)
print(f"answer 1 is {answer_1}")
answer_2 = find_next_password(increment(answer_1))
print(f"answer 2 is {answer_2}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
answer_1 = find_next_password(input)
yield answer_1
yield find_next_password(increment(answer_1))

View File

@@ -1,6 +1,7 @@
import json
import sys
from typing import TypeAlias
from typing import Any, Iterator, TypeAlias
from ..base import BaseSolver
JsonObject: TypeAlias = dict[str, "JsonObject"] | list["JsonObject"] | int | str
@@ -18,10 +19,9 @@ def json_sum(value: JsonObject, ignore: str | None = None) -> int:
return 0
data: JsonObject = json.load(sys.stdin)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
data: JsonObject = json.loads(input)
answer_1 = json_sum(data)
print(f"answer 1 is {answer_1}")
answer_2 = json_sum(data, "red")
print(f"answer 2 is {answer_2}")
yield json_sum(data)
yield json_sum(data, "red")

View File

@@ -1,10 +1,11 @@
import itertools
import sys
from collections import defaultdict
from typing import Literal, cast
from typing import Any, Iterator, Literal, cast
import parse # type: ignore
from ..base import BaseSolver
def max_change_in_happiness(happiness: dict[str, dict[str, int]]) -> int:
guests = list(happiness)
@@ -17,7 +18,9 @@ def max_change_in_happiness(happiness: dict[str, dict[str, int]]) -> int:
)
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
happiness: dict[str, dict[str, int]] = defaultdict(dict)
for line in lines:
@@ -29,13 +32,9 @@ for line in lines:
)
happiness[u1][u2] = hap if gain_or_loose == "gain" else -hap
answer_1 = max_change_in_happiness(happiness)
print(f"answer 1 is {answer_1}")
yield max_change_in_happiness(happiness)
for guest in list(happiness):
happiness["me"][guest] = 0
happiness[guest]["me"] = 0
answer_2 = max_change_in_happiness(happiness)
print(f"answer 2 is {answer_2}")
yield max_change_in_happiness(happiness)

View File

@@ -1,9 +1,10 @@
import sys
from dataclasses import dataclass
from typing import Literal, cast
from typing import Any, Iterator, Literal, cast
import parse # type: ignore
from ..base import BaseSolver
@dataclass(frozen=True)
class Reindeer:
@@ -13,7 +14,9 @@ class Reindeer:
rest_time: int
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
reindeers: list[Reindeer] = []
for line in lines:
@@ -26,7 +29,9 @@ for line in lines:
),
)
reindeers.append(
Reindeer(name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time)
Reindeer(
name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time
)
)
target = 1000 if len(reindeers) <= 2 else 2503
@@ -37,7 +42,7 @@ states: dict[Reindeer, tuple[Literal["resting", "flying"], int]] = {
distances: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
points: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
for time in range(target):
for time in self.progress.wrap(range(target)):
for reindeer in reindeers:
if states[reindeer][0] == "flying":
distances[reindeer] += reindeer.speed
@@ -54,9 +59,5 @@ for time in range(target):
else:
states[reindeer] = ("resting", time + reindeer.rest_time)
answer_1 = max(distances.values())
print(f"answer 1 is {answer_1}")
answer_2 = max(points.values()) - 1
print(f"answer 2 is {answer_2}")
yield max(distances.values())
yield max(points.values()) - 1

View File

@@ -1,9 +1,10 @@
import math
import sys
from typing import Sequence, cast
from typing import Any, Iterator, Sequence, cast
import parse # type: ignore
from ..base import BaseSolver
def score(ingredients: list[list[int]], teaspoons: Sequence[int]) -> int:
return math.prod(
@@ -18,7 +19,9 @@ def score(ingredients: list[list[int]], teaspoons: Sequence[int]) -> int:
)
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
ingredients: list[list[int]] = []
for line in lines:
@@ -49,9 +52,5 @@ for a in range(total_teaspoons + 1):
)
)
answer_1 = max(scores)
print(f"answer 1 is {answer_1}")
answer_2 = max(score for score, calory in zip(scores, calories) if calory == 500)
print(f"answer 2 is {answer_2}")
yield max(scores)
yield max(score for score, calory in zip(scores, calories) if calory == 500)

View File

@@ -1,8 +1,9 @@
import operator as op
import re
import sys
from collections import defaultdict
from typing import Callable
from typing import Any, Callable, Iterator
from ..base import BaseSolver
MFCSAM: dict[str, int] = {
"children": 3,
@@ -17,18 +18,10 @@ MFCSAM: dict[str, int] = {
"perfumes": 1,
}
lines = sys.stdin.readlines()
aunts: list[dict[str, int]] = [
{
match[1]: int(match[2])
for match in re.findall(R"((?P<compound>[^:, ]+): (?P<quantity>\d+))", line)
}
for line in lines
]
def match(operators: dict[str, Callable[[int, int], bool]]) -> int:
def match(
aunts: list[dict[str, int]], operators: dict[str, Callable[[int, int], bool]]
) -> int:
return next(
i
for i, aunt in enumerate(aunts, start=1)
@@ -36,16 +29,29 @@ def match(operators: dict[str, Callable[[int, int], bool]]) -> int:
)
answer_1 = match(defaultdict(lambda: op.eq))
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
answer_2 = match(
aunts: list[dict[str, int]] = [
{
match[1]: int(match[2])
for match in re.findall(
R"((?P<compound>[^:, ]+): (?P<quantity>\d+))", line
)
}
for line in lines
]
yield match(aunts, defaultdict(lambda: op.eq))
yield match(
aunts,
defaultdict(
lambda: op.eq,
trees=op.gt,
cats=op.gt,
pomeranians=op.lt,
goldfish=op.lt,
),
)
)
print(f"answer 2 is {answer_2}")

View File

@@ -1,5 +1,6 @@
import sys
from typing import Iterator
from typing import Any, Iterator
from ..base import BaseSolver
def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int, ...]]:
@@ -16,15 +17,18 @@ def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int,
yield (containers[i],) + combination
containers = [int(c) for c in sys.stdin.read().split()]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
containers = [int(c) for c in input.split()]
total = 25 if len(containers) <= 5 else 150
combinations = [combination for combination in iter_combinations(total, containers)]
combinations = [
combination for combination in iter_combinations(total, containers)
]
answer_1 = len(combinations)
print(f"answer 1 is {answer_1}")
yield len(combinations)
min_containers = min(len(combination) for combination in combinations)
answer_2 = sum(1 for combination in combinations if len(combination) == min_containers)
print(f"answer 2 is {answer_2}")
yield sum(
1 for combination in combinations if len(combination) == min_containers
)

View File

@@ -1,10 +1,15 @@
import itertools
import sys
from typing import Any, Iterator
import numpy as np
from numpy.typing import NDArray
grid0 = np.array([[c == "#" for c in line] for line in sys.stdin.read().splitlines()])
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
grid0 = np.array([[c == "#" for c in line] for line in input.splitlines()])
# add an always off circle around
grid0 = np.concatenate(
@@ -34,7 +39,6 @@ iis, jjs = iis.flatten(), jjs.flatten()
ins = iis[:, None] + np.array(moves)[:, 0]
jns = jjs[:, None] + np.array(moves)[:, 1]
def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
neighbors_on = grid[ins, jns].sum(axis=1)
cells_on = grid[iis, jjs]
@@ -44,15 +48,12 @@ def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
return grid
grid = grid0
n_steps = 4 if len(grid) < 10 else 100
for _ in range(n_steps):
grid = game_of_life(grid)
answer_1 = grid.sum()
print(f"answer 1 is {answer_1}")
yield grid.sum()
n_steps = 5 if len(grid) < 10 else 100
grid = grid0
@@ -62,5 +63,4 @@ for _ in range(n_steps):
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
answer_2 = sum(cell for line in grid for cell in line)
print(f"answer 2 is {answer_2}")
yield sum(cell for line in grid for cell in line)

View File

@@ -1,7 +1,12 @@
import sys
from collections import defaultdict
from typing import Any, Iterator
replacements_s, molecule = sys.stdin.read().split("\n\n")
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
replacements_s, molecule = input.split("\n\n")
REPLACEMENTS: dict[str, list[str]] = defaultdict(list)
for replacement_s in replacements_s.splitlines():
@@ -17,8 +22,7 @@ generated = [
if molecule[i:].startswith(symbol)
]
answer_1 = len(set(generated))
print(f"answer 1 is {answer_1}")
yield len(set(generated))
inversion: dict[str, str] = {
replacement: symbol
@@ -51,6 +55,4 @@ while molecule != "e":
# print(m2)
molecule = m2
answer_2 = count
print(f"answer 2 is {count}")
yield count

View File

@@ -1,20 +1,24 @@
import sys
from typing import Any, Iterator
import numpy as np
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
length, width, height = np.array(
[[int(c) for c in line.split("x")] for line in lines]
[[int(c) for c in line.split("x")] for line in input.splitlines()]
).T
lw, wh, hl = (length * width, width * height, height * length)
answer_1 = np.sum(2 * (lw + wh + hl) + np.min(np.stack([lw, wh, hl]), axis=0))
print(f"answer 1 is {answer_1}")
yield np.sum(2 * (lw + wh + hl) + np.min(np.stack([lw, wh, hl]), axis=0))
answer_2 = np.sum(
yield np.sum(
length * width * height
+ 2 * np.min(np.stack([length + width, length + height, height + width]), axis=0)
+ 2
* np.min(
np.stack([length + width, length + height, height + width]), axis=0
)
)
print(f"answer 2 is {answer_2}")

View File

@@ -1,10 +1,10 @@
import itertools
import sys
from typing import Any, Iterator
target = int(sys.stdin.read())
from ..base import BaseSolver
def presents(n: int, elf: int, max: int = target) -> int:
def presents(n: int, elf: int, max: int) -> int:
count = 0
k = 1
while k * k < n:
@@ -21,8 +21,9 @@ def presents(n: int, elf: int, max: int = target) -> int:
return count
answer_1 = next(n for n in itertools.count(1) if presents(n, 10) >= target)
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
target = int(input)
answer_2 = next(n for n in itertools.count(1) if presents(n, 11, 50) >= target)
print(f"answer 2 is {answer_2}")
yield next(n for n in itertools.count(1) if presents(n, 10, target) >= target)
yield next(n for n in itertools.count(1) if presents(n, 11, 50) >= target)

View File

@@ -1,7 +1,8 @@
import itertools
import sys
from math import ceil
from typing import TypeAlias
from typing import Any, Iterator, TypeAlias
from ..base import BaseSolver
Modifier: TypeAlias = tuple[str, int, int, int]
@@ -33,7 +34,9 @@ RINGS: list[Modifier] = [
]
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
player_hp = 100
@@ -41,7 +44,6 @@ boss_attack = int(lines[1].split(":")[1].strip())
boss_armor = int(lines[2].split(":")[1].strip())
boss_hp = int(lines[0].split(":")[1].strip())
min_cost, max_cost = 1_000_000, 0
for equipments in itertools.product(WEAPONS, ARMORS, RINGS, RINGS):
if equipments[-1][0] != "" and equipments[-2] == equipments[-1]:
@@ -58,9 +60,5 @@ for equipments in itertools.product(WEAPONS, ARMORS, RINGS, RINGS):
else:
max_cost = max(cost, max_cost)
answer_1 = min_cost
print(f"answer 1 is {answer_1}")
answer_2 = max_cost
print(f"answer 2 is {answer_2}")
yield min_cost
yield max_cost

View File

@@ -1,8 +1,9 @@
from __future__ import annotations
import heapq
import sys
from typing import Literal, TypeAlias, cast
from typing import Any, Iterator, Literal, TypeAlias, cast
from ..base import BaseSolver
PlayerType: TypeAlias = Literal["player", "boss"]
SpellType: TypeAlias = Literal["magic missile", "drain", "shield", "poison", "recharge"]
@@ -62,17 +63,6 @@ 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
@@ -88,6 +78,16 @@ 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":
@@ -155,7 +155,9 @@ def play(
return winning_node
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
player_hp = 50
player_mana = 500
@@ -164,14 +166,17 @@ player_armor = 0
boss_hp = int(lines[0].split(":")[1].strip())
boss_attack = int(lines[1].split(":")[1].strip())
answer_1 = sum(
yield sum(
c
for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, False)
for _, c in play(
player_hp, player_mana, player_armor, boss_hp, boss_attack, False
)
)
print(f"answer 1 is {answer_1}")
# 1242 (not working)
answer_2 = sum(
c for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, True)
yield sum(
c
for _, c in play(
player_hp, player_mana, player_armor, boss_hp, boss_attack, True
)
)
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,7 @@
import sys
from collections import defaultdict
from typing import Any, Iterator
line = sys.stdin.read().strip()
from ..base import BaseSolver
def process(directions: str) -> dict[tuple[int, int], int]:
@@ -27,8 +27,7 @@ def process(directions: str) -> dict[tuple[int, int], int]:
return counts
answer_1 = len(process(line))
print(f"answer 1 is {answer_1}")
answer_2 = len(process(line[::2]) | process(line[1::2]))
print(f"answer 2 is {answer_2}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
yield len(process(input))
yield len(process(input[::2]) | process(input[1::2]))

View File

@@ -1,16 +1,20 @@
import hashlib
import itertools
import sys
from typing import Any, Iterator
line = sys.stdin.read().strip()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
it = iter(itertools.count(1))
answer_1 = next(
i for i in it if hashlib.md5(f"{line}{i}".encode()).hexdigest().startswith("00000")
yield next(
i
for i in it
if hashlib.md5(f"{input}{i}".encode()).hexdigest().startswith("00000")
)
print(f"answer 1 is {answer_1}")
answer_2 = next(
i for i in it if hashlib.md5(f"{line}{i}".encode()).hexdigest().startswith("000000")
yield next(
i
for i in it
if hashlib.md5(f"{input}{i}".encode()).hexdigest().startswith("000000")
)
print(f"answer 2 is {answer_2}")

View File

@@ -1,4 +1,6 @@
import sys
from typing import Any, Iterator
from ..base import BaseSolver
VOWELS = "aeiou"
FORBIDDEN = {"ab", "cd", "pq", "xy"}
@@ -27,10 +29,8 @@ def is_nice_2(s: str) -> bool:
return True
lines = sys.stdin.read().splitlines()
answer_1 = sum(map(is_nice_1, lines))
print(f"answer 1 is {answer_1}")
answer_2 = sum(map(is_nice_2, lines))
print(f"answer 2 is {answer_2}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
yield sum(map(is_nice_1, lines))
yield sum(map(is_nice_2, lines))

View File

@@ -1,14 +1,16 @@
import sys
from typing import Literal, cast
from typing import Any, Iterator, Literal, cast
import numpy as np
import parse # type: ignore
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lights_1 = np.zeros((1000, 1000), dtype=bool)
lights_2 = np.zeros((1000, 1000), dtype=int)
for line in lines:
for line in input.splitlines():
action, sx, sy, ex, ey = cast(
tuple[Literal["turn on", "turn off", "toggle"], int, int, int, int],
parse.parse("{} {:d},{:d} through {:d},{:d}", line), # type: ignore
@@ -26,8 +28,5 @@ for line in lines:
lights_1[sx:ex, sy:ey] = ~lights_1[sx:ex, sy:ey]
lights_2[sx:ex, sy:ey] += 2
answer_1 = lights_1.sum()
print(f"answer 1 is {answer_1}")
answer_2 = lights_2.sum()
print(f"answer 2 is {answer_2}")
yield lights_1.sum()
yield lights_2.sum()

View File

@@ -1,11 +1,7 @@
import logging
import operator
import os
import sys
from typing import Callable
from typing import Any, Callable, Iterator
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
from ..base import BaseSolver
OPERATORS = {
"AND": operator.and_,
@@ -36,7 +32,22 @@ def value_of(key: str) -> tuple[str, Callable[[dict[str, int]], int]]:
return key, lambda values: values[key]
lines = sys.stdin.read().splitlines()
def process(
signals: Signals,
values: dict[str, int],
) -> dict[str, int]:
while signals:
signal = next(s for s in signals if all(p in values for p in signals[s][0]))
_, deps, command = signals[signal]
values[signal] = command(deps[0](values), deps[1](values)) % 65536
del signals[signal]
return values
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any] | None:
lines = input.splitlines()
signals: Signals = {}
values: dict[str, int] = {"": 0}
@@ -77,25 +88,9 @@ for line in lines:
signals[signal] = ((lhs_s, rhs_s), (lhs_fn, rhs_fn), op)
def process(
signals: Signals,
values: dict[str, int],
) -> dict[str, int]:
while signals:
signal = next(s for s in signals if all(p in values for p in signals[s][0]))
_, deps, command = signals[signal]
values[signal] = command(deps[0](values), deps[1](values)) % 65536
del signals[signal]
return values
values_1 = process(signals.copy(), values.copy())
logging.info("\n" + "\n".join(f"{k}: {values_1[k]}" for k in sorted(values_1)))
answer_1 = values_1["a"]
print(f"answer 1 is {answer_1}")
for k in sorted(values_1):
self.logger.info(f"{k}: {values_1[k]}")
yield values_1["a"]
values_2 = process(signals.copy(), values | {"b": values_1["a"]})
answer_2 = values_2["a"]
print(f"answer 2 is {answer_2}")
yield process(signals.copy(), values | {"b": values_1["a"]})["a"]

View File

@@ -1,14 +1,13 @@
import logging
import os
import sys
from typing import Any, Iterator
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
answer_1 = sum(
yield sum(
# left and right quotes (not in memory)
2
# each \\ adds one character in the literals (compared to memory)
@@ -21,9 +20,8 @@ answer_1 = sum(
+ 3 * (line.count(R"\x") - line.count(R"\\x") + line.count(R"\\\x"))
for line in lines
)
print(f"answer 1 is {answer_1}")
answer_2 = sum(
yield sum(
# needs to wrap in quotes (2 characters)
2
# needs to escape every \ with an extra \
@@ -32,4 +30,3 @@ answer_2 = sum(
+ line.count('"')
for line in lines
)
print(f"answer 2 is {answer_2}")

View File

@@ -1,11 +1,15 @@
import itertools
import sys
from collections import defaultdict
from typing import cast
from typing import Any, Iterator, cast
import parse # type: ignore
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
distances: dict[str, dict[str, int]] = defaultdict(dict)
for line in lines:
@@ -20,8 +24,5 @@ distance_of_routes = {
for route in map(tuple, itertools.permutations(distances))
}
answer_1 = min(distance_of_routes.values())
print(f"answer 1 is {answer_1}")
answer_2 = max(distance_of_routes.values())
print(f"answer 2 is {answer_2}")
yield min(distance_of_routes.values())
yield max(distance_of_routes.values())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,19 @@
import sys
from math import prod
from typing import Literal, TypeAlias, cast
from typing import Any, Iterator, Literal, TypeAlias, cast
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
Command: TypeAlias = Literal["forward", "up", "down"]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
commands: list[tuple[Command, int]] = [
(cast(Command, (p := line.split())[0]), int(p[1])) for line in lines
]
def depth_and_position(use_aim: bool):
aim, pos, depth = 0, 0, 0
for command, value in commands:
@@ -31,11 +34,5 @@ def depth_and_position(use_aim: bool):
return depth, pos
# 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}")
yield prod(depth_and_position(False))
yield prod(depth_and_position(True))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,8 +1,13 @@
import sys
from typing import Any, Iterator
import numpy as np
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
numbers = [int(c) for c in lines[0].split(",")]
@@ -26,7 +31,9 @@ for round, number in enumerate(numbers):
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]])),
@@ -38,8 +45,8 @@ for round, number in enumerate(numbers):
# part 1
(_, score) = min(winning_rounds, key=lambda w: w[0])
print(f"answer 1 is {score}")
yield score
# part 2
(_, score) = max(winning_rounds, key=lambda w: w[0])
print(f"answer 2 is {score}")
yield score

View File

@@ -1,8 +1,13 @@
import sys
from typing import Any, Iterator
import numpy as np
lines: list[str] = sys.stdin.read().splitlines()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
(
@@ -20,10 +25,8 @@ sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
np_sections = np.array(sections).reshape(-1, 4)
x_min, x_max, y_min, y_max = (
min(np_sections[:, 0].min(), np_sections[:, 2].min()),
x_max, y_max = (
max(np_sections[:, 0].max(), np_sections[:, 2].max()),
min(np_sections[:, 1].min(), np_sections[:, 3].min()),
max(np_sections[:, 1].max(), np_sections[:, 3].max()),
)
@@ -41,8 +44,5 @@ for (x1, y1), (x2, y2) in sections:
for i, j in zip(y_rng, x_rng):
counts_2[i, j] += 1
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}")
yield (counts_1 >= 2).sum()
yield (counts_2 >= 2).sum()

View File

@@ -1,6 +1,11 @@
import sys
from typing import Any, Iterator
values = [int(c) for c in sys.stdin.read().strip().split(",")]
from ..base import BaseSolver
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)}
@@ -12,10 +17,5 @@ for day in range(days):
for day2 in range(day + 9, days, 7):
lanterns[day2] += lanterns[day]
# 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}")
yield sum(v for k, v in lanterns.items() if k < 80) + len(values)
yield sum(lanterns.values()) + len(values)

View File

@@ -1,19 +1,22 @@
import sys
from typing import Any, Iterator
positions = [int(c) for c in sys.stdin.read().strip().split(",")]
from ..base import BaseSolver
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
answer_1 = min(
yield 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
answer_2 = min(
yield 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,8 +1,7 @@
import itertools
import os
import sys
from typing import Any, Iterator
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
from ..base import BaseSolver
digits = {
"abcefg": 0,
@@ -17,14 +16,18 @@ 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()
yield 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 2
values: list[int] = []
@@ -49,7 +52,9 @@ for line in lines:
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]
@@ -77,11 +82,8 @@ for line in lines:
digit = "".join(sorted(mapping[c] for c in number))
value = 10 * value + digits[digit]
if VERBOSE:
print(value)
self.logger.info(f"value for '{line}' is {value}")
values.append(value)
answer_2 = sum(values)
print(f"answer 2 is {answer_2}")
yield sum(values)

View File

@@ -1,18 +1,18 @@
import sys
from math import prod
from typing import Any, Iterator
values = [[int(c) for c in row] for row in sys.stdin.read().splitlines()]
n_rows, n_cols = len(values), len(values[0])
from ..base import BaseSolver
def neighbors(point: tuple[int, int]):
def neighbors(point: tuple[int, int], n_rows: int, n_cols: 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(start: tuple[int, int]) -> set[tuple[int, int]]:
def basin(values: list[list[int]], start: tuple[int, int]) -> set[tuple[int, int]]:
n_rows, n_cols = len(values), len(values[0])
visited: set[tuple[int, int]] = set()
queue = [start]
@@ -23,22 +23,25 @@ def basin(start: tuple[int, int]) -> set[tuple[int, int]]:
continue
visited.add((i, j))
queue.extend(neighbors((i, j)))
queue.extend(neighbors((i, j), n_rows, n_cols))
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 = [
(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)))
if all(
values[ti][tj] > values[i][j]
for ti, tj in neighbors((i, j), n_rows, n_cols)
)
]
# 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}")
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:])

View File

@@ -1,7 +1,12 @@
import sys
from typing import Any, Iterator
blocks = sys.stdin.read().split("\n\n")
from ..base import BaseSolver
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)
print(f"answer 1 is {values[-1]}")
print(f"answer 2 is {sum(values[-3:])}")
yield values[-1]
yield sum(values[-3:])

View File

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

View File

@@ -1,7 +1,8 @@
import copy
import sys
from functools import reduce
from typing import Callable, Final, Mapping, Sequence
from typing import Any, Callable, Final, Iterator, Mapping, Sequence
from ..base import BaseSolver
class Monkey:
@@ -119,13 +120,14 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
return sorted_levels[-2] * sorted_levels[-1]
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
monkeys = [parse_monkey(block.splitlines()) for block in input.split("\n\n")]
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
answer_1 = monkey_business(
yield 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
@@ -136,7 +138,10 @@ print(f"answer 1 is {answer_1}")
# 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)
yield 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,6 +1,7 @@
import heapq
import sys
from typing import Callable, Iterator, TypeVar
from typing import Any, Callable, Iterator, TypeVar
from ..base import BaseSolver
Node = TypeVar("Node")
@@ -68,30 +69,6 @@ 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]]:
@@ -118,7 +95,34 @@ 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]
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])
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]
@@ -145,19 +149,20 @@ assert end is not None
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
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
print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
self.print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
yield lengths_1[end] - 1
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
lengths_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}")
yield min(lengths_2.get(start, float("inf")) for start in start_s)

View File

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

View File

@@ -1,6 +1,7 @@
import sys
from enum import Enum, auto
from typing import Callable, cast
from typing import Any, Callable, Iterator, cast
from ..base import BaseSolver
class Cell(Enum):
@@ -12,26 +13,6 @@ 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],
@@ -84,19 +65,44 @@ 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:
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:
for start, end in zip(path[:-1], path[1:]):
@@ -109,24 +115,17 @@ for path in paths:
for y in range(y_start, y_end):
blocks[x, y] = Cell.ROCK
print_blocks(blocks)
print()
self.print_blocks(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),
)
y_max = max(y for _, y in blocks)
# === part 1 ===
blocks_1 = flow(
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
)
print_blocks(blocks_1)
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
print()
self.print_blocks(blocks_1)
yield sum(v == Cell.SAND for v in blocks_1.values())
# === part 2 ===
@@ -136,5 +135,5 @@ blocks_2 = flow(
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
)
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())}")
self.print_blocks(blocks_2)
yield sum(v == Cell.SAND for v in blocks_2.values())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,4 +1,6 @@
import sys
from typing import Any, Iterator
from ..base import BaseSolver
def score_1(ux: int, vx: int) -> int:
@@ -33,7 +35,9 @@ def score_2(ux: int, vx: int) -> int:
return (ux + vx - 1) % 3 + 1 + vx * 3
lines = sys.stdin.readlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
# modulo-3 arithmetic
@@ -47,7 +51,7 @@ lines = sys.stdin.readlines()
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
# part 1 - 13526
print(f"answer 1 is {sum(score_1(*v) for v in values)}")
yield sum(score_1(*v) for v in values)
# part 2 - 14204
print(f"answer 2 is {sum(score_2(*v) for v in values)}")
yield sum(score_2(*v) for v in values)

View File

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

View File

@@ -1,6 +1,7 @@
import operator
import sys
from typing import Callable
from typing import Any, Callable, Iterator
from ..base import BaseSolver
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
@@ -77,7 +78,9 @@ def invert(
return monkeys
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
monkeys: dict[str, int | tuple[str, str, str]] = {}
@@ -96,12 +99,10 @@ for line in lines:
op_monkeys.add(name)
answer_1 = compute(monkeys.copy(), "root")
print(f"answer 1 is {answer_1}")
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
answer_2 = compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
print(f"answer 2 is {answer_2}")
yield compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")

View File

@@ -1,16 +1,19 @@
import re
import sys
from typing import Callable
from typing import Any, Callable, Iterator
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}
board_map_s, direction_s = sys.stdin.read().split("\n\n")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
board_map_s, direction_s = input.split("\n\n")
# board
board_lines = board_map_s.splitlines()
@@ -23,16 +26,19 @@ board = np.array(
)
directions = [
int(p1) if p2 else p1 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
int(p1) if p2 else p1
for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
]
# find on each row and column the first and last non-void
row_first_non_void = np.argmax(board != VOID, axis=1)
row_last_non_void = board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1
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
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])
@@ -103,7 +109,6 @@ else:
},
}
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
@@ -116,14 +121,14 @@ def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
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]:
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"
@@ -132,7 +137,9 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
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
@@ -148,10 +155,14 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
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
@@ -167,7 +178,9 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
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)
@@ -175,7 +188,10 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
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:
@@ -184,7 +200,9 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
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)
@@ -192,7 +210,10 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
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:
@@ -201,7 +222,9 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
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 = {
@@ -213,11 +236,8 @@ def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int
return y0, x0, r0
y1, x1, r1 = run(wrap_part_1)
answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
print(f"answer 1 is {answer_1}")
yield 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
y2, x2, r2 = run(wrap_part_2)
answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
print(f"answer 2 is {answer_2}")
yield 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]

View File

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

View File

@@ -1,9 +1,14 @@
import heapq
import math
import sys
from collections import defaultdict
from typing import Any, Iterator
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
winds = {
(i - 1, j - 1, lines[i][j])
@@ -15,8 +20,12 @@ winds = {
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)]
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)
]
@@ -24,13 +33,14 @@ 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 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: {})
@@ -54,13 +64,17 @@ def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
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:
@@ -75,12 +89,17 @@ def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
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 = (
-1,
next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
@@ -91,8 +110,8 @@ end = (
)
distances_1, forward_1 = run(start, 0, end)
print(f"answer 1 is {forward_1}")
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)
print(f"answer 2 is {forward_1 + return_1 + forward_2}")
_distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
yield forward_1 + return_1 + forward_2

View File

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

View File

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

View File

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

View File

@@ -1,7 +1,12 @@
import copy
import sys
from typing import Any, Iterator
blocks_s, moves_s = (part.splitlines() for part in sys.stdin.read().split("\n\n"))
from ..base import BaseSolver
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()}
@@ -34,8 +39,5 @@ for move in moves_s:
blocks_2[to_].extend(blocks_2[from_][-count:])
del blocks_2[from_][-count:]
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}")
yield "".join(s[-1] for s in blocks_1.values())
yield "".join(s[-1] for s in blocks_2.values())

View File

@@ -1,4 +1,6 @@
import sys
from typing import Any, Iterator
from ..base import BaseSolver
def index_of_first_n_differents(data: str, n: int) -> int:
@@ -8,8 +10,7 @@ def index_of_first_n_differents(data: str, n: int) -> int:
return -1
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)}")
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)

View File

@@ -1,7 +1,12 @@
import sys
from pathlib import Path
from typing import Any, Iterator
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# we are going to use Path to create path and go up/down in the file tree since it
# implements everything we need
@@ -53,7 +58,6 @@ for line in lines[1:]:
trees[cur_path].append(path)
sizes[path] = size
def compute_size(path: Path) -> int:
size = sizes[path]
@@ -62,12 +66,10 @@ def compute_size(path: Path) -> int:
return sum(compute_size(sub) for sub in trees[path])
acc_sizes = {path: compute_size(path) for path in trees}
# part 1
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
print(f"answer 1 is {answer_1}")
yield sum(size for size in acc_sizes.values() if size <= 100_000)
# part 2
total_space = 70_000_000
@@ -76,5 +78,4 @@ free_space = total_space - acc_sizes[base_path]
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}")
yield min(size for size in acc_sizes.values() if size >= to_free_space)

View File

@@ -1,9 +1,14 @@
import sys
from typing import Any, Iterator
import numpy as np
from numpy.typing import NDArray
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
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])
@@ -22,9 +27,7 @@ highest_trees[1:-1, 1:-1] = [
for i in range(1, trees.shape[0] - 1)
]
answer_1 = (highest_trees.min(axis=2) < trees).sum()
print(f"answer 1 is {answer_1}")
yield (highest_trees.min(axis=2) < trees).sum()
def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
w = np.where(row_of_trees >= value)[0]
@@ -34,7 +37,6 @@ def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
return w[0] + 1
# answer 2
v_distances = np.zeros(trees.shape + (4,), dtype=int)
v_distances[1:-1, 1:-1, :] = [
@@ -49,5 +51,4 @@ v_distances[1:-1, 1:-1, :] = [
]
for i in range(1, trees.shape[0] - 1)
]
answer_2 = np.prod(v_distances, axis=2).max()
print(f"answer 2 is {answer_2}")
yield np.prod(v_distances, axis=2).max()

View File

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

View File

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

View File

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

View File

@@ -31,4 +31,4 @@ class BaseSolver:
self.outputs = outputs
@abstractmethod
def solve(self, input: str) -> Iterator[Any]: ...
def solve(self, input: str) -> Iterator[Any] | None: ...