2 Commits

Author SHA1 Message Date
Mikael CAPELLE
b21c50562f Refactor 2024 day 3.
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-03 15:22:37 +01:00
Mikael CAPELLE
211483f679 Add .drone.yml for CI.
All checks were successful
continuous-integration/drone Build is passing
2024-12-03 14:12:42 +01:00
111 changed files with 2440 additions and 5609 deletions

16
poetry.lock generated
View File

@@ -1245,20 +1245,6 @@ files = [
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"] test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
[[package]]
name = "types-networkx"
version = "3.4.2.20241115"
description = "Typing stubs for networkx"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-networkx-3.4.2.20241115.tar.gz", hash = "sha256:d669b650cf6c6c9ec879a825449eb04a5c10742f3109177e1683f57ee49e0f59"},
{file = "types_networkx-3.4.2.20241115-py3-none-any.whl", hash = "sha256:f0c382924d6614e06bf0b1ca0b837b8f33faa58982bc086ea762efaf39aa98dd"},
]
[package.dependencies]
numpy = ">=1.20"
[[package]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.12.2" version = "4.12.2"
@@ -1295,4 +1281,4 @@ files = [
[metadata] [metadata]
lock-version = "2.0" lock-version = "2.0"
python-versions = "^3.10" python-versions = "^3.10"
content-hash = "c91bc307ff4a5b3e8cd1976ebea211c9749fe09d563dd80861f70ce26826cda9" content-hash = "b643261f91a781d77735e05f6d2ac1002867600c2df6393a9d1a15f5e1189109"

View File

@@ -23,7 +23,6 @@ ruff = "^0.8.1"
poethepoet = "^0.31.1" poethepoet = "^0.31.1"
ipykernel = "^6.29.5" ipykernel = "^6.29.5"
networkx-stubs = "^0.0.1" networkx-stubs = "^0.0.1"
types-networkx = "^3.4.2.20241115"
[tool.poetry.scripts] [tool.poetry.scripts]
holt59-aoc = "holt59.aoc.__main__:main" holt59-aoc = "holt59.aoc.__main__:main"

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,24 +1,20 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
length, width, height = np.array( length, width, height = np.array(
[[int(c) for c in line.split("x")] for line in input.splitlines()] [[int(c) for c in line.split("x")] for line in lines]
).T ).T
lw, wh, hl = (length * width, width * height, height * length) lw, wh, hl = (length * width, width * height, height * length)
yield np.sum(2 * (lw + wh + hl) + np.min(np.stack([lw, wh, hl]), axis=0)) 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( answer_2 = np.sum(
length * width * height length * width * height
+ 2 + 2 * np.min(np.stack([length + width, length + height, height + width]), axis=0)
* 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 itertools
from typing import Any, Iterator import sys
from ..base import BaseSolver target = int(sys.stdin.read())
def presents(n: int, elf: int, max: int) -> int: def presents(n: int, elf: int, max: int = target) -> int:
count = 0 count = 0
k = 1 k = 1
while k * k < n: while k * k < n:
@@ -21,9 +21,8 @@ def presents(n: int, elf: int, max: int) -> int:
return count return count
class Solver(BaseSolver): answer_1 = next(n for n in itertools.count(1) if presents(n, 10) >= target)
def solve(self, input: str) -> Iterator[Any]: print(f"answer 1 is {answer_1}")
target = int(input)
yield next(n for n in itertools.count(1) if presents(n, 10, target) >= target) answer_2 = next(n for n in itertools.count(1) if presents(n, 11, 50) >= target)
yield next(n for n in itertools.count(1) if presents(n, 11, 50) >= target) print(f"answer 2 is {answer_2}")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,10 @@
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()]
cycle, x = 1, 1
values = {cycle: x} values = {cycle: x}
for line in lines: for line in lines:
@@ -26,18 +23,16 @@ class Solver(BaseSolver):
values[cycle] = x values[cycle] = x
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40)) answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
yield answer_1 print(f"answer 1 is {answer_1}")
yield (
"\n" for i in range(6):
+ "\n".join( for j in range(40):
"".join( v = values[1 + i * 40 + j]
"#"
if j >= (v := values[1 + i * 40 + j]) - 1 and j <= v + 1 if j >= v - 1 and j <= v + 1:
else "." print("#", end="")
for j in range(40) else:
) print(".", end="")
for i in range(6)
) print()
+ "\n"
)

View File

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

View File

@@ -1,7 +1,6 @@
import heapq import heapq
from typing import Any, Callable, Iterator, TypeVar import sys
from typing import Callable, Iterator, TypeVar
from ..base import BaseSolver
Node = TypeVar("Node") Node = TypeVar("Node")
@@ -69,6 +68,30 @@ def make_path(parents: dict[Node, Node], start: Node, end: Node) -> list[Node] |
return list(reversed(path)) return list(reversed(path))
def print_path(path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
end = path[-1]
graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
graph[end[0]][end[1]] = "E"
for i in range(0, len(path) - 1):
cr, cc = path[i]
nr, nc = path[i + 1]
if cr == nr and nc == cc - 1:
graph[cr][cc] = "<"
elif cr == nr and nc == cc + 1:
graph[cr][cc] = ">"
elif cr == nr - 1 and nc == cc:
graph[cr][cc] = "v"
elif cr == nr + 1 and nc == cc:
graph[cr][cc] = "^"
else:
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
print("\n".join("".join(row) for row in graph))
def neighbors( def neighbors(
grid: list[list[int]], node: tuple[int, int], up: bool grid: list[list[int]], node: tuple[int, int], up: bool
) -> Iterator[tuple[int, int]]: ) -> Iterator[tuple[int, int]]:
@@ -95,34 +118,7 @@ def neighbors(
# === main code === # === 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] grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
@@ -149,20 +145,19 @@ class Solver(BaseSolver):
grid[start[0]][start[1]] = 0 grid[start[0]][start[1]] = 0
grid[end[0]][end[1]] = ord("z") - ord("a") grid[end[0]][end[1]] = ord("z") - ord("a")
lengths_1, parents_1 = dijkstra( lengths_1, parents_1 = dijkstra(
start=start, start=start, neighbors=lambda n: neighbors(grid, n, True), cost=lambda lhs, rhs: 1
neighbors=lambda n: neighbors(grid, n, True),
cost=lambda lhs, rhs: 1,
) )
path_1 = make_path(parents_1, start, end) path_1 = make_path(parents_1, start, end)
assert path_1 is not None assert path_1 is not None
self.print_path(path_1, n_rows=len(grid), n_cols=len(grid[0])) print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
yield lengths_1[end] - 1
lengths_2, _ = dijkstra( print(f"answer 1 is {lengths_1[end] - 1}")
start=end,
neighbors=lambda n: neighbors(grid, n, False), lengths_2, parents_2 = dijkstra(
cost=lambda lhs, rhs: 1, 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) 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 json
import sys
from functools import cmp_to_key from functools import cmp_to_key
from typing import Any, Iterator, TypeAlias, cast from typing import TypeAlias, cast
from ..base import BaseSolver blocks = sys.stdin.read().strip().split("\n\n")
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
Packet: TypeAlias = list[int | list["Packet"]] Packet: TypeAlias = list[int | list["Packet"]]
@@ -25,12 +28,8 @@ def compare(lhs: Packet, rhs: Packet) -> int:
return len(rhs) - len(lhs) return len(rhs) - len(lhs)
class Solver(BaseSolver): answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
def solve(self, input: str) -> Iterator[Any]: print(f"answer_1 is {answer_1}")
blocks = input.split("\n\n")
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
dividers = [[[2]], [[6]]] dividers = [[[2]], [[6]]]
@@ -39,4 +38,4 @@ class Solver(BaseSolver):
packets = list(reversed(sorted(packets, key=cmp_to_key(compare)))) packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
d_index = [packets.index(d) + 1 for d in dividers] d_index = [packets.index(d) + 1 for d in dividers]
yield d_index[0] * d_index[1] 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 enum import Enum, auto
from typing import Any, Callable, Iterator, cast from typing import Callable, cast
from ..base import BaseSolver
class Cell(Enum): class Cell(Enum):
@@ -13,6 +12,26 @@ class Cell(Enum):
return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self] return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self]
def print_blocks(blocks: dict[tuple[int, int], Cell]):
"""
Print the given set of blocks on a grid.
Args:
blocks: Set of blocks to print.
"""
x_min, y_min, x_max, y_max = (
min(x for x, _ in blocks),
0,
max(x for x, _ in blocks),
max(y for _, y in blocks),
)
for y in range(y_min, y_max + 1):
print(
"".join(str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1))
)
def flow( def flow(
blocks: dict[tuple[int, int], Cell], blocks: dict[tuple[int, int], Cell],
stop_fn: Callable[[int, int], bool], stop_fn: Callable[[int, int], bool],
@@ -65,44 +84,19 @@ def flow(
# === inputs === # === 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]]] = [] paths: list[list[tuple[int, int]]] = []
for line in lines: for line in lines:
parts = line.split(" -> ") parts = line.split(" -> ")
paths.append( paths.append(
[ [
cast( cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
tuple[int, int], tuple(int(c.strip()) for c in part.split(","))
)
for part in parts for part in parts
] ]
) )
blocks: dict[tuple[int, int], Cell] = {} blocks: dict[tuple[int, int], Cell] = {}
for path in paths: for path in paths:
for start, end in zip(path[:-1], path[1:]): for start, end in zip(path[:-1], path[1:]):
@@ -115,17 +109,24 @@ class Solver(BaseSolver):
for y in range(y_start, y_end): for y in range(y_start, y_end):
blocks[x, y] = Cell.ROCK 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 blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
) )
self.print_blocks(blocks_1) print_blocks(blocks_1)
yield sum(v == Cell.SAND for v in blocks_1.values()) print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
print()
# === part 2 === # === part 2 ===
@@ -135,5 +136,5 @@ class Solver(BaseSolver):
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK, fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
) )
blocks_2[500, 0] = Cell.SAND blocks_2[500, 0] = Cell.SAND
self.print_blocks(blocks_2) print_blocks(blocks_2)
yield sum(v == Cell.SAND for v in blocks_2.values()) print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")

View File

@@ -1,16 +1,12 @@
from typing import Any, Iterator import sys
from typing import Any
import numpy as np import numpy as np
import parse # type: ignore import parse # type: ignore
from numpy.typing import NDArray from numpy.typing import NDArray
from ..base import BaseSolver
def part1(sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int) -> int:
class Solver(BaseSolver):
def part1(
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int
) -> int:
no_beacons_row_l: list[NDArray[np.floating[Any]]] = [] no_beacons_row_l: list[NDArray[np.floating[Any]]] = []
for (sx, sy), (bx, by) in sensor_to_beacon.items(): for (sx, sy), (bx, by) in sensor_to_beacon.items():
@@ -20,16 +16,17 @@ class Solver(BaseSolver):
no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1)) # type: ignore no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1)) # type: ignore
beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row) beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row)
no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference( no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference(beacons_at_row) # type: ignore
beacons_at_row
) # type: ignore
return len(no_beacons_row) return len(no_beacons_row)
def part2_intervals( def part2_intervals(
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]: ) -> tuple[int, int, int]:
for y in self.progress.wrap(range(xy_max + 1)): from tqdm import trange
for y in trange(xy_max + 1):
its: list[tuple[int, int]] = [] its: list[tuple[int, int]] = []
for (sx, sy), (bx, by) in sensor_to_beacon.items(): for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by) d = abs(sx - bx) + abs(sy - by)
@@ -49,8 +46,9 @@ class Solver(BaseSolver):
return (0, 0, 0) return (0, 0, 0)
def part2_cplex( def part2_cplex(
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]: ) -> tuple[int, int, int]:
from docplex.mp.model import Model from docplex.mp.model import Model
@@ -60,10 +58,7 @@ class Solver(BaseSolver):
for (sx, sy), (bx, by) in sensor_to_beacon.items(): for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by) d = abs(sx - bx) + abs(sy - by)
m.add_constraint( m.add_constraint(m.abs(x - sx) + m.abs(y - sy) >= d + 1, 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) m.set_objective("min", x + y)
@@ -74,8 +69,8 @@ class Solver(BaseSolver):
vy = int(s.get_value(y)) vy = int(s.get_value(y))
return vx, vy, 4_000_000 * vx + vy return vx, vy, 4_000_000 * vx + vy
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines() lines = sys.stdin.read().splitlines()
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {} sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {}
@@ -88,9 +83,8 @@ class Solver(BaseSolver):
xy_max = 4_000_000 if max(sensor_to_beacon) > (1_000, 0) else 20 xy_max = 4_000_000 if max(sensor_to_beacon) > (1_000, 0) else 20
row = 2_000_000 if max(sensor_to_beacon) > (1_000, 0) else 10 row = 2_000_000 if max(sensor_to_beacon) > (1_000, 0) else 10
yield self.part1(sensor_to_beacon, row) print(f"answer 1 is {part1(sensor_to_beacon, row)}")
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max) # x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
x, y, a2 = self.part2_intervals(sensor_to_beacon, xy_max) x, y, a2 = part2_intervals(sensor_to_beacon, xy_max)
self.logger.info("answer 2 is {at} (x={x}, y={y})") print(f"answer 2 is {a2} (x={x}, y={y})")
yield a2

