Refactor code for API #3

Merged
mikael.capelle merged 13 commits from dev/refactor-for-ui into master 2024-12-08 13:06:42 +00:00
24 changed files with 530 additions and 505 deletions
Showing only changes of commit 377e501d34 - Show all commits

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,13 +1,18 @@
import itertools
import sys
from typing import Any, Iterator
import numpy as np
from numpy.typing import NDArray
grid0 = np.array([[c == "#" for c in line] for line in sys.stdin.read().splitlines()])
from ..base import BaseSolver
# add an always off circle around
grid0 = np.concatenate(
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
grid0 = np.array([[c == "#" for c in line] for line in input.splitlines()])
# add an always off circle around
grid0 = np.concatenate(
[
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
np.concatenate(
@ -20,22 +25,21 @@ grid0 = np.concatenate(
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
],
axis=1,
)
)
moves = list(itertools.product([-1, 0, 1], repeat=2))
moves.remove((0, 0))
moves = list(itertools.product([-1, 0, 1], repeat=2))
moves.remove((0, 0))
jjs, iis = np.meshgrid(
jjs, iis = np.meshgrid(
np.arange(1, grid0.shape[0] - 1, dtype=int),
np.arange(1, grid0.shape[1] - 1, dtype=int),
)
iis, jjs = iis.flatten(), jjs.flatten()
)
iis, jjs = iis.flatten(), jjs.flatten()
ins = iis[:, None] + np.array(moves)[:, 0]
jns = jjs[:, None] + np.array(moves)[:, 1]
ins = iis[:, None] + np.array(moves)[:, 0]
jns = jjs[:, None] + np.array(moves)[:, 1]
def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
neighbors_on = grid[ins, jns].sum(axis=1)
cells_on = grid[iis, jjs]
@ -44,23 +48,19 @@ def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
return grid
grid = grid0
n_steps = 4 if len(grid) < 10 else 100
for _ in range(n_steps):
grid = grid0
n_steps = 4 if len(grid) < 10 else 100
for _ in range(n_steps):
grid = game_of_life(grid)
answer_1 = grid.sum()
print(f"answer 1 is {answer_1}")
yield grid.sum()
n_steps = 5 if len(grid) < 10 else 100
grid = grid0
for _ in range(n_steps):
n_steps = 5 if len(grid) < 10 else 100
grid = grid0
for _ in range(n_steps):
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
grid = game_of_life(grid)
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
answer_2 = sum(cell for line in grid for cell in line)
print(f"answer 2 is {answer_2}")
yield sum(cell for line in grid for cell in line)

View File

@ -1,37 +1,41 @@
import sys
from collections import defaultdict
from typing import Any, Iterator
replacements_s, molecule = sys.stdin.read().split("\n\n")
from ..base import BaseSolver
REPLACEMENTS: dict[str, list[str]] = defaultdict(list)
for replacement_s in replacements_s.splitlines():
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
replacements_s, molecule = input.split("\n\n")
REPLACEMENTS: dict[str, list[str]] = defaultdict(list)
for replacement_s in replacements_s.splitlines():
p = replacement_s.split(" => ")
REPLACEMENTS[p[0]].append(p[1])
molecule = molecule.strip()
molecule = molecule.strip()
generated = [
generated = [
molecule[:i] + replacement + molecule[i + len(symbol) :]
for symbol, replacements in REPLACEMENTS.items()
for replacement in replacements
for i in range(len(molecule))
if molecule[i:].startswith(symbol)
]
]
answer_1 = len(set(generated))
print(f"answer 1 is {answer_1}")
yield len(set(generated))
inversion: dict[str, str] = {
inversion: dict[str, str] = {
replacement: symbol
for symbol, replacements in REPLACEMENTS.items()
for replacement in replacements
}
}
# there is actually only one way to create the molecule, and we can greedily replace
# tokens with their replacements, e.g., if H => OH then we can replace OH by H directly
# without thinking
# there is actually only one way to create the molecule, and we can greedily replace
# tokens with their replacements, e.g., if H => OH then we can replace OH by H directly
# without thinking
count = 0
while molecule != "e":
count = 0
while molecule != "e":
i = 0
m2 = ""
while i < len(molecule):
@ -51,6 +55,4 @@ while molecule != "e":
# print(m2)
molecule = m2
answer_2 = count
print(f"answer 2 is {count}")
yield count

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,11 +1,7 @@
import logging
import operator
import os
import sys
from typing import Callable
from typing import Any, Callable, Iterator
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
from ..base import BaseSolver
OPERATORS = {
"AND": operator.and_,
@ -36,12 +32,27 @@ def value_of(key: str) -> tuple[str, Callable[[dict[str, int]], int]]:
return key, lambda values: values[key]
lines = sys.stdin.read().splitlines()
def process(
signals: Signals,
values: dict[str, int],
) -> dict[str, int]:
while signals:
signal = next(s for s in signals if all(p in values for p in signals[s][0]))
_, deps, command = signals[signal]
values[signal] = command(deps[0](values), deps[1](values)) % 65536
del signals[signal]
signals: Signals = {}
values: dict[str, int] = {"": 0}
return values
for line in lines:
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any] | None:
lines = input.splitlines()
signals: Signals = {}
values: dict[str, int] = {"": 0}
for line in lines:
command, signal = line.split(" -> ")
if command.startswith("NOT"):
@ -77,25 +88,9 @@ for line in lines:
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"]
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}")
yield process(signals.copy(), values | {"b": values_1["a"]})["a"]

View File

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

View File

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

View File

@ -178,7 +178,14 @@ def main():
start = datetime.now()
last = start
for i_answer, answer in enumerate(solver.solve(data.strip())):
it = solver.solve(data.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:

View File

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