View File

@@ -5,12 +5,10 @@ import itertools
import re import re
import sys import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, FrozenSet, Iterator, NamedTuple from typing import FrozenSet, NamedTuple
from tqdm import tqdm from tqdm import tqdm
from ..base import BaseSolver
class Pipe(NamedTuple): class Pipe(NamedTuple):
name: str name: str

View File

@@ -1,10 +1,8 @@
import sys import sys
from typing import Any, Iterator, Sequence, TypeVar from typing import Sequence, TypeVar
import numpy as np import numpy as np
from ..base import BaseSolver
T = TypeVar("T") T = TypeVar("T")

View File

@@ -1,10 +1,7 @@
import sys import sys
from typing import Any, Iterator
import numpy as np import numpy as np
from ..base import BaseSolver
xyz = np.asarray( xyz = np.asarray(
[ [
tuple(int(x) for x in row.split(",")) # type: ignore tuple(int(x) for x in row.split(",")) # type: ignore

View File

@@ -1,12 +1,10 @@
import sys import sys
from typing import Any, Iterator, Literal from typing import Any, Literal
import numpy as np import numpy as np
import parse # pyright: ignore[reportMissingTypeStubs] import parse # pyright: ignore[reportMissingTypeStubs]
from numpy.typing import NDArray from numpy.typing import NDArray
from ..base import BaseSolver
Reagent = Literal["ore", "clay", "obsidian", "geode"] Reagent = Literal["ore", "clay", "obsidian", "geode"]
REAGENTS: tuple[Reagent, ...] = ( REAGENTS: tuple[Reagent, ...] = (
"ore", "ore",

View File

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

View File

@@ -1,9 +1,6 @@
from __future__ import annotations from __future__ import annotations
import sys import sys
from typing import Any, Iterator
from ..base import BaseSolver
class Number: class Number:

View File

@@ -1,8 +1,6 @@
import operator import operator
import sys import sys
from typing import Any, Callable, Iterator from typing import Callable
from ..base import BaseSolver
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int: def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:

View File

@@ -1,11 +1,9 @@
import re import re
import sys import sys
from typing import Any, Callable, Iterator from typing import Callable
import numpy as np import numpy as np
from ..base import BaseSolver
VOID, EMPTY, WALL = 0, 1, 2 VOID, EMPTY, WALL = 0, 1, 2
TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL} TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}

View File

@@ -1,9 +1,6 @@
import itertools import itertools
import sys import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
Directions = list[ Directions = list[
tuple[ tuple[

View File

@@ -2,9 +2,6 @@ import heapq
import math import math
import sys import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
lines = sys.stdin.read().splitlines() lines = sys.stdin.read().splitlines()

View File

@@ -1,7 +1,4 @@
import sys import sys
from typing import Any, Iterator
from ..base import BaseSolver
lines = sys.stdin.read().splitlines() lines = sys.stdin.read().splitlines()

View File

@@ -1,28 +1,23 @@
import string import string
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = [line.strip() for line in sys.stdin.readlines()]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# extract content of each part # extract content of each part
parts = [ parts = [(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines]
(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines
]
# priorities # priorities
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)} priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
# part 1 # part 1
yield sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2)) part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
print(f"answer 1 is {part1}")
# part 2 # part 2
n_per_group = 3 n_per_group = 3
yield sum( part2 = sum(
priorities[c] priorities[c]
for i in range(0, len(lines), n_per_group) for i in range(0, len(lines), n_per_group)
for c in set(lines[i]).intersection(*lines[i + 1 : i + 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]: def make_range(value: str) -> set[int]:
@@ -8,13 +8,10 @@ def make_range(value: str) -> set[int]:
return set(range(int(parts[0]), int(parts[1]) + 1)) return set(range(int(parts[0]), int(parts[1]) + 1))
class Solver(BaseSolver): sections = [tuple(make_range(part) for part in line.split(",")) for line in lines]
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
sections = [ answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
tuple(make_range(part) for part in line.split(",")) for line in lines print(f"answer 1 is {answer_1}")
]
yield sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections) answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections)
yield sum(bool(s1.intersection(s2)) for s1, s2 in sections) print(f"answer 1 is {answer_2}")

View File

@@ -1,12 +1,7 @@
import copy import copy
from typing import Any, Iterator import sys
from ..base import BaseSolver blocks_s, moves_s = (part.splitlines() for part in sys.stdin.read().split("\n\n"))
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()} blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
@@ -39,5 +34,8 @@ class Solver(BaseSolver):
blocks_2[to_].extend(blocks_2[from_][-count:]) blocks_2[to_].extend(blocks_2[from_][-count:])
del blocks_2[from_][-count:] del blocks_2[from_][-count:]
yield "".join(s[-1] for s in blocks_1.values()) answer_1 = "".join(s[-1] for s in blocks_1.values())
yield "".join(s[-1] for s in blocks_2.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 import sys
from ..base import BaseSolver
def index_of_first_n_differents(data: str, n: int) -> int: def index_of_first_n_differents(data: str, n: int) -> int:
@@ -10,7 +8,8 @@ def index_of_first_n_differents(data: str, n: int) -> int:
return -1 return -1
class Solver(BaseSolver): data = sys.stdin.read().strip()
def solve(self, input: str) -> Iterator[Any]:
yield index_of_first_n_differents(input, 4)
yield index_of_first_n_differents(input, 14) print(f"answer 1 is {index_of_first_n_differents(data, 4)}")
print(f"answer 2 is {index_of_first_n_differents(data, 14)}")

View File

@@ -1,12 +1,7 @@
import sys
from pathlib import Path from pathlib import Path
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()]
# we are going to use Path to create path and go up/down in the file tree since it # we are going to use Path to create path and go up/down in the file tree since it
# implements everything we need # implements everything we need
@@ -58,6 +53,7 @@ class Solver(BaseSolver):
trees[cur_path].append(path) trees[cur_path].append(path)
sizes[path] = size sizes[path] = size
def compute_size(path: Path) -> int: def compute_size(path: Path) -> int:
size = sizes[path] size = sizes[path]
@@ -66,10 +62,12 @@ class Solver(BaseSolver):
return sum(compute_size(sub) for sub in trees[path]) return sum(compute_size(sub) for sub in trees[path])
acc_sizes = {path: compute_size(path) for path in trees} acc_sizes = {path: compute_size(path) for path in trees}
# part 1 # part 1
yield sum(size for size in acc_sizes.values() if size <= 100_000) answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
total_space = 70_000_000 total_space = 70_000_000
@@ -78,4 +76,5 @@ class Solver(BaseSolver):
to_free_space = update_space - free_space to_free_space = update_space - free_space
yield min(size for size in acc_sizes.values() if size >= to_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,14 +1,9 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from numpy.typing import NDArray from numpy.typing import NDArray
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
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]) trees = np.array([[int(x) for x in row] for row in lines])
@@ -27,7 +22,9 @@ class Solver(BaseSolver):
for i in range(1, trees.shape[0] - 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] w = np.where(row_of_trees >= value)[0]
@@ -37,6 +34,7 @@ class Solver(BaseSolver):
return w[0] + 1 return w[0] + 1
# answer 2 # answer 2
v_distances = np.zeros(trees.shape + (4,), dtype=int) v_distances = np.zeros(trees.shape + (4,), dtype=int)
v_distances[1:-1, 1:-1, :] = [ v_distances[1:-1, 1:-1, :] = [
@@ -51,4 +49,5 @@ class Solver(BaseSolver):
] ]
for i in range(1, trees.shape[0] - 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 import sys
from typing import Any, Iterator
import numpy as np import numpy as np
from ..base import BaseSolver
def move(head: tuple[int, int], command: str) -> tuple[int, int]: def move(head: tuple[int, int], command: str) -> tuple[int, int]:
h_col, h_row = head h_col, h_row = head
@@ -46,14 +43,17 @@ def run(commands: list[str], n_blocks: int) -> list[tuple[int, int]]:
return visited return visited
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# flatten the commands # flatten the commands
commands = list( commands: list[str] = []
it.chain(*(p[0] * int(p[1]) for line in lines if (p := line.split()))) for line in lines:
) d, c = line.split()
commands.extend(d * int(c))
yield len(set(run(commands, n_blocks=2)))
yield len(set(run(commands, n_blocks=10))) visited_1 = run(commands, n_blocks=2)
print(f"answer 1 is {len(set(visited_1))}")
visited_2 = run(commands, n_blocks=10)
print(f"answer 2 is {len(set(visited_2))}")

View File

@@ -1,9 +1,27 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
lookups_1 = {str(d): d for d in range(1, 10)}
lookups_2 = lookups_1 | {
d: i + 1
for i, d in enumerate(
(
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
)
)
}
def find_values(lines: list[str], lookups: dict[str, int]) -> list[int]: def find_values(lookups: dict[str, int]) -> list[int]:
values: list[int] = [] values: list[int] = []
for line in filter(bool, lines): for line in filter(bool, lines):
@@ -23,27 +41,5 @@ def find_values(lines: list[str], lookups: dict[str, int]) -> list[int]:
return values return values
class Solver(BaseSolver): print(f"answer 1 is {sum(find_values(lookups_1))}")
def solve(self, input: str) -> Iterator[Any]: print(f"answer 2 is {sum(find_values(lookups_2))}")
lookups_1 = {str(d): d for d in range(1, 10)}
lookups_2 = lookups_1 | {
d: i + 1
for i, d in enumerate(
(
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
)
)
}
lines = input.splitlines()
yield sum(find_values(lines, lookups_1))
yield sum(find_values(lines, lookups_2))

View File

@@ -1,14 +1,13 @@
from typing import Any, Iterator, Literal, cast import os
import sys
from typing import Literal, cast
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
Symbol = Literal["|", "-", "L", "J", "7", "F", ".", "S"] Symbol = Literal["|", "-", "L", "J", "7", "F", ".", "S"]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines: list[list[Symbol]] = [ lines: list[list[Symbol]] = [
[cast(Symbol, symbol) for symbol in line] for line in input.splitlines() [cast(Symbol, symbol) for symbol in line] for line in sys.stdin.read().splitlines()
] ]
# find starting point # find starting point
@@ -52,7 +51,8 @@ class Solver(BaseSolver):
loop.append((i, j)) loop.append((i, j))
yield len(loop) // 2 answer_1 = len(loop) // 2
print(f"answer 1 is {answer_1}")
# part 2 # part 2
@@ -83,18 +83,18 @@ class Solver(BaseSolver):
if (i, j) in loop_s and lines[i][j] in "|LJ": if (i, j) in loop_s and lines[i][j] in "|LJ":
cnt += 1 cnt += 1
if self.verbose: if VERBOSE:
for i in range(len(lines)): for i in range(len(lines)):
s = ""
for j in range(len(lines[0])): for j in range(len(lines[0])):
if (i, j) == (si, sj): if (i, j) == (si, sj):
s += "\033[91mS\033[0m" print("\033[91mS\033[0m", end="")
elif (i, j) in loop: elif (i, j) in loop:
s += lines[i][j] print(lines[i][j], end="")
elif (i, j) in inside: elif (i, j) in inside:
s += "\033[92mI\033[0m" print("\033[92mI\033[0m", end="")
else: else:
s += "." print(".", end="")
self.logger.info(s) print()
yield len(inside) answer_2 = len(inside)
print(f"answer 2 is {answer_2}")

View File

@@ -1,13 +1,8 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
data = np.array([[c == "#" for c in line] for line in lines]) data = np.array([[c == "#" for c in line] for line in lines])
@@ -16,6 +11,7 @@ class Solver(BaseSolver):
galaxies_y, galaxies_x = np.where(data) # type: ignore galaxies_y, galaxies_x = np.where(data) # type: ignore
def compute_total_distance(expansion: int) -> int: def compute_total_distance(expansion: int) -> int:
distances: list[int] = [] distances: list[int] = []
for g1 in range(len(galaxies_y)): for g1 in range(len(galaxies_y)):
@@ -35,8 +31,11 @@ class Solver(BaseSolver):
distances.append(dx + dy) distances.append(dx + dy)
return sum(distances) return sum(distances)
# part 1 # part 1
yield compute_total_distance(2) answer_1 = compute_total_distance(2)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield compute_total_distance(1000000) answer_2 = compute_total_distance(1000000)
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,9 @@
import os
import sys
from functools import lru_cache from functools import lru_cache
from typing import Any, Iterable, Iterator from typing import Iterable
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
@lru_cache @lru_cache
@@ -75,29 +77,31 @@ def compute_possible_arrangements(
) )
class Solver(BaseSolver): def compute_all_possible_arrangements(lines: Iterable[str], repeat: int) -> int:
def compute_all_possible_arrangements(
self, lines: Iterable[str], repeat: int
) -> int:
count = 0 count = 0
for i_line, line in enumerate(lines): if VERBOSE:
self.logger.info(f"processing line {i_line}: {line}...") from tqdm import tqdm
lines = tqdm(lines)
for line in lines:
parts = line.split(" ") parts = line.split(" ")
count += compute_possible_arrangements( count += compute_possible_arrangements(
tuple( tuple(filter(len, "?".join(parts[0] for _ in range(repeat)).split("."))),
filter(len, "?".join(parts[0] for _ in range(repeat)).split("."))
),
tuple(int(c) for c in parts[1].split(",")) * repeat, tuple(int(c) for c in parts[1].split(",")) * repeat,
) )
return count return count
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines() lines = sys.stdin.read().splitlines()
# part 1 # part 1
yield self.compute_all_possible_arrangements(lines, 1) answer_1 = compute_all_possible_arrangements(lines, 1)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield self.compute_all_possible_arrangements(lines, 5) answer_2 = compute_all_possible_arrangements(lines, 5)
print(f"answer 2 is {answer_2}")

View File

@@ -1,6 +1,5 @@
from typing import Any, Callable, Iterator, Literal import sys
from typing import Callable, Literal
from ..base import BaseSolver
def split(block: list[str], axis: Literal[0, 1], count: int) -> int: def split(block: list[str], axis: Literal[0, 1], count: int) -> int:
@@ -26,18 +25,19 @@ def split(block: list[str], axis: Literal[0, 1], count: int) -> int:
return 0 return 0
class Solver(BaseSolver): blocks = [block.splitlines() for block in sys.stdin.read().split("\n\n")]
def solve(self, input: str) -> Iterator[Any]:
blocks = [block.splitlines() for block in input.split("\n\n")]
# part 1 # part 1
yield sum( answer_1 = sum(
split(block, axis=1, count=0) + 100 * split(block, axis=0, count=0) split(block, axis=1, count=0) + 100 * split(block, axis=0, count=0)
for block in blocks for block in blocks
) )
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield sum( answer_2 = sum(
split(block, axis=1, count=1) + 100 * split(block, axis=0, count=1) split(block, axis=1, count=1) + 100 * split(block, axis=0, count=1)
for block in blocks for block in blocks
) )
print(f"answer 2 is {answer_2}")

View File

@@ -1,9 +1,10 @@
from typing import Any, Iterator, TypeAlias import sys
from typing import TypeAlias
from ..base import BaseSolver
RockGrid: TypeAlias = list[list[str]] RockGrid: TypeAlias = list[list[str]]
rocks0 = [list(line) for line in sys.stdin.read().splitlines()]
def slide_rocks_top(rocks: RockGrid) -> RockGrid: def slide_rocks_top(rocks: RockGrid) -> RockGrid:
top = [0 if c == "." else 1 for c in rocks[0]] top = [0 if c == "." else 1 for c in rocks[0]]
@@ -33,17 +34,13 @@ def cycle(rocks: RockGrid) -> RockGrid:
return rocks return rocks
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
rocks0 = [list(line) for line in input.splitlines()]
rocks = slide_rocks_top([[c for c in r] for r in rocks0]) rocks = slide_rocks_top([[c for c in r] for r in rocks0])
# part 1 # part 1
yield sum( answer_1 = sum(
(len(rocks) - i) * sum(1 for c in row if c == "O") (len(rocks) - i) * sum(1 for c in row if c == "O") for i, row in enumerate(rocks)
for i, row in enumerate(rocks)
) )
print(f"answer 1 is {answer_1}")
# part 2 # part 2
rocks = rocks0 rocks = rocks0
@@ -64,7 +61,8 @@ class Solver(BaseSolver):
ci = cycle_start + (N - cycle_start) % cycle_length - 1 ci = cycle_start + (N - cycle_start) % cycle_length - 1
yield sum( answer_2 = sum(
(len(rocks) - i) * sum(1 for c in row if c == "O") (len(rocks) - i) * sum(1 for c in row if c == "O")
for i, row in enumerate(cycles[ci]) for i, row in enumerate(cycles[ci])
) )
print(f"answer 2 is {answer_2}")

View File

@@ -1,19 +1,16 @@
import sys
from functools import reduce from functools import reduce
from typing import Any, Iterator
from ..base import BaseSolver steps = sys.stdin.read().strip().split(",")
def _hash(s: str) -> int: def _hash(s: str) -> int:
return reduce(lambda v, u: ((v + ord(u)) * 17) % 256, s, 0) return reduce(lambda v, u: ((v + ord(u)) * 17) % 256, s, 0)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
steps = input.split(",")
# part 1 # part 1
yield sum(map(_hash, steps)) answer_1 = sum(map(_hash, steps))
print(f"answer 1 is {answer_1}")
# part 2 # part 2
boxes: list[dict[str, int]] = [{} for _ in range(256)] boxes: list[dict[str, int]] = [{} for _ in range(256)]
@@ -26,8 +23,9 @@ class Solver(BaseSolver):
label = step[:-1] label = step[:-1]
boxes[_hash(label)].pop(label, None) boxes[_hash(label)].pop(label, None)
yield sum( answer_2 = sum(
i_box * i_lens * length i_box * i_lens * length
for i_box, box in enumerate(boxes, start=1) for i_box, box in enumerate(boxes, start=1)
for i_lens, length in enumerate(box.values(), start=1) for i_lens, length in enumerate(box.values(), start=1)
) )
print(f"answer 2 is {answer_2}")

View File

@@ -1,6 +1,8 @@
from typing import Any, Iterator, Literal, TypeAlias, cast import os
import sys
from typing import Literal, TypeAlias, cast
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
CellType: TypeAlias = Literal[".", "|", "-", "\\", "/"] CellType: TypeAlias = Literal[".", "|", "-", "\\", "/"]
Direction: TypeAlias = Literal["R", "L", "U", "D"] Direction: TypeAlias = Literal["R", "L", "U", "D"]
@@ -76,20 +78,19 @@ def propagate(
return beams return beams
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
layout: list[list[CellType]] = [ layout: list[list[CellType]] = [
[cast(CellType, col) for col in row] for row in input.splitlines() [cast(CellType, col) for col in row] for row in sys.stdin.read().splitlines()
] ]
beams = propagate(layout, (0, 0), "R") beams = propagate(layout, (0, 0), "R")
if self.verbose: if VERBOSE:
for row in beams: print("\n".join(["".join("#" if col else "." for col in row) for row in beams]))
self.logger.info("".join("#" if col else "." for col in row))
# part 1 # part 1
yield sum(sum(map(bool, row)) for row in beams) answer_1 = sum(sum(map(bool, row)) for row in beams)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
n_rows, n_cols = len(layout), len(layout[0]) n_rows, n_cols = len(layout), len(layout[0])
@@ -102,7 +103,8 @@ class Solver(BaseSolver):
cases.append(((0, col), "D")) cases.append(((0, col), "D"))
cases.append(((n_rows - 1, col), "U")) cases.append(((n_rows - 1, col), "U"))
yield max( answer_2 = max(
sum(sum(map(bool, row)) for row in propagate(layout, start, direction)) sum(sum(map(bool, row)) for row in propagate(layout, start, direction))
for start, direction in cases for start, direction in cases
) )
print(f"answer 2 is {answer_2}")

View File

@@ -1,11 +1,13 @@
from __future__ import annotations from __future__ import annotations
import heapq import heapq
import os
import sys
from collections import defaultdict from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Iterator, Literal, TypeAlias from typing import Literal, TypeAlias
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
Direction: TypeAlias = Literal[">", "<", "^", "v"] Direction: TypeAlias = Literal[">", "<", "^", "v"]
@@ -30,9 +32,7 @@ MAPPINGS: dict[Direction, tuple[int, int, Direction]] = {
} }
class Solver(BaseSolver):
def print_shortest_path( def print_shortest_path(
self,
grid: list[list[int]], grid: list[list[int]],
target: tuple[int, int], target: tuple[int, int],
per_cell: dict[tuple[int, int], list[tuple[Label, int]]], per_cell: dict[tuple[int, int], list[tuple[Label, int]]],
@@ -66,18 +66,16 @@ class Solver(BaseSolver):
if (r, c) != (prev_label.row, prev_label.col): if (r, c) != (prev_label.row, prev_label.col):
p_grid[r][c] = f"\033[93m{grid[r][c]}\033[0m" p_grid[r][c] = f"\033[93m{grid[r][c]}\033[0m"
p_grid[label.row][label.col] = ( p_grid[label.row][label.col] = f"\033[91m{grid[label.row][label.col]}\033[0m"
f"\033[91m{grid[label.row][label.col]}\033[0m"
)
prev_label = label prev_label = label
p_grid[0][0] = f"\033[92m{grid[0][0]}\033[0m" p_grid[0][0] = f"\033[92m{grid[0][0]}\033[0m"
for row in p_grid: print("\n".join("".join(row) for row in p_grid))
self.logger.info("".join(row))
def shortest_many_paths(self, grid: list[list[int]]) -> dict[tuple[int, int], int]:
def shortest_many_paths(grid: list[list[int]]) -> dict[tuple[int, int], int]:
n_rows, n_cols = len(grid), len(grid[0]) n_rows, n_cols = len(grid), len(grid[0])
visited: dict[tuple[int, int], tuple[Label, int]] = {} visited: dict[tuple[int, int], tuple[Label, int]] = {}
@@ -127,8 +125,8 @@ class Solver(BaseSolver):
return {(r, c): visited[r, c][1] for r in range(n_rows) for c in range(n_cols)} return {(r, c): visited[r, c][1] for r in range(n_rows) for c in range(n_cols)}
def shortest_path( def shortest_path(
self,
grid: list[list[int]], grid: list[list[int]],
min_straight: int, min_straight: int,
max_straight: int, max_straight: int,
@@ -217,17 +215,19 @@ class Solver(BaseSolver):
), ),
) )
if self.verbose: if VERBOSE:
self.print_shortest_path(grid, target, per_cell) print_shortest_path(grid, target, per_cell)
return per_cell[target][0][1] return per_cell[target][0][1]
def solve(self, input: str) -> Iterator[Any]:
data = [[int(c) for c in r] for r in input.splitlines()] data = [[int(c) for c in r] for r in sys.stdin.read().splitlines()]
estimates = self.shortest_many_paths(data) estimates = shortest_many_paths(data)
# part 1 # part 1
yield self.shortest_path(data, 1, 3, lower_bounds=estimates) answer_1 = shortest_path(data, 1, 3, lower_bounds=estimates)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield self.shortest_path(data, 4, 10, lower_bounds=estimates) answer_2 = shortest_path(data, 4, 10, lower_bounds=estimates)
print(f"answer 2 is {answer_2}")

View File

@@ -1,6 +1,5 @@
from typing import Any, Iterator, Literal, TypeAlias, cast import sys
from typing import Literal, TypeAlias, cast
from ..base import BaseSolver
Direction: TypeAlias = Literal["R", "L", "U", "D"] Direction: TypeAlias = Literal["R", "L", "U", "D"]
@@ -34,19 +33,17 @@ def polygon(values: list[tuple[Direction, int]]) -> tuple[list[tuple[int, int]],
return corners, perimeter return corners, perimeter
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# part 1 # part 1
yield area( answer_1 = area(
*polygon( *polygon([(cast(Direction, (p := line.split())[0]), int(p[1])) for line in lines])
[(cast(Direction, (p := line.split())[0]), int(p[1])) for line in lines]
)
) )
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield area( answer_2 = area(
*polygon( *polygon(
[ [
(DIRECTIONS[int((h := line.split()[-1])[-2])], int(h[2:-2], 16)) (DIRECTIONS[int((h := line.split()[-1])[-2])], int(h[2:-2], 16))
@@ -54,3 +51,4 @@ class Solver(BaseSolver):
] ]
) )
) )
print(f"answer 2 is {answer_2}")

View File

@@ -1,8 +1,13 @@
import logging
import operator import operator
import os
import sys
from math import prod from math import prod
from typing import Any, Iterator, Literal, TypeAlias, cast from typing import Literal, TypeAlias, cast
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
Category: TypeAlias = Literal["x", "m", "a", "s"] Category: TypeAlias = Literal["x", "m", "a", "s"]
Part: TypeAlias = dict[Category, int] Part: TypeAlias = dict[Category, int]
@@ -17,8 +22,7 @@ Check: TypeAlias = tuple[Category, Literal["<", ">"], int] | None
Workflow: TypeAlias = list[tuple[Check, str]] Workflow: TypeAlias = list[tuple[Check, str]]
class Solver(BaseSolver): def accept(workflows: dict[str, Workflow], part: Part) -> bool:
def accept(self, workflows: dict[str, Workflow], part: Part) -> bool:
workflow = "in" workflow = "in"
decision: bool | None = None decision: bool | None = None
@@ -38,7 +42,8 @@ class Solver(BaseSolver):
return decision return decision
def propagate(self, workflows: dict[str, Workflow], start: PartWithBounds) -> int:
def propagate(workflows: dict[str, Workflow], start: PartWithBounds) -> int:
def _fmt(meta: PartWithBounds) -> str: def _fmt(meta: PartWithBounds) -> str:
return "{" + ", ".join(f"{k}={v}" for k, v in meta.items()) + "}" return "{" + ", ".join(f"{k}={v}" for k, v in meta.items()) + "}"
@@ -47,13 +52,13 @@ class Solver(BaseSolver):
) -> int: ) -> int:
count = 0 count = 0
if target in workflows: if target in workflows:
self.logger.info(f" transfer to {target}") logging.info(f" transfer to {target}")
queue.append((meta, target)) queue.append((meta, target))
elif target == "A": elif target == "A":
count = prod((high - low + 1) for low, high in meta.values()) count = prod((high - low + 1) for low, high in meta.values())
self.logger.info(f" accepted ({count})") logging.info(f" accepted ({count})")
else: else:
self.logger.info(" rejected") logging.info(" rejected")
return count return count
accepted = 0 accepted = 0
@@ -64,26 +69,24 @@ class Solver(BaseSolver):
while queue: while queue:
n_iterations += 1 n_iterations += 1
meta, workflow = queue.pop() meta, workflow = queue.pop()
self.logger.info(f"{workflow}: {_fmt(meta)}") logging.info(f"{workflow}: {_fmt(meta)}")
for check, target in workflows[workflow]: for check, target in workflows[workflow]:
if check is None: if check is None:
self.logger.info(" end-of-workflow") logging.info(" end-of-workflow")
accepted += transfer_or_accept(target, meta, queue) accepted += transfer_or_accept(target, meta, queue)
continue continue
category, sense, value = check category, sense, value = check
bounds, op = meta[category], OPERATORS[sense] bounds, op = meta[category], OPERATORS[sense]
self.logger.info( logging.info(f" checking {_fmt(meta)} against {category} {sense} {value}")
f" checking {_fmt(meta)} against {category} {sense} {value}"
)
if not op(bounds[0], value) and not op(bounds[1], value): if not op(bounds[0], value) and not op(bounds[1], value):
self.logger.info(" reject, always false") logging.info(" reject, always false")
continue continue
if op(meta[category][0], value) and op(meta[category][1], value): if op(meta[category][0], value) and op(meta[category][1], value):
self.logger.info(" accept, always true") logging.info(" accept, always true")
accepted += transfer_or_accept(target, meta, queue) accepted += transfer_or_accept(target, meta, queue)
break break
@@ -93,15 +96,15 @@ class Solver(BaseSolver):
meta[category], meta2[category] = (value, high), (low, value - 1) meta[category], meta2[category] = (value, high), (low, value - 1)
else: else:
meta[category], meta2[category] = (low, value), (value + 1, high) meta[category], meta2[category] = (low, value), (value + 1, high)
self.logger.info(f" split {_fmt(meta2)} ({target}), {_fmt(meta)}") logging.info(f" split {_fmt(meta2)} ({target}), {_fmt(meta)}")
accepted += transfer_or_accept(target, meta2, queue) accepted += transfer_or_accept(target, meta2, queue)
self.logger.info(f"run took {n_iterations} iterations") logging.info(f"run took {n_iterations} iterations")
return accepted return accepted
def solve(self, input: str) -> Iterator[Any]:
workflows_s, parts_s = input.split("\n\n") workflows_s, parts_s = sys.stdin.read().strip().split("\n\n")
workflows: dict[str, Workflow] = {} workflows: dict[str, Workflow] = {}
for workflow_s in workflows_s.split("\n"): for workflow_s in workflows_s.split("\n"):
@@ -126,9 +129,12 @@ class Solver(BaseSolver):
{cast(Category, s[0]): int(s[2:]) for s in part_s[1:-1].split(",")} {cast(Category, s[0]): int(s[2:]) for s in part_s[1:-1].split(",")}
for part_s in parts_s.split("\n") for part_s in parts_s.split("\n")
] ]
yield sum(sum(part.values()) for part in parts if self.accept(workflows, part)) answer_1 = sum(sum(part.values()) for part in parts if accept(workflows, part))
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield self.propagate( answer_2 = propagate(
workflows, {cast(Category, c): (1, 4000) for c in ["x", "m", "a", "s"]} workflows, {cast(Category, c): (1, 4000) for c in ["x", "m", "a", "s"]}
) )
print(f"answer 2 is {answer_2}")

View File

@@ -1,16 +1,13 @@
import math import math
from typing import Any, Iterator, Literal, TypeAlias, cast import sys
from typing import Literal, TypeAlias, cast
from ..base import BaseSolver
CubeType: TypeAlias = Literal["red", "blue", "green"] CubeType: TypeAlias = Literal["red", "blue", "green"]
MAX_CUBES: dict[CubeType, int] = {"red": 12, "green": 13, "blue": 14} MAX_CUBES: dict[CubeType, int] = {"red": 12, "green": 13, "blue": 14}
# parse games
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
games: dict[int, list[dict[CubeType, int]]] = {} games: dict[int, list[dict[CubeType, int]]] = {}
for line in filter(bool, lines): for line in filter(bool, lines):
id_part, sets_part = line.split(":") id_part, sets_part = line.split(":")
@@ -24,7 +21,8 @@ class Solver(BaseSolver):
for cube_set_s in sets_part.strip().split(";") for cube_set_s in sets_part.strip().split(";")
] ]
yield sum( # part 1
answer_1 = sum(
id id
for id, set_of_cubes in games.items() for id, set_of_cubes in games.items()
if all( if all(
@@ -33,11 +31,13 @@ class Solver(BaseSolver):
for cube, n_cubes in cube_set.items() for cube, n_cubes in cube_set.items()
) )
) )
print(f"answer 1 is {answer_1}")
yield sum( # part 2
answer_2 = sum(
math.prod( math.prod(
max(cube_set.get(cube, 0) for cube_set in set_of_cubes) max(cube_set.get(cube, 0) for cube_set in set_of_cubes) for cube in MAX_CUBES
for cube in MAX_CUBES
) )
for set_of_cubes in games.values() for set_of_cubes in games.values()
) )
print(f"answer 2 is {answer_2}")

View File

@@ -1,42 +1,55 @@
import logging
import os
import sys import sys
from collections import defaultdict from collections import defaultdict
from math import lcm from math import lcm
from typing import Any, Iterator, Literal, TypeAlias from typing import Literal, TypeAlias
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
from ..base import BaseSolver
ModuleType: TypeAlias = Literal["broadcaster", "conjunction", "flip-flop"] ModuleType: TypeAlias = Literal["broadcaster", "conjunction", "flip-flop"]
PulseType: TypeAlias = Literal["high", "low"] PulseType: TypeAlias = Literal["high", "low"]
modules: dict[str, tuple[ModuleType, list[str]]] = {}
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
_modules: dict[str, tuple[ModuleType, list[str]]]
def _process( for line in lines:
self, name, outputs_s = line.split(" -> ")
outputs = outputs_s.split(", ")
if name == "broadcaster":
modules["broadcaster"] = ("broadcaster", outputs)
else:
modules[name[1:]] = (
"conjunction" if name.startswith("&") else "flip-flop",
outputs,
)
def process(
start: tuple[str, str, PulseType], start: tuple[str, str, PulseType],
flip_flop_states: dict[str, Literal["on", "off"]], flip_flop_states: dict[str, Literal["on", "off"]],
conjunction_states: dict[str, dict[str, PulseType]], conjunction_states: dict[str, dict[str, PulseType]],
) -> tuple[dict[PulseType, int], dict[str, dict[PulseType, int]]]: ) -> tuple[dict[PulseType, int], dict[str, dict[PulseType, int]]]:
pulses: list[tuple[str, str, PulseType]] = [start] pulses: list[tuple[str, str, PulseType]] = [start]
counts: dict[PulseType, int] = {"low": 0, "high": 0} counts: dict[PulseType, int] = {"low": 0, "high": 0}
inputs: dict[str, dict[PulseType, int]] = defaultdict( inputs: dict[str, dict[PulseType, int]] = defaultdict(lambda: {"low": 0, "high": 0})
lambda: {"low": 0, "high": 0}
)
self.logger.info("starting process... ") logging.info("starting process... ")
while pulses: while pulses:
input, name, pulse = pulses.pop(0) input, name, pulse = pulses.pop(0)
self.logger.info(f"{input} -{pulse}-> {name}") logging.info(f"{input} -{pulse}-> {name}")
counts[pulse] += 1 counts[pulse] += 1
inputs[name][pulse] += 1 inputs[name][pulse] += 1
if name not in self._modules: if name not in modules:
continue continue
type, outputs = self._modules[name] type, outputs = modules[name]
if type == "broadcaster": if type == "broadcaster":
... ...
@@ -64,27 +77,11 @@ class Solver(BaseSolver):
return counts, inputs return counts, inputs
def solve(self, input: str) -> Iterator[Any]:
self._modules = {}
lines = sys.stdin.read().splitlines()
for line in lines:
name, outputs_s = line.split(" -> ")
outputs = outputs_s.split(", ")
if name == "broadcaster":
self._modules["broadcaster"] = ("broadcaster", outputs)
else:
self._modules[name[1:]] = (
"conjunction" if name.startswith("&") else "flip-flop",
outputs,
)
if self.outputs:
with open("./day20.dot", "w") as fp: with open("./day20.dot", "w") as fp:
fp.write("digraph G {\n") fp.write("digraph G {\n")
fp.write("rx [shape=circle, color=red, style=filled];\n") fp.write("rx [shape=circle, color=red, style=filled];\n")
for name, (type, outputs) in self._modules.items(): for name, (type, outputs) in modules.items():
if type == "conjunction": if type == "conjunction":
shape = "diamond" shape = "diamond"
elif type == "flip-flop": elif type == "flip-flop":
@@ -92,34 +89,29 @@ class Solver(BaseSolver):
else: else:
shape = "circle" shape = "circle"
fp.write(f"{name} [shape={shape}];\n") fp.write(f"{name} [shape={shape}];\n")
for name, (type, outputs) in self._modules.items(): for name, (type, outputs) in modules.items():
for output in outputs: for output in outputs:
fp.write(f"{name} -> {output};\n") fp.write(f"{name} -> {output};\n")
fp.write("}\n") fp.write("}\n")
# part 1 # part 1
flip_flop_states: dict[str, Literal["on", "off"]] = { flip_flop_states: dict[str, Literal["on", "off"]] = {
name: "off" name: "off" for name, (type, _) in modules.items() if type == "flip-flop"
for name, (type, _) in self._modules.items()
if type == "flip-flop"
} }
conjunction_states: dict[str, dict[str, PulseType]] = { conjunction_states: dict[str, dict[str, PulseType]] = {
name: { name: {input: "low" for input, (_, outputs) in modules.items() if name in outputs}
input: "low" for name, (type, _) in modules.items()
for input, (_, outputs) in self._modules.items()
if name in outputs
}
for name, (type, _) in self._modules.items()
if type == "conjunction" if type == "conjunction"
} }
counts: dict[PulseType, int] = {"low": 0, "high": 0} counts: dict[PulseType, int] = {"low": 0, "high": 0}
for _ in range(1000): for _ in range(1000):
result, _ = self._process( result, _ = process(
("button", "broadcaster", "low"), flip_flop_states, conjunction_states ("button", "broadcaster", "low"), flip_flop_states, conjunction_states
) )
for pulse in ("low", "high"): for pulse in ("low", "high"):
counts[pulse] += result[pulse] counts[pulse] += result[pulse]
yield counts["low"] * counts["high"] answer_1 = counts["low"] * counts["high"]
print(f"answer 1 is {answer_1}")
# part 2 # part 2
@@ -132,27 +124,23 @@ class Solver(BaseSolver):
conjunction_states[name][input] = "low" conjunction_states[name][input] = "low"
# find the conjunction connected to rx # find the conjunction connected to rx
to_rx = [ to_rx = [name for name, (_, outputs) in modules.items() if "rx" in outputs]
name for name, (_, outputs) in self._modules.items() if "rx" in outputs
]
assert len(to_rx) == 1, "cannot handle multiple module inputs for rx" assert len(to_rx) == 1, "cannot handle multiple module inputs for rx"
assert ( assert (
self._modules[to_rx[0]][0] == "conjunction" modules[to_rx[0]][0] == "conjunction"
), "can only handle conjunction as input to rx" ), "can only handle conjunction as input to rx"
to_rx_inputs = [ to_rx_inputs = [name for name, (_, outputs) in modules.items() if to_rx[0] in outputs]
name for name, (_, outputs) in self._modules.items() if to_rx[0] in outputs
]
assert all( assert all(
self._modules[i][0] == "conjunction" and len(self._modules[i][1]) == 1 modules[i][0] == "conjunction" and len(modules[i][1]) == 1 for i in to_rx_inputs
for i in to_rx_inputs
), "can only handle inversion as second-order inputs to rx" ), "can only handle inversion as second-order inputs to rx"
count = 1 count = 1
cycles: dict[str, int] = {} cycles: dict[str, int] = {}
second: dict[str, int] = {} second: dict[str, int] = {}
while len(second) != len(to_rx_inputs): while len(second) != len(to_rx_inputs):
_, inputs = self._process( _, inputs = process(
("button", "broadcaster", "low"), flip_flop_states, conjunction_states ("button", "broadcaster", "low"), flip_flop_states, conjunction_states
) )
@@ -169,4 +157,5 @@ class Solver(BaseSolver):
second[k] == cycles[k] * 2 for k in to_rx_inputs second[k] == cycles[k] * 2 for k in to_rx_inputs
), "cannot only handle cycles starting at the beginning" ), "cannot only handle cycles starting at the beginning"
yield lcm(*cycles.values()) answer_2 = lcm(*cycles.values())
print(f"answer 2 is {answer_2}")

View File

@@ -1,6 +1,9 @@
from typing import Any, Iterator import logging
import os
import sys
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
def reachable( def reachable(
@@ -18,29 +21,25 @@ def reachable(
return tiles return tiles
class Solver(BaseSolver): map = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
map = input.splitlines()
start = next( start = next(
(i, j) (i, j) for i in range(len(map)) for j in range(len(map[i])) if map[i][j] == "S"
for i in range(len(map))
for j in range(len(map[i]))
if map[i][j] == "S"
) )
# part 1 # part 1
yield len(reachable(map, {start}, 6 if len(map) < 20 else 64)) answer_1 = len(reachable(map, {start}, 6 if len(map) < 20 else 64))
print(f"answer 1 is {answer_1}")
# part 2 # part 2
# the initial map is a square and contains an empty rhombus whose diameter is # the initial map is a square and contains an empty rhombus whose diameter is the size
# the size of the map, and has only empty cells around the middle row and column # of the map, and has only empty cells around the middle row and column
# #
# after ~n/2 steps, the first map is filled with a rhombus, after that we get a # after ~n/2 steps, the first map is filled with a rhombus, after that we get a bigger
# bigger rhombus every n steps # rhombus every n steps
# #
# we are going to find the number of cells reached for the initial rhombus, n # we are going to find the number of cells reached for the initial rhombus, n steps
# steps after and n * 2 steps after # after and n * 2 steps after
# #
cycle = len(map) cycle = len(map)
rhombus = (len(map) - 3) // 2 + 1 rhombus = (len(map) - 3) // 2 + 1
@@ -50,7 +49,7 @@ class Solver(BaseSolver):
values.append(len(tiles := reachable(map, tiles, cycle))) values.append(len(tiles := reachable(map, tiles, cycle)))
values.append(len(tiles := reachable(map, tiles, cycle))) values.append(len(tiles := reachable(map, tiles, cycle)))
if self.verbose: if logging.root.getEffectiveLevel() == logging.INFO:
n_rows, n_cols = len(map), len(map[0]) n_rows, n_cols = len(map), len(map[0])
rows = [ rows = [
@@ -66,10 +65,10 @@ class Solver(BaseSolver):
if (i // cycle) % 2 == (j // cycle) % 2: if (i // cycle) % 2 == (j // cycle) % 2:
rows[i][j] = f"\033[91m{rows[i][j]}\033[0m" rows[i][j] = f"\033[91m{rows[i][j]}\033[0m"
for row in rows: print("\n".join("".join(row) for row in rows))
self.logger.info("".join(row))
self.logger.info(f"values to fit: {values}")
logging.info(f"values to fit: {values}")
# version 1: # version 1:
# #
@@ -122,8 +121,7 @@ class Solver(BaseSolver):
+ 2 * radius * (radius + 1) // 2 * A + 2 * radius * (radius + 1) // 2 * A
+ 2 * radius * (radius - 1) // 2 * B + 2 * radius * (radius - 1) // 2 * B
+ sum(counts[i][j] for i, j in ((0, 2), (-1, 2), (2, 0), (2, -1))) + sum(counts[i][j] for i, j in ((0, 2), (-1, 2), (2, 0), (2, -1)))
+ sum(counts[i][j] for i, j in ((0, 1), (0, 3), (-1, 1), (-1, 3))) + sum(counts[i][j] for i, j in ((0, 1), (0, 3), (-1, 1), (-1, 3))) * (radius + 1)
* (radius + 1)
+ sum(counts[i][j] for i, j in ((1, 1), (1, 3), (-2, 1), (-2, 3))) * radius + sum(counts[i][j] for i, j in ((1, 1), (1, 3), (-2, 1), (-2, 3))) * radius
) )
print(f"answer 2 (v1) is {answer_2}") print(f"answer 2 (v1) is {answer_2}")
@@ -147,4 +145,5 @@ class Solver(BaseSolver):
a, b, c = (y1 + y3) // 2 - y2, 2 * y2 - (3 * y1 + y3) // 2, y1 a, b, c = (y1 + y3) // 2 - y2, 2 * y2 - (3 * y1 + y3) // 2, y1
n = (26501365 - rhombus) // cycle n = (26501365 - rhombus) // cycle
yield a * n * n + b * n + c answer_2 = a * n * n + b * n + c
print(f"answer 2 (v2) is {answer_2}")

View File

@@ -1,20 +1,23 @@
import itertools import itertools
import logging
import os
import string import string
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
def _name(i: int) -> str: def _name(i: int) -> str:
if len(lines) < 26: if len(lines) < 26:
return string.ascii_uppercase[i] return string.ascii_uppercase[i]
return f"B{i:04d}" return f"B{i:04d}"
def build_supports( def build_supports(
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]], bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]],
) -> tuple[dict[int, set[int]], dict[int, set[int]]]: ) -> tuple[dict[int, set[int]], dict[int, set[int]]]:
@@ -39,9 +42,7 @@ class Solver(BaseSolver):
# 2. compute the bricks that supports any brick # 2. compute the bricks that supports any brick
supported_by: dict[int, set[int]] = {} supported_by: dict[int, set[int]] = {}
supports: dict[int, set[int]] = { supports: dict[int, set[int]] = {i_brick: set() for i_brick in range(len(bricks))}
i_brick: set() for i_brick in range(len(bricks))
}
for i_brick, ((sx, sy, sz), (ex, ey, ez)) in enumerate(bricks): for i_brick, ((sx, sy, sz), (ex, ey, ez)) in enumerate(bricks):
name = _name(i_brick) name = _name(i_brick)
@@ -50,7 +51,7 @@ class Solver(BaseSolver):
for x, y in itertools.product(range(sx, ex + 1), range(sy, ey + 1)) for x, y in itertools.product(range(sx, ex + 1), range(sy, ey + 1))
if (v := levels[x, y, sz - 1]) != -1 if (v := levels[x, y, sz - 1]) != -1
} }
self.logger.info( logging.info(
f"{name} supported by {', '.join(map(_name, supported_by[i_brick]))}" f"{name} supported by {', '.join(map(_name, supported_by[i_brick]))}"
) )
@@ -59,6 +60,7 @@ class Solver(BaseSolver):
return supported_by, supports return supported_by, supports
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]] = [] bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]] = []
for line in lines: for line in lines:
bricks.append( bricks.append(
@@ -73,10 +75,11 @@ class Solver(BaseSolver):
supported_by, supports = build_supports(bricks) supported_by, supports = build_supports(bricks)
# part 1 # part 1
yield len(bricks) - sum( answer_1 = len(bricks) - sum(
any(len(supported_by[supported]) == 1 for supported in supports_to) any(len(supported_by[supported]) == 1 for supported in supports_to)
for supports_to in supports.values() for supports_to in supports.values()
) )
print(f"answer 1 is {answer_1}")
# part 2 # part 2
falling_in_chain: dict[int, set[int]] = {} falling_in_chain: dict[int, set[int]] = {}
@@ -97,13 +100,12 @@ class Solver(BaseSolver):
for d_brick in to_disintegrate: for d_brick in to_disintegrate:
for supported in supports[d_brick]: for supported in supports[d_brick]:
supported_by_copy[supported] = supported_by_copy[supported] - { supported_by_copy[supported] = supported_by_copy[supported] - {d_brick}
d_brick
}
if not supported_by_copy[supported]: if not supported_by_copy[supported]:
to_disintegrate_v.add(supported) to_disintegrate_v.add(supported)
to_disintegrate = to_disintegrate_v to_disintegrate = to_disintegrate_v
yield sum(len(falling) for falling in falling_in_chain.values()) answer_2 = sum(len(falling) for falling in falling_in_chain.values())
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,11 @@
import logging
import os
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator, Literal, Sequence, TypeAlias, cast from typing import Literal, Sequence, TypeAlias, cast
from ..base import BaseSolver VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
DirectionType: TypeAlias = Literal[">", "<", "^", "v", ".", "#"] DirectionType: TypeAlias = Literal[">", "<", "^", "v", ".", "#"]
@@ -31,7 +35,6 @@ def neighbors(
Compute neighbors of the given node, ignoring the given set of nodes and considering Compute neighbors of the given node, ignoring the given set of nodes and considering
that you can go uphill on slopes. that you can go uphill on slopes.
""" """
n_rows, n_cols = len(grid), len(grid[0])
i, j = node i, j = node
for di, dj in Neighbors[grid[i][j]]: for di, dj in Neighbors[grid[i][j]]:
@@ -100,9 +103,7 @@ def compute_direct_links(
return direct return direct
class Solver(BaseSolver):
def longest_path_length( def longest_path_length(
self,
links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]], links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]],
start: tuple[int, int], start: tuple[int, int],
target: tuple[int, int], target: tuple[int, int],
@@ -128,29 +129,29 @@ class Solver(BaseSolver):
if reach not in path if reach not in path
) )
self.logger.info(f"processed {nodes} nodes") logging.info(f"processed {nodes} nodes")
return max_distance return max_distance
def solve(self, input: str) -> Iterator[Any]:
lines = cast(list[Sequence[DirectionType]], input.splitlines())
lines = cast(list[Sequence[DirectionType]], sys.stdin.read().splitlines())
n_rows, n_cols = len(lines), len(lines[0])
start = (0, 1) start = (0, 1)
target = (len(lines) - 1, len(lines[0]) - 2) target = (len(lines) - 1, len(lines[0]) - 2)
direct_links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = { direct_links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = {
start: [reachable(lines, start, target)] start: [reachable(lines, start, target)]
} }
direct_links.update( direct_links.update(compute_direct_links(lines, direct_links[start][0][0], target))
compute_direct_links(lines, direct_links[start][0][0], target)
)
# part 1 # part 1
yield self.longest_path_length(direct_links, start, target) answer_1 = longest_path_length(direct_links, start, target)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
reverse_links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = ( reverse_links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = defaultdict(
defaultdict(list) list
) )
for origin, links in direct_links.items(): for origin, links in direct_links.items():
for destination, distance in links: for destination, distance in links:
@@ -162,4 +163,5 @@ class Solver(BaseSolver):
for k in direct_links.keys() | reverse_links.keys() for k in direct_links.keys() | reverse_links.keys()
} }
yield self.longest_path_length(links, start, target) answer_2 = longest_path_length(links, start, target)
print(f"answer 2 is {answer_2}")

View File

@@ -1,14 +1,9 @@
from typing import Any, Iterator import sys
import numpy as np import numpy as np
from sympy import solve, symbols from sympy import solve, symbols
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
positions = np.array( positions = np.array(
[[int(c) for c in line.split("@")[0].strip().split(", ")] for line in lines] [[int(c) for c in line.split("@")[0].strip().split(", ")] for line in lines]
@@ -18,9 +13,7 @@ class Solver(BaseSolver):
) )
# part 1 # part 1
low, high = ( low, high = [7, 27] if len(positions) <= 10 else [200000000000000, 400000000000000]
[7, 27] if len(positions) <= 10 else [200000000000000, 400000000000000]
)
count = 0 count = 0
for i1, (p1, v1) in enumerate(zip(positions, velocities)): for i1, (p1, v1) in enumerate(zip(positions, velocities)):
@@ -38,7 +31,9 @@ class Solver(BaseSolver):
c = p + np.expand_dims(t, 1) * r c = p + np.expand_dims(t, 1) * r
count += np.all((low <= c) & (c <= high), axis=1).sum() count += np.all((low <= c) & (c <= high), axis=1).sum()
yield count
answer_1 = count
print(f"answer 1 is {answer_1}")
# part 2 # part 2
# equation # equation
@@ -59,10 +54,10 @@ class Solver(BaseSolver):
) )
equations = [] equations = []
for i1, ti in zip(range(n), ts): for i1, ti in zip(range(n), ts):
for p, d, pi, di in zip( for p, d, pi, di in zip((x, y, z), (vx, vy, vz), positions[i1], velocities[i1]):
(x, y, z), (vx, vy, vz), positions[i1], velocities[i1]
):
equations.append(p + ti * d - pi - ti * di) equations.append(p + ti * d - pi - ti * di)
r = solve(equations, [x, y, z, vx, vy, vz] + list(ts), dict=True)[0] r = solve(equations, [x, y, z, vx, vy, vz] + list(ts), dict=True)[0]
yield r[x] + r[y] + r[z]
answer_2 = r[x] + r[y] + r[z]
print(f"answer 2 is {answer_2}")

View File

@@ -1,19 +1,14 @@
# pyright: reportUnknownMemberType=false import sys
from typing import Any, Iterator
import networkx as nx import networkx as nx
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
components = { components = {
(p := line.split(": "))[0]: p[1].split() for line in input.splitlines() (p := line.split(": "))[0]: p[1].split() for line in sys.stdin.read().splitlines()
} }
graph: "nx.Graph[str]" = nx.Graph() targets = {t for c in components for t in components[c] if t not in components}
graph = nx.Graph()
graph.add_edges_from((u, v) for u, vs in components.items() for v in vs) graph.add_edges_from((u, v) for u, vs in components.items() for v in vs)
cut = nx.minimum_edge_cut(graph) cut = nx.minimum_edge_cut(graph)
@@ -22,4 +17,9 @@ class Solver(BaseSolver):
c1, c2 = nx.connected_components(graph) c1, c2 = nx.connected_components(graph)
# part 1 # part 1
yield len(c1) * len(c2) answer_1 = len(c1) * len(c2)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@@ -1,15 +1,10 @@
import string import string
import sys
from collections import defaultdict from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
NOT_A_SYMBOL = "." + string.digits NOT_A_SYMBOL = "." + string.digits
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
values: list[int] = [] values: list[int] = []
gears: dict[tuple[int, int], list[int]] = defaultdict(list) gears: dict[tuple[int, int], list[int]] = defaultdict(list)
@@ -49,5 +44,10 @@ class Solver(BaseSolver):
# continue starting from the end of the number # continue starting from the end of the number
j = k j = k
yield sum(values) # part 1
yield sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values())) answer_1 = sum(values)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values()))
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,5 @@
import sys
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Iterator
from ..base import BaseSolver
@dataclass(frozen=True) @dataclass(frozen=True)
@@ -11,9 +9,7 @@ class Card:
values: list[int] values: list[int]
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
cards: list[Card] = [] cards: list[Card] = []
for line in lines: for line in lines:
@@ -30,7 +26,8 @@ class Solver(BaseSolver):
winnings = [sum(1 for n in card.values if n in card.numbers) for card in cards] winnings = [sum(1 for n in card.values if n in card.numbers) for card in cards]
# part 1 # part 1
yield sum(2 ** (winning - 1) for winning in winnings if winning > 0) answer_1 = sum(2 ** (winning - 1) for winning in winnings if winning > 0)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)} card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)}
@@ -41,4 +38,4 @@ class Solver(BaseSolver):
for j in card2cards[i]: for j in card2cards[i]:
card2values[j] += card2values[i] card2values[j] += card2values[i]
yield sum(card2values.values()) print(f"answer 2 is {sum(card2values.values())}")

View File

@@ -1,6 +1,5 @@
from typing import Any, Iterator, Sequence import sys
from typing import Sequence
from ..base import BaseSolver
MAP_ORDER = [ MAP_ORDER = [
"seed", "seed",
@@ -13,6 +12,55 @@ MAP_ORDER = [
"location", "location",
] ]
lines = sys.stdin.read().splitlines()
# mappings from one category to another, each list contains
# ranges stored as (source, target, length), ordered by start and
# completed to have no "hole"
maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {}
# parsing
index = 2
while index < len(lines):
p1, _, p2 = lines[index].split()[0].split("-")
# extract the existing ranges from the file - we store as (source, target, length)
# whereas the file is in order (target, source, length)
index += 1
values: list[tuple[int, int, int]] = []
while index < len(lines) and lines[index]:
n1, n2, n3 = lines[index].split()
values.append((int(n2), int(n1), int(n3)))
index += 1
# sort by source value
values.sort()
# add a 'fake' interval starting at 0 if missing
if values[0][0] != 0:
values.insert(0, (0, 0, values[0][0]))
# fill gaps between intervals
for i in range(len(values) - 1):
next_start = values[i + 1][0]
end = values[i][0] + values[i][2]
if next_start != end:
values.insert(
i + 1,
(end, end, next_start - end),
)
# add an interval covering values up to at least 2**32 at the end
last_start, _, last_length = values[-1]
values.append((last_start + last_length, last_start + last_length, 2**32))
assert all(v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:]))
assert values[0][0] == 0
assert values[-1][0] + values[-1][-1] >= 2**32
maps[p1, p2] = values
index += 1
def find_range( def find_range(
values: tuple[int, int], map: list[tuple[int, int, int]] values: tuple[int, int], map: list[tuple[int, int, int]]
@@ -63,71 +111,19 @@ def find_range(
return ranges return ranges
class Solver(BaseSolver): def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]:
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# mappings from one category to another, each list contains
# ranges stored as (source, target, length), ordered by start and
# completed to have no "hole"
maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {}
def find_location_ranges(
seeds: Sequence[tuple[int, int]],
) -> Sequence[tuple[int, int]]:
for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]): for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]):
seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])] seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])]
return seeds return seeds
# parsing
index = 2
while index < len(lines):
p1, _, p2 = lines[index].split()[0].split("-")
# extract the existing ranges from the file - we store as (source, target, length)
# whereas the file is in order (target, source, length)
index += 1
values: list[tuple[int, int, int]] = []
while index < len(lines) and lines[index]:
n1, n2, n3 = lines[index].split()
values.append((int(n2), int(n1), int(n3)))
index += 1
# sort by source value
values.sort()
# add a 'fake' interval starting at 0 if missing
if values[0][0] != 0:
values.insert(0, (0, 0, values[0][0]))
# fill gaps between intervals
for i in range(len(values) - 1):
next_start = values[i + 1][0]
end = values[i][0] + values[i][2]
if next_start != end:
values.insert(
i + 1,
(end, end, next_start - end),
)
# add an interval covering values up to at least 2**32 at the end
last_start, _, last_length = values[-1]
values.append((last_start + last_length, last_start + last_length, 2**32))
assert all(
v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:])
)
assert values[0][0] == 0
assert values[-1][0] + values[-1][-1] >= 2**32
maps[p1, p2] = values
index += 1
# part 1 - use find_range() with range of length 1 # part 1 - use find_range() with range of length 1
seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()] seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()]
yield min(start for start, _ in find_location_ranges(seeds_p1)) answer_1 = min(start for start, _ in find_location_ranges(seeds_p1))
print(f"answer 1 is {answer_1}")
# # part 2 # # part 2
parts = lines[0].split(":")[1].strip().split() parts = lines[0].split(":")[1].strip().split()
seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])] seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])]
yield min(start for start, _ in find_location_ranges(seeds_p2)) answer_2 = min(start for start, _ in find_location_ranges(seeds_p2))
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,5 @@
import math import math
from typing import Any, Iterator import sys
from ..base import BaseSolver
def extreme_times_to_beat(time: int, distance: int) -> tuple[int, int]: def extreme_times_to_beat(time: int, distance: int) -> tuple[int, int]:
@@ -27,23 +25,23 @@ def extreme_times_to_beat(time: int, distance: int) -> tuple[int, int]:
return t1, t2 return t1, t2
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# part 1 # part 1
times = list(map(int, lines[0].split()[1:])) times = list(map(int, lines[0].split()[1:]))
distances = list(map(int, lines[1].split()[1:])) distances = list(map(int, lines[1].split()[1:]))
yield math.prod( answer_1 = math.prod(
t2 - t1 + 1 t2 - t1 + 1
for t1, t2 in ( for t1, t2 in (
extreme_times_to_beat(time, distance) extreme_times_to_beat(time, distance)
for time, distance in zip(times, distances) for time, distance in zip(times, distances)
) )
) )
print(f"answer 1 is {answer_1}")
# part 2 # part 2
time = int(lines[0].split(":")[1].strip().replace(" ", "")) time = int(lines[0].split(":")[1].strip().replace(" ", ""))
distance = int(lines[1].split(":")[1].strip().replace(" ", "")) distance = int(lines[1].split(":")[1].strip().replace(" ", ""))
t1, t2 = extreme_times_to_beat(time, distance) t1, t2 = extreme_times_to_beat(time, distance)
yield t2 - t1 + 1 answer_2 = t2 - t1 + 1
print(f"answer 2 is {answer_2}")

View File

@@ -1,7 +1,5 @@
import sys
from collections import Counter, defaultdict from collections import Counter, defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
class HandTypes: class HandTypes:
@@ -34,17 +32,18 @@ def extract_key(hand: str, values: dict[str, int], joker: str = "0") -> tuple[in
) )
class Solver(BaseSolver): lines = sys.stdin.read().splitlines()
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
cards = [(t[0], int(t[1])) for line in lines if (t := line.split())] cards = [(t[0], int(t[1])) for line in lines if (t := line.split())]
# part 1 # part 1
values = {card: value for value, card in enumerate("23456789TJQKA")} values = {card: value for value, card in enumerate("23456789TJQKA")}
cards.sort(key=lambda cv: extract_key(cv[0], values=values)) cards.sort(key=lambda cv: extract_key(cv[0], values=values))
yield sum(rank * value for rank, (_, value) in enumerate(cards, start=1)) answer_1 = sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
print(f"answer 1 is {answer_1}")
# part 2 # part 2
values = {card: value for value, card in enumerate("J23456789TQKA")} values = {card: value for value, card in enumerate("J23456789TQKA")}
cards.sort(key=lambda cv: extract_key(cv[0], values=values, joker="J")) cards.sort(key=lambda cv: extract_key(cv[0], values=values, joker="J"))
yield sum(rank * value for rank, (_, value) in enumerate(cards, start=1)) answer_2 = sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
print(f"answer 2 is {answer_2}")

View File

@@ -1,13 +1,8 @@
import itertools import itertools
import math import math
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
sequence = lines[0] sequence = lines[0]
nodes = { nodes = {
@@ -16,6 +11,7 @@ class Solver(BaseSolver):
if (p := line.split(" = ")) if (p := line.split(" = "))
} }
def path(start: str): def path(start: str):
path = [start] path = [start]
it_seq = iter(itertools.cycle(sequence)) it_seq = iter(itertools.cycle(sequence))
@@ -23,8 +19,11 @@ class Solver(BaseSolver):
path.append(nodes[path[-1]][next(it_seq)]) path.append(nodes[path[-1]][next(it_seq)])
return path return path
# part 1 # part 1
yield len(path(next(node for node in nodes if node.endswith("A")))) - 1 answer_1 = len(path(next(node for node in nodes if node.endswith("A")))) - 1
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield math.lcm(*(len(path(node)) - 1 for node in nodes if node.endswith("A"))) answer_2 = math.lcm(*(len(path(node)) - 1 for node in nodes if node.endswith("A")))
print(f"answer 2 is {answer_2}")

View File

@@ -1,11 +1,6 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
data = [[int(c) for c in line.split()] for line in lines] data = [[int(c) for c in line.split()] for line in lines]
@@ -14,9 +9,7 @@ class Solver(BaseSolver):
for values in data: for values in data:
diffs = [values] diffs = [values]
while any(d != 0 for d in diffs[-1]): while any(d != 0 for d in diffs[-1]):
diffs.append( diffs.append([rhs - lhs for lhs, rhs in zip(diffs[-1][:-1], diffs[-1][1:])])
[rhs - lhs for lhs, rhs in zip(diffs[-1][:-1], diffs[-1][1:])]
)
rhs: list[int] = [0] rhs: list[int] = [0]
lhs: list[int] = [0] lhs: list[int] = [0]
@@ -28,7 +21,9 @@ class Solver(BaseSolver):
left_values.append(lhs[-1]) left_values.append(lhs[-1])
# part 1 # part 1
yield sum(right_values) answer_1 = sum(right_values)
print(f"answer 1 is {answer_1}")
# part 2 # part 2
yield sum(left_values) answer_2 = sum(left_values)
print(f"answer 2 is {answer_2}")

View File

@@ -1,17 +1,14 @@
import sys
from collections import Counter from collections import Counter
from typing import Any, Iterator
from ..base import BaseSolver values = list(map(int, sys.stdin.read().strip().split()))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
values = list(map(int, input.split()))
column_1 = sorted(values[::2]) column_1 = sorted(values[::2])
column_2 = sorted(values[1::2]) column_2 = sorted(values[1::2])
yield sum(abs(v1 - v2) for v1, v2 in zip(column_1, column_2, strict=True))
counter_2 = Counter(column_2) counter_2 = Counter(column_2)
yield sum(value * counter_2.get(value, 0) for value in column_1)
answer_1 = sum(abs(v1 - v2) for v1, v2 in zip(column_1, column_2, strict=True))
answer_2 = sum(value * counter_2.get(value, 0) for value in column_1)
print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,6 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
def is_safe(level: list[int]) -> bool: def is_safe(level: list[int]) -> bool:
diff = [a - b for a, b in zip(level[:-1], level[1:], strict=True)] diff = [a - b for a, b in zip(level[:-1], level[1:], strict=True)]
@@ -12,12 +8,15 @@ class Solver(BaseSolver):
1 <= abs(d) <= 3 for d in diff 1 <= abs(d) <= 3 for d in diff
) )
def is_any_safe(level: list[int]) -> bool: def is_any_safe(level: list[int]) -> bool:
return any( return any(is_safe(level[:i] + level[i + 1 :]) for i in range(0, len(level)))
is_safe(level[:i] + level[i + 1 :]) for i in range(0, len(level))
)
levels = [[int(c) for c in r.split()] for r in input.splitlines()]
yield sum(is_safe(level) for level in levels) levels = [[int(c) for c in r.split()] for r in sys.stdin.read().strip().splitlines()]
yield sum(is_safe(level) or is_any_safe(level) for level in levels)
answer_1 = sum(is_safe(level) for level in levels)
answer_2 = sum(is_safe(level) or is_any_safe(level) for level in levels)
print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,15 +1,13 @@
import re import re
from typing import Any, Iterator import sys
from typing import Iterator
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
def extract_multiply(line: str) -> Iterator[int]: def extract_multiply(line: str) -> Iterator[int]:
for m in re.finditer(r"mul\(([0-9]{1,3}),\s*([0-9]{1,3})\)", line): for m in re.finditer(r"mul\(([0-9]{1,3}),\s*([0-9]{1,3})\)", line):
yield int(m.group(1)) * int(m.group(2)) yield int(m.group(1)) * int(m.group(2))
def valid_memory_blocks(line: str) -> Iterator[str]: def valid_memory_blocks(line: str) -> Iterator[str]:
accumulate = True accumulate = True
while line: while line:
@@ -26,5 +24,11 @@ class Solver(BaseSolver):
else: else:
line = "" line = ""
yield sum(extract_multiply(input))
yield sum(sum(extract_multiply(block)) for block in valid_memory_blocks(input)) line = sys.stdin.read().strip()
answer_1 = sum(extract_multiply(line))
answer_2 = sum(sum(extract_multiply(block)) for block in valid_memory_blocks(line))
print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")

View File

@@ -1,37 +1,10 @@
import itertools as it import sys
from typing import Any, Iterator
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
answer_1 = ...
class Solver(BaseSolver): answer_2 = ...
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
n = len(lines)
yield sum( print(f"answer 1 is {answer_1}")
line.count("XMAS") + line.count("SAMX") print(f"answer 2 is {answer_2}")
for i in range(n)
for ri, rk, ro, ci, ck, cm in (
(1, 0, 0, 0, 1, n),
(0, 1, 0, 1, 0, n),
(0, 1, 0, 1, 1, n - i),
(0, -1, -1, 1, 1, n - i),
(1, 1, 0, 0, 1, n - i if i != 0 else 0),
(-1, -1, -1, 0, 1, n - i if i != 0 else 0),
)
if (
line := "".join(
lines[ri * i + rk * k + ro][ci * i + ck * k] for k in range(cm)
)
)
)
yield sum(
lines[i][j] == "A"
and "".join(
lines[i + di][j + dj] for di, dj in it.product((-1, 1), (-1, 1))
)
in {"MSMS", "SSMM", "MMSS", "SMSM"}
for i, j in it.product(range(1, n - 1), range(1, n - 1))
)

View File

@@ -1,65 +1,10 @@
from collections import defaultdict import sys
from typing import Any, Iterator
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
answer_1 = ...
def in_correct_order(update: list[int], requirements: dict[int, set[int]]) -> bool: answer_2 = ...
return all(
not any(value_2 in requirements[value] for value_2 in update[i_value:])
for i_value, value in enumerate(update)
)
print(f"answer 1 is {answer_1}")
def to_correct_order( print(f"answer 2 is {answer_2}")
update: list[int],
requirements: dict[int, set[int]],
max_update_length: int | None = None,
) -> list[int]:
# copy requirements to update
requirements = {
value: {predecessor for predecessor in predecessors if predecessor in update}
for value, predecessors in requirements.items()
if value in update
}
max_update_length = max_update_length or len(update)
update = []
while requirements and len(update) < max_update_length:
value = next(
value for value, predecessors in requirements.items() if not predecessors
)
update.append(value)
del requirements[value]
for predecessors in requirements.values():
if value in predecessors:
predecessors.remove(value)
return update
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
part1, part2 = input.split("\n\n")
requirements: dict[int, set[int]] = defaultdict(set)
for line in part1.splitlines():
v1, v2 = line.split("|")
requirements[int(v2)].add(int(v1))
updates = [list(map(int, line.split(","))) for line in part2.splitlines()]
yield sum(
update[len(update) // 2]
for update in updates
if in_correct_order(update, requirements)
)
yield sum(
to_correct_order(update, requirements, len(update) // 2 + 1)[-1]
for update in updates
if not in_correct_order(update, requirements)
)

View File

@@ -1,122 +1,10 @@
import itertools as it import sys
from typing import Any, Iterator, TypeAlias
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
NodeType: TypeAlias = tuple[tuple[int, int], tuple[int, int]] answer_1 = ...
EdgesType: TypeAlias = dict[NodeType, tuple[NodeType, set[tuple[int, int]]]]
ROTATE = {(-1, 0): (0, 1), (0, 1): (1, 0), (1, 0): (0, -1), (0, -1): (-1, 0)} answer_2 = ...
START_NODE: NodeType = ((-2, -2), (-1, 0)) print(f"answer 1 is {answer_1}")
FINAL_POS: tuple[int, int] = (-1, -1) print(f"answer 2 is {answer_2}")
def move(
lines: list[str], pos: tuple[int, int], dir: tuple[int, int]
) -> tuple[tuple[int, int] | None, set[tuple[int, int]]]:
n_rows, n_cols = len(lines), len(lines[0])
row, col = pos
marked: set[tuple[int, int]] = set()
final_pos: tuple[int, int] | None = None
while True:
marked.add((row, col))
if not (0 <= row + dir[0] < n_rows and 0 <= col + dir[1] < n_cols):
final_pos = None
break
if lines[row + dir[0]][col + dir[1]] != ".":
final_pos = (row, col)
break
row += dir[0]
col += dir[1]
return final_pos, marked
def compute_graph(lines: list[str], start_node: NodeType):
n_rows, n_cols = len(lines), len(lines[0])
edges: EdgesType = {}
start_pos, start_dir = start_node
end_pos, marked = move(lines, start_pos, start_dir)
assert end_pos is not None
edges[START_NODE] = ((end_pos, start_dir), marked)
for row, col in it.product(range(n_rows), range(n_cols)):
if lines[row][col] != "#":
continue
for start_pos, start_dir in (
((row - 1, col), (1, 0)),
((row + 1, col), (-1, 0)),
((row, col - 1), (0, 1)),
((row, col + 1), (0, -1)),
):
if 0 <= start_pos[0] < n_rows and 0 <= start_pos[1] < n_cols:
end_pos, marked = move(lines, start_pos, ROTATE[start_dir])
edges[start_pos, start_dir] = (
(end_pos or FINAL_POS, ROTATE[start_dir]),
marked,
)
return edges
def is_loop(lines: list[str], edges: EdgesType, position: tuple[int, int]):
row, col = position
current_node = START_NODE
found: set[NodeType] = set()
while current_node[0] != FINAL_POS and current_node not in found:
found.add(current_node)
target_node, edge_marked = edges[current_node]
if (row, col) in edge_marked:
# need to break the edge
target_dir = target_node[1]
end_pos, _ = move(
lines, (row - target_dir[0], col - target_dir[1]), ROTATE[target_dir]
)
current_node = (end_pos or FINAL_POS, ROTATE[target_dir])
else:
current_node = target_node
return current_node in found
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
# read lines
lines = input.splitlines()
# find and delete original position
start_pos = next(
(i, j)
for i, row in enumerate(lines)
for j, col in enumerate(row)
if col == "^"
)
lines[start_pos[0]] = lines[start_pos[0]].replace("^", ".")
# compute edges from the map
edges = compute_graph(lines, (start_pos, (-1, 0)))
# part 1
marked: set[tuple[int, int]] = set()
current_node = START_NODE
while current_node[0] != FINAL_POS:
current_node, current_marked = edges[current_node]
marked = marked.union(current_marked)
yield len(marked)
yield sum(is_loop(lines, edges, pos) for pos in marked if pos != start_pos)

View File

@@ -1,50 +1,10 @@
from typing import Any, Iterator import sys
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
answer_1 = ...
def evaluate( answer_2 = ...
target: int, numbers: list[int], concatenate: bool = False, current: int = 0
) -> bool:
if not numbers:
return current == target
if current > target: print(f"answer 1 is {answer_1}")
return False print(f"answer 2 is {answer_2}")
head, *tail = numbers
if evaluate(target, tail, concatenate, current + head) or evaluate(
target, tail, concatenate, current * head
):
return True
if not concatenate:
return False
return evaluate(target, tail, concatenate, int(str(current) + str(head)))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
targets = {
int(part[0]): list(map(int, part[1].strip().split()))
for line in input.splitlines()
if (part := line.split(":"))
}
yield sum(
target
for target, numbers in self.progress.wrap(
targets.items(), total=len(targets)
)
if evaluate(target, numbers)
)
yield sum(
target
for target, numbers in self.progress.wrap(
targets.items(), total=len(targets)
)
if evaluate(target, numbers, True)
)

View File

@@ -1,76 +1,10 @@
import itertools as it import sys
from collections import defaultdict
from typing import Any, Iterator, cast
from ..base import BaseSolver lines = sys.stdin.read().splitlines()
answer_1 = ...
def compute_antinodes( answer_2 = ...
a1: tuple[int, int],
a2: tuple[int, int],
n_rows: int,
n_cols: int,
min_distance: int = 1,
max_distance: int | None = 1,
):
if a1[0] > a2[0]:
a1, a2 = a2, a1
d_row, d_col = a2[0] - a1[0], a2[1] - a1[1] print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")
points: list[tuple[int, int]] = []
for c in range(min_distance, (max_distance or n_rows) + 1):
row_1, col_1 = a1[0] - c * d_row, a1[1] - c * d_col
row_2, col_2 = a2[0] + c * d_row, a2[1] + c * d_col
valid_1, valid_2 = (
0 <= row_1 < n_rows and 0 <= col_1 < n_cols,
0 <= row_2 < n_rows and 0 <= col_2 < n_cols,
)
if not valid_1 and not valid_2:
break
if valid_1:
points.append((row_1, col_1))
if valid_2:
points.append((row_2, col_2))
return tuple(points)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
n_rows, n_cols = len(lines), len(lines[0])
antennas: dict[str, list[tuple[int, int]]] = defaultdict(list)
for i, j in it.product(range(n_rows), range(n_cols)):
if lines[i][j] != ".":
antennas[lines[i][j]].append((i, j))
yield len(
cast(set[tuple[int, int]], set()).union(
it.chain(
*(
compute_antinodes(a1, a2, n_rows, n_cols)
for antennas_of_frequency in antennas.values()
for a1, a2 in it.permutations(antennas_of_frequency, 2)
)
)
)
)
yield len(
cast(set[tuple[int, int]], set()).union(
it.chain(
*(
compute_antinodes(a1, a2, n_rows, n_cols, 0, None)
for antennas_of_frequency in antennas.values()
for a1, a2 in it.permutations(antennas_of_frequency, 2)
)
)
)
)

View File

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

View File

@@ -1,114 +1,14 @@
import argparse import argparse
import importlib import importlib
import json import os
import logging
import logging.handlers
import sys import sys
from datetime import datetime, timedelta
from pathlib import Path from pathlib import Path
from typing import Any, Iterable, Iterator, Literal, Sequence, TextIO, TypeVar
from tqdm import tqdm
from .base import BaseSolver
_T = TypeVar("_T")
def dump_api_message(
type: Literal["log", "answer", "progress-start", "progress-step", "progress-end"],
content: Any,
file: TextIO = sys.stdout,
):
print(
json.dumps(
{"type": type, "time": datetime.now().isoformat(), "content": content}
),
flush=True,
file=file,
)
class LoggerAPIHandler(logging.Handler):
def __init__(self, output: TextIO = sys.stdout):
super().__init__()
self.output = output
def emit(self, record: logging.LogRecord):
dump_api_message(
"log", {"level": record.levelname, "message": record.getMessage()}
)
class ProgressAPI:
def __init__(
self,
min_step: int = 1,
min_time: timedelta = timedelta(milliseconds=100),
output: TextIO = sys.stdout,
):
super().__init__()
self.counter = 0
self.output = output
self.min_step = min_step
self.min_time = min_time
def wrap(
self, values: Sequence[_T] | Iterable[_T], total: int | None = None
) -> Iterator[_T]:
total = total or len(values) # type: ignore
current = self.counter
self.counter += 1
dump_api_message("progress-start", {"counter": current, "total": total})
try:
percent = 0
time = datetime.now()
for i_value, value in enumerate(values):
yield value
if datetime.now() - time < self.min_time:
continue
time = datetime.now()
c_percent = round(i_value / total * 100)
if c_percent >= percent + self.min_step:
dump_api_message(
"progress-step", {"counter": current, "percent": c_percent}
)
percent = c_percent
finally:
dump_api_message(
"progress-end",
{"counter": current},
)
class ProgressTQDM:
def wrap(
self, values: Sequence[_T] | Iterable[_T], total: int | None = None
) -> Iterator[_T]:
return iter(tqdm(values, total=total))
class ProgressNone:
def wrap(
self, values: Sequence[_T] | Iterable[_T], total: int | None = None
) -> Iterator[_T]:
return iter(values)
def main(): def main():
parser = argparse.ArgumentParser("Holt59 Advent-Of-Code Runner") parser = argparse.ArgumentParser("Holt59 Advent-Of-Code Runner")
parser.add_argument("-v", "--verbose", action="store_true", help="verbose mode") parser.add_argument("-v", "--verbose", action="store_true", help="verbose mode")
parser.add_argument("-t", "--test", action="store_true", help="test mode") parser.add_argument("-t", "--test", action="store_true", help="test mode")
parser.add_argument("-a", "--api", action="store_true", help="API mode")
parser.add_argument( parser.add_argument(
"-u", "--user", type=str, default="holt59", help="user input to use" "-u", "--user", type=str, default="holt59", help="user input to use"
) )
@@ -131,7 +31,6 @@ def main():
args = parser.parse_args() args = parser.parse_args()
verbose: bool = args.verbose verbose: bool = args.verbose
api: bool = args.api
test: bool = args.test test: bool = args.test
stdin: bool = args.stdin stdin: bool = args.stdin
user: str = args.user user: str = args.user
@@ -141,10 +40,8 @@ def main():
day: int = args.day day: int = args.day
# TODO: change this # TODO: change this
logging.basicConfig( if verbose:
level=logging.INFO if verbose or api else logging.WARNING, os.environ["AOC_VERBOSE"] = "True"
handlers=[LoggerAPIHandler()] if api else None,
)
if input_path is None: if input_path is None:
input_path = Path(__file__).parent.joinpath( input_path = Path(__file__).parent.joinpath(
@@ -152,55 +49,11 @@ def main():
) )
assert input_path.exists(), f"{input_path} missing" assert input_path.exists(), f"{input_path} missing"
solver_class: type[BaseSolver] = importlib.import_module(
f".{year}.day{day}", __package__
).Solver
solver = solver_class(
logging.getLogger("AOC"),
verbose=verbose,
year=year,
day=day,
progress=ProgressAPI()
if api
else ProgressTQDM()
if verbose
else ProgressNone(), # type: ignore
outputs=not api,
)
data: str
if stdin: if stdin:
data = sys.stdin.read() importlib.import_module(f".{year}.day{day}", __package__)
else: else:
with open(input_path) as fp: with open(input_path) as fp:
data = fp.read() sys.stdin = fp
importlib.import_module(f".{year}.day{day}", __package__)
start = datetime.now() sys.stdin = sys.__stdin__
last = start
it = solver.solve(data.strip())
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:
dump_api_message(
"answer",
{
"answer": i_answer + 1,
"value": answer,
"answerTime_s": (current - last).total_seconds(),
"totalTime_s": (current - start).total_seconds(),
},
)
else:
print(
f"answer {i_answer + 1} is {answer} (found in {(current - last).total_seconds():.2f}s)"
)
last = current

Some files were not shown because too many files have changed in this diff Show More