15 Commits

Author SHA1 Message Date
Mikaël Capelle
377e501d34 Start fixing 2015 for new API.
Some checks failed
continuous-integration/drone/pr Build is failing
continuous-integration/drone/push Build is failing
2024-12-08 12:13:41 +01:00
Mikaël Capelle
d1f4f5bed0 2024 day 8.
Some checks failed
continuous-integration/drone/push Build is failing
continuous-integration/drone/pr Build is failing
2024-12-08 08:53:36 +01:00
Mikaël Capelle
03a950c485 Force flush API message.
Some checks failed
continuous-integration/drone/push Build is failing
2024-12-07 12:38:02 +01:00
Mikaël Capelle
22b1513271 Fix answer API model.
Some checks failed
continuous-integration/drone/push Build is failing
2024-12-07 12:21:57 +01:00
Mikaël Capelle
1f5b21a13a 2024 day 7.
Some checks failed
continuous-integration/drone/push Build is failing
2024-12-07 09:39:17 +01:00
Mikaël Capelle
8c707c00ba 2024 day 6 new API.
Some checks failed
continuous-integration/drone/push Build is failing
2024-12-06 21:15:18 +01:00
Mikael CAPELLE
ae4f42517c Start adding progress.
Some checks failed
continuous-integration/drone/push Build is failing
2024-12-06 14:39:23 +01:00
Mikael CAPELLE
17432f7ac6 Refactor main entrypoint for UI. 2024-12-06 14:11:35 +01:00
Mikael CAPELLE
664dcfe7ba Refactor 2023 for new system. 2024-12-06 14:11:35 +01:00
Mikael CAPELLE
a9bcf9ef8f Start refactoring code better flexibility. 2024-12-06 14:11:32 +01:00
Mikael CAPELLE
1caf93b38b Refactor 2024 day 6 to be a little bit faster.
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-06 14:10:51 +01:00
Mikaël Capelle
f9a3dee20b 2024 day 6, brute force.
Some checks failed
continuous-integration/drone/push Build is failing
2024-12-06 06:50:35 +01:00
Mikael CAPELLE
1a1ff0c64d Refactor 2024 day 5 without networkx.
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-05 08:28:55 +01:00
Mikaël Capelle
d7621d09b5 2024 day 5.
Some checks failed
continuous-integration/drone/push Build is failing
2024-12-05 07:25:55 +01:00
Mikael CAPELLE
b89d29e880 Refactor 2024 day 4.
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-04 09:31:47 +01:00
85 changed files with 5000 additions and 2081 deletions

16
poetry.lock generated
View File

@@ -1245,6 +1245,20 @@ files = [
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"]
[[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]]
name = "typing-extensions"
version = "4.12.2"
@@ -1281,4 +1295,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "b643261f91a781d77735e05f6d2ac1002867600c2df6393a9d1a15f5e1189109"
content-hash = "c91bc307ff4a5b3e8cd1976ebea211c9749fe09d563dd80861f70ce26826cda9"

View File

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

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,25 +18,23 @@ 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:
u1, gain_or_loose, hap, u2 = cast(
tuple[str, Literal["gain", "lose"], int, str],
parse.parse( # type: ignore
"{} would {} {:d} happiness units by sitting next to {}.", line
),
)
happiness[u1][u2] = hap if gain_or_loose == "gain" else -hap
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
"{} would {} {:d} happiness units by sitting next to {}.", line
),
)
happiness[u1][u2] = hap if gain_or_loose == "gain" else -hap
yield max_change_in_happiness(happiness)
for guest in list(happiness):
happiness["me"][guest] = 0
happiness[guest]["me"] = 0
answer_1 = max_change_in_happiness(happiness)
print(f"answer 1 is {answer_1}")
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,50 +14,50 @@ 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:
reindeer, speed, speed_time, rest_time = cast(
tuple[str, int, int, int],
parse.parse( # type: ignore
"{} can fly {:d} km/s for {:d} seconds, "
"but then must rest for {:d} seconds.",
line,
),
)
reindeers.append(
Reindeer(name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time)
)
reindeers: list[Reindeer] = []
for line in lines:
reindeer, speed, speed_time, rest_time = cast(
tuple[str, int, int, int],
parse.parse( # type: ignore
"{} can fly {:d} km/s for {:d} seconds, "
"but then must rest for {:d} seconds.",
line,
),
)
reindeers.append(
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]] = {
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}
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}
for time in range(target):
for reindeer in reindeers:
if states[reindeer][0] == "flying":
distances[reindeer] += reindeer.speed
for time in self.progress.wrap(range(target)):
for reindeer in reindeers:
if states[reindeer][0] == "flying":
distances[reindeer] += reindeer.speed
top_distance = max(distances.values())
for reindeer in reindeers:
if distances[reindeer] == top_distance:
points[reindeer] += 1
top_distance = max(distances.values())
for reindeer in reindeers:
if distances[reindeer] == top_distance:
points[reindeer] += 1
for reindeer in reindeers:
if states[reindeer][1] == time:
if states[reindeer][0] == "resting":
states[reindeer] = ("flying", time + reindeer.fly_time)
else:
states[reindeer] = ("resting", time + reindeer.rest_time)
for reindeer in reindeers:
if states[reindeer][1] == time:
if states[reindeer][0] == "resting":
states[reindeer] = ("flying", time + reindeer.fly_time)
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,40 +19,38 @@ 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:
_, *scores = cast(
tuple[str, int, int, int, int, int],
parse.parse( # type: ignore
"{}: capacity {:d}, durability {:d}, flavor {:d}, "
"texture {:d}, calories {:d}",
line,
),
)
ingredients.append(scores)
total_teaspoons = 100
calories: list[int] = []
scores: list[int] = []
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)
scores.append(score(ingredients, teaspoons))
calories.append(
sum(
ingredient[-1] * teaspoon
for ingredient, teaspoon in zip(ingredients, teaspoons)
)
ingredients: list[list[int]] = []
for line in lines:
_, *scores = cast(
tuple[str, int, int, int, int, int],
parse.parse( # type: ignore
"{}: capacity {:d}, durability {:d}, flavor {:d}, "
"texture {:d}, calories {:d}",
line,
),
)
ingredients.append(scores)
total_teaspoons = 100
calories: list[int] = []
scores: list[int] = []
answer_1 = max(scores)
print(f"answer 1 is {answer_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)
answer_2 = max(score for score, calory in zip(scores, calories) if calory == 500)
print(f"answer 2 is {answer_2}")
scores.append(score(ingredients, teaspoons))
calories.append(
sum(
ingredient[-1] * teaspoon
for ingredient, teaspoon in zip(ingredients, teaspoons)
)
)
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(
defaultdict(
lambda: op.eq,
trees=op.gt,
cats=op.gt,
pomeranians=op.lt,
goldfish=op.lt,
)
)
print(f"answer 2 is {answer_2}")
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,
),
)

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,66 +1,66 @@
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(
[
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
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((1, grid0.shape[1]), dtype=bool),
grid0,
np.zeros((1, grid0.shape[1]), dtype=bool),
]
),
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
],
axis=1,
)
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
np.concatenate(
[
np.zeros((1, grid0.shape[1]), dtype=bool),
grid0,
np.zeros((1, grid0.shape[1]), dtype=bool),
]
),
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(
np.arange(1, grid0.shape[0] - 1, dtype=int),
np.arange(1, grid0.shape[1] - 1, dtype=int),
)
iis, jjs = iis.flatten(), jjs.flatten()
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()
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_]:
neighbors_on = grid[ins, jns].sum(axis=1)
cells_on = grid[iis, jjs]
def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
neighbors_on = grid[ins, jns].sum(axis=1)
cells_on = grid[iis, jjs]
grid = np.zeros_like(grid)
grid[iis, jjs] = (neighbors_on == 3) | (cells_on & (neighbors_on == 2))
grid = np.zeros_like(grid)
grid[iis, jjs] = (neighbors_on == 3) | (cells_on & (neighbors_on == 2))
return grid
return grid
grid = grid0
n_steps = 4 if len(grid) < 10 else 100
for _ in range(n_steps):
grid = game_of_life(grid)
yield grid.sum()
grid = grid0
n_steps = 4 if len(grid) < 10 else 100
for _ in range(n_steps):
grid = game_of_life(grid)
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)
answer_1 = grid.sum()
print(f"answer 1 is {answer_1}")
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
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
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,56 +1,58 @@
import sys
from collections import defaultdict
from typing import Any, Iterator
replacements_s, molecule = sys.stdin.read().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()
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}")
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
count = 0
while molecule != "e":
i = 0
m2 = ""
while i < len(molecule):
found = False
for replacement in inversion:
if molecule[i:].startswith(replacement):
m2 += inversion[replacement]
i += len(replacement)
count += 1
found = True
break
if not found:
m2 += molecule[i]
i += 1
# print(m2)
molecule = m2
from ..base import BaseSolver
answer_2 = count
print(f"answer 2 is {count}")
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()
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)
]
yield len(set(generated))
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
count = 0
while molecule != "e":
i = 0
m2 = ""
while i < len(molecule):
found = False
for replacement in inversion:
if molecule[i:].startswith(replacement):
m2 += inversion[replacement]
i += len(replacement)
count += 1
found = True
break
if not found:
m2 += molecule[i]
i += 1
# print(m2)
molecule = m2
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(
length * width * height
+ 2 * np.min(np.stack([length + width, length + height, height + width]), axis=0)
)
print(f"answer 2 is {answer_2}")
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
)
)

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,34 +34,31 @@ 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):
if equipments[-1][0] != "" and equipments[-2] == equipments[-1]:
continue
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
cost, player_attack, player_armor = (
sum(equipment[1:][k] for equipment in equipments) for k in range(3)
)
cost, player_attack, player_armor = (
sum(equipment[1:][k] for equipment in equipments) for k in range(3)
)
if ceil(boss_hp / max(1, player_attack - boss_armor)) <= ceil(
player_hp / max(1, boss_attack - player_armor)
):
min_cost = min(cost, min_cost)
else:
max_cost = max(cost, max_cost)
if ceil(boss_hp / max(1, player_attack - boss_armor)) <= ceil(
player_hp / max(1, boss_attack - player_armor)
):
min_cost = min(cost, min_cost)
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(
c
for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, False)
)
print(f"answer 1 is {answer_1}")
yield sum(
c
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,33 +1,32 @@
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:
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
)
ex, ey = ex + 1, ey + 1
match action:
case "turn on":
lights_1[sx:ex, sy:ey] = True
lights_2[sx:ex, sy:ey] += 1
case "turn off":
lights_1[sx:ex, sy:ey] = False
lights_2[sx:ex, sy:ey] = np.maximum(lights_2[sx:ex, sy:ey] - 1, 0)
case "toggle":
lights_1[sx:ex, sy:ey] = ~lights_1[sx:ex, sy:ey]
lights_2[sx:ex, sy:ey] += 2
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
)
ex, ey = ex + 1, ey + 1
answer_1 = lights_1.sum()
print(f"answer 1 is {answer_1}")
match action:
case "turn on":
lights_1[sx:ex, sy:ey] = True
lights_2[sx:ex, sy:ey] += 1
case "turn off":
lights_1[sx:ex, sy:ey] = False
lights_2[sx:ex, sy:ey] = np.maximum(lights_2[sx:ex, sy:ey] - 1, 0)
case "toggle":
lights_1[sx:ex, sy:ey] = ~lights_1[sx:ex, sy:ey]
lights_2[sx:ex, sy:ey] += 2
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,48 +32,6 @@ def value_of(key: str) -> tuple[str, Callable[[dict[str, int]], int]]:
return key, lambda values: values[key]
lines = sys.stdin.read().splitlines()
signals: Signals = {}
values: dict[str, int] = {"": 0}
for line in lines:
command, signal = line.split(" -> ")
if command.startswith("NOT"):
name = command.split(" ")[1]
signals[signal] = (
(name, ""),
(lambda values, _n=name: values[_n], lambda _v: 0),
lambda a, _b: ~a,
)
elif not any(command.find(name) >= 0 for name in OPERATORS):
try:
values[signal] = int(command)
except ValueError:
signals[signal] = (
(command, ""),
(lambda values, _c=command: values[_c], lambda _v: 0),
lambda a, _b: a,
)
else:
op: Callable[[int, int], int] = zero_op
lhs_s, rhs_s = "", ""
for name in OPERATORS:
if command.find(name) >= 0:
op = OPERATORS[name]
lhs_s, rhs_s = command.split(f" {name} ")
break
lhs_s, lhs_fn = value_of(lhs_s)
rhs_s, rhs_fn = value_of(rhs_s)
signals[signal] = ((lhs_s, rhs_s), (lhs_fn, rhs_fn), op)
def process(
signals: Signals,
values: dict[str, int],
@@ -91,11 +45,52 @@ def process(
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}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any] | None:
lines = input.splitlines()
values_2 = process(signals.copy(), values | {"b": values_1["a"]})
answer_2 = values_2["a"]
print(f"answer 2 is {answer_2}")
signals: Signals = {}
values: dict[str, int] = {"": 0}
for line in lines:
command, signal = line.split(" -> ")
if command.startswith("NOT"):
name = command.split(" ")[1]
signals[signal] = (
(name, ""),
(lambda values, _n=name: values[_n], lambda _v: 0),
lambda a, _b: ~a,
)
elif not any(command.find(name) >= 0 for name in OPERATORS):
try:
values[signal] = int(command)
except ValueError:
signals[signal] = (
(command, ""),
(lambda values, _c=command: values[_c], lambda _v: 0),
lambda a, _b: a,
)
else:
op: Callable[[int, int], int] = zero_op
lhs_s, rhs_s = "", ""
for name in OPERATORS:
if command.find(name) >= 0:
op = OPERATORS[name]
lhs_s, rhs_s = command.split(f" {name} ")
break
lhs_s, lhs_fn = value_of(lhs_s)
rhs_s, rhs_fn = value_of(rhs_s)
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"]

View File

@@ -1,35 +1,32 @@
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(
# left and right quotes (not in memory)
2
# each \\ adds one character in the literals (compared to memory)
+ line.count(R"\\")
# each \" adds one character in the literals (compared to memory)
+ line[1:-1].count(R"\"")
# each \xFF adds 3 characters in the literals (compared to memory), but we must not
# count A\\x (A != \), but we must count A\\\x (A != \) - in practice we should also
# 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}")
yield sum(
# left and right quotes (not in memory)
2
# each \\ adds one character in the literals (compared to memory)
+ line.count(R"\\")
# each \" adds one character in the literals (compared to memory)
+ line[1:-1].count(R"\"")
# each \xFF adds 3 characters in the literals (compared to memory), but we must not
# count A\\x (A != \), but we must count A\\\x (A != \) - in practice we should also
# 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
)
answer_2 = sum(
# needs to wrap in quotes (2 characters)
2
# needs to escape every \ with an extra \
+ line.count("\\")
# 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}")
yield sum(
# needs to wrap in quotes (2 characters)
2
# needs to escape every \ with an extra \
+ line.count("\\")
# needs to escape every " with an extra \ (including the first and last ones)
+ line.count('"')
for line in lines
)

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:
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 = {
route: sum(distances[o][d] for o, d in zip(route[:-1], route[1:]))
for route in map(tuple, itertools.permutations(distances))
}
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
answer_1 = min(distance_of_routes.values())
print(f"answer 1 is {answer_1}")
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
answer_2 = max(distance_of_routes.values())
print(f"answer 2 is {answer_2}")
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))
}
yield min(distance_of_routes.values())
yield max(distance_of_routes.values())

View File

@@ -1,90 +1,96 @@
import sys
from typing import Any
from typing import Any, Iterator
import numpy as np
import parse # type: ignore
from numpy.typing import NDArray
def part1(sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int) -> int:
no_beacons_row_l: list[NDArray[np.floating[Any]]] = []
for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by) # closest
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)
no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference(beacons_at_row) # type: ignore
return len(no_beacons_row)
from ..base import BaseSolver
def part2_intervals(
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]:
from tqdm import trange
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]]] = []
for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by) # closest
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)
no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference(
beacons_at_row
) # type: ignore
return len(no_beacons_row)
def part2_intervals(
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]:
for y in self.progress.wrap(range(xy_max + 1)):
its: list[tuple[int, int]] = []
for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by)
dx = d - abs(sy - y)
if dx >= 0:
its.append((max(0, sx - dx), min(sx + dx, xy_max)))
its = sorted(its)
_, e = its[0]
for si, ei in its[1:]:
if si > e + 1:
return si - 1, y, 4_000_000 * (si - 1) + y
if ei > e:
e = ei
return (0, 0, 0)
def part2_cplex(
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]:
from docplex.mp.model import Model
m = Model()
x, y = m.continuous_var_list(2, ub=xy_max, name=["x", "y"])
for y in trange(xy_max + 1):
its: list[tuple[int, int]] = []
for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by)
dx = d - abs(sy - y)
m.add_constraint(
m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}"
) # type: ignore
if dx >= 0:
its.append((max(0, sx - dx), min(sx + dx, xy_max)))
m.set_objective("min", x + y)
its = sorted(its)
_, e = its[0]
s = m.solve()
assert s is not None
for si, ei in its[1:]:
if si > e + 1:
return si - 1, y, 4_000_000 * (si - 1) + y
if ei > e:
e = ei
vx = int(s.get_value(x))
vy = int(s.get_value(y))
return vx, vy, 4_000_000 * vx + vy
return (0, 0, 0)
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {}
def part2_cplex(
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]:
from docplex.mp.model import Model
for line in lines:
r: dict[str, str] = parse.parse( # type: ignore
"Sensor at x={sx}, y={sy}: closest beacon is at x={bx}, y={by}", line
)
sensor_to_beacon[int(r["sx"]), int(r["sy"])] = (int(r["bx"]), int(r["by"]))
m = Model()
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
x, y = m.continuous_var_list(2, ub=xy_max, name=["x", "y"])
yield self.part1(sensor_to_beacon, row)
for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by)
m.add_constraint(m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}") # type: ignore
m.set_objective("min", x + y)
s = m.solve()
assert s is not None
vx = int(s.get_value(x))
vy = int(s.get_value(y))
return vx, vy, 4_000_000 * vx + vy
lines = sys.stdin.read().splitlines()
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {}
for line in lines:
r: dict[str, str] = parse.parse( # type: ignore
"Sensor at x={sx}, y={sy}: closest beacon is at x={bx}, y={by}", line
)
sensor_to_beacon[int(r["sx"]), int(r["sy"])] = (int(r["bx"]), int(r["by"]))
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
print(f"answer 1 is {part1(sensor_to_beacon, row)}")
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
x, y, a2 = part2_intervals(sensor_to_beacon, xy_max)
print(f"answer 2 is {a2} (x={x}, y={y})")
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
x, y, a2 = self.part2_intervals(sensor_to_beacon, xy_max)
self.logger.info("answer 2 is {at} (x={x}, y={y})")
yield a2

View File

@@ -1,27 +1,9 @@
import sys
from typing import Any, Iterator
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",
)
)
}
from ..base import BaseSolver
def find_values(lookups: dict[str, int]) -> list[int]:
def find_values(lines: list[str], lookups: dict[str, int]) -> list[int]:
values: list[int] = []
for line in filter(bool, lines):
@@ -41,5 +23,27 @@ def find_values(lookups: dict[str, int]) -> list[int]:
return values
print(f"answer 1 is {sum(find_values(lookups_1))}")
print(f"answer 2 is {sum(find_values(lookups_2))}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
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,100 +1,100 @@
import os
import sys
from typing import Literal, cast
from typing import Any, Iterator, Literal, cast
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
from ..base import BaseSolver
Symbol = Literal["|", "-", "L", "J", "7", "F", ".", "S"]
lines: list[list[Symbol]] = [
[cast(Symbol, symbol) for symbol in line] for line in sys.stdin.read().splitlines()
]
# find starting point
si, sj = next(
(i, j)
for i in range(len(lines))
for j in range(len(lines[0]))
if lines[i][j] == "S"
)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines: list[list[Symbol]] = [
[cast(Symbol, symbol) for symbol in line] for line in input.splitlines()
]
# find one of the two outputs
ni, nj = si, sj
for ni, nj, chars in (
(si - 1, sj, "|7F"),
(si + 1, sj, "|LJ"),
(si, sj - 1, "-LF"),
(si, sj + 1, "-J7"),
):
if lines[ni][nj] in chars:
break
# find starting point
si, sj = next(
(i, j)
for i in range(len(lines))
for j in range(len(lines[0]))
if lines[i][j] == "S"
)
# part 1 - find the loop (re-used in part 2)
loop = [(si, sj), (ni, nj)]
while True:
pi, pj = loop[-2]
i, j = loop[-1]
# find one of the two outputs
ni, nj = si, sj
for ni, nj, chars in (
(si - 1, sj, "|7F"),
(si + 1, sj, "|LJ"),
(si, sj - 1, "-LF"),
(si, sj + 1, "-J7"),
):
if lines[ni][nj] in chars:
break
sym = lines[i][j]
# part 1 - find the loop (re-used in part 2)
loop = [(si, sj), (ni, nj)]
while True:
pi, pj = loop[-2]
i, j = loop[-1]
if sym == "|" and pi > i or sym in "JL" and pi == i:
i -= 1
elif sym == "|" and pi < i or sym in "7F" and pi == i:
i += 1
elif sym == "-" and pj > j or sym in "J7" and pj == j:
j -= 1
elif sym == "-" and pj < j or sym in "LF" and pj == j:
j += 1
sym = lines[i][j]
if (i, j) == (si, sj):
break
if sym == "|" and pi > i or sym in "JL" and pi == i:
i -= 1
elif sym == "|" and pi < i or sym in "7F" and pi == i:
i += 1
elif sym == "-" and pj > j or sym in "J7" and pj == j:
j -= 1
elif sym == "-" and pj < j or sym in "LF" and pj == j:
j += 1
loop.append((i, j))
answer_1 = len(loop) // 2
print(f"answer 1 is {answer_1}")
# part 2
# replace S by an appropriate character for the loop below
di1, dj1 = loop[1][0] - loop[0][0], loop[1][1] - loop[0][1]
di2, dj2 = loop[0][0] - loop[-1][0], loop[0][1] - loop[-1][1]
mapping: dict[tuple[int, int], dict[tuple[int, int], Symbol]] = {
(0, 1): {(0, 1): "-", (-1, 0): "F", (1, 0): "L"},
(0, -1): {(0, -1): "-", (-1, 0): "7", (1, 0): "J"},
(1, 0): {(1, 0): "|", (0, 1): "7", (0, -1): "F"},
(-1, 0): {(-1, 0): "|", (0, -1): "L", (0, 1): "J"},
}
lines[si][sj] = mapping[di1, dj1][di2, dj2]
# find the points inside the loop using an adaptation of ray casting for a discrete
# grid (https://stackoverflow.com/a/218081/2666289)
#
# use a set for faster '... in loop' check
#
loop_s = set(loop)
inside: set[tuple[int, int]] = set()
for i in range(len(lines)):
cnt = 0
for j in range(len(lines[0])):
if (i, j) not in loop_s and cnt % 2 == 1:
inside.add((i, j))
if (i, j) in loop_s and lines[i][j] in "|LJ":
cnt += 1
if VERBOSE:
for i in range(len(lines)):
for j in range(len(lines[0])):
if (i, j) == (si, sj):
print("\033[91mS\033[0m", end="")
elif (i, j) in loop:
print(lines[i][j], end="")
elif (i, j) in inside:
print("\033[92mI\033[0m", end="")
else:
print(".", end="")
print()
break
answer_2 = len(inside)
print(f"answer 2 is {answer_2}")
loop.append((i, j))
yield len(loop) // 2
# part 2
# replace S by an appropriate character for the loop below
di1, dj1 = loop[1][0] - loop[0][0], loop[1][1] - loop[0][1]
di2, dj2 = loop[0][0] - loop[-1][0], loop[0][1] - loop[-1][1]
mapping: dict[tuple[int, int], dict[tuple[int, int], Symbol]] = {
(0, 1): {(0, 1): "-", (-1, 0): "F", (1, 0): "L"},
(0, -1): {(0, -1): "-", (-1, 0): "7", (1, 0): "J"},
(1, 0): {(1, 0): "|", (0, 1): "7", (0, -1): "F"},
(-1, 0): {(-1, 0): "|", (0, -1): "L", (0, 1): "J"},
}
lines[si][sj] = mapping[di1, dj1][di2, dj2]
# find the points inside the loop using an adaptation of ray casting for a discrete
# grid (https://stackoverflow.com/a/218081/2666289)
#
# use a set for faster '... in loop' check
#
loop_s = set(loop)
inside: set[tuple[int, int]] = set()
for i in range(len(lines)):
cnt = 0
for j in range(len(lines[0])):
if (i, j) not in loop_s and cnt % 2 == 1:
inside.add((i, j))
if (i, j) in loop_s and lines[i][j] in "|LJ":
cnt += 1
if self.verbose:
for i in range(len(lines)):
s = ""
for j in range(len(lines[0])):
if (i, j) == (si, sj):
s += "\033[91mS\033[0m"
elif (i, j) in loop:
s += lines[i][j]
elif (i, j) in inside:
s += "\033[92mI\033[0m"
else:
s += "."
self.logger.info(s)
yield len(inside)

View File

@@ -1,41 +1,42 @@
import sys
from typing import Any, Iterator
import numpy as np
lines = sys.stdin.read().splitlines()
data = np.array([[c == "#" for c in line] for line in lines])
rows = {c for c in range(data.shape[0]) if not data[c, :].any()}
columns = {c for c in range(data.shape[1]) if not data[:, c].any()}
galaxies_y, galaxies_x = np.where(data) # type: ignore
from ..base import BaseSolver
def compute_total_distance(expansion: int) -> int:
distances: list[int] = []
for g1 in range(len(galaxies_y)):
x1, y1 = int(galaxies_x[g1]), int(galaxies_y[g1])
for g2 in range(g1 + 1, len(galaxies_y)):
x2, y2 = int(galaxies_x[g2]), int(galaxies_y[g2])
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
dx = sum(
1 + (expansion - 1) * (x in columns)
for x in range(min(x1, x2), max(x1, x2))
)
dy = sum(
1 + (expansion - 1) * (y in rows)
for y in range(min(y1, y2), max(y1, y2))
)
data = np.array([[c == "#" for c in line] for line in lines])
distances.append(dx + dy)
return sum(distances)
rows = {c for c in range(data.shape[0]) if not data[c, :].any()}
columns = {c for c in range(data.shape[1]) if not data[:, c].any()}
galaxies_y, galaxies_x = np.where(data) # type: ignore
# part 1
answer_1 = compute_total_distance(2)
print(f"answer 1 is {answer_1}")
def compute_total_distance(expansion: int) -> int:
distances: list[int] = []
for g1 in range(len(galaxies_y)):
x1, y1 = int(galaxies_x[g1]), int(galaxies_y[g1])
for g2 in range(g1 + 1, len(galaxies_y)):
x2, y2 = int(galaxies_x[g2]), int(galaxies_y[g2])
# part 2
answer_2 = compute_total_distance(1000000)
print(f"answer 2 is {answer_2}")
dx = sum(
1 + (expansion - 1) * (x in columns)
for x in range(min(x1, x2), max(x1, x2))
)
dy = sum(
1 + (expansion - 1) * (y in rows)
for y in range(min(y1, y2), max(y1, y2))
)
distances.append(dx + dy)
return sum(distances)
# part 1
yield compute_total_distance(2)
# part 2
yield compute_total_distance(1000000)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,13 +1,11 @@
from __future__ import annotations
import heapq
import os
import sys
from collections import defaultdict
from dataclasses import dataclass
from typing import Literal, TypeAlias
from typing import Any, Iterator, Literal, TypeAlias
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
from ..base import BaseSolver
Direction: TypeAlias = Literal[">", "<", "^", "v"]
@@ -32,202 +30,204 @@ MAPPINGS: dict[Direction, tuple[int, int, Direction]] = {
}
def print_shortest_path(
grid: list[list[int]],
target: tuple[int, int],
per_cell: dict[tuple[int, int], list[tuple[Label, int]]],
):
assert len(per_cell[target]) == 1
label = per_cell[target][0][0]
class Solver(BaseSolver):
def print_shortest_path(
self,
grid: list[list[int]],
target: tuple[int, int],
per_cell: dict[tuple[int, int], list[tuple[Label, int]]],
):
assert len(per_cell[target]) == 1
label = per_cell[target][0][0]
path: list[Label] = []
while True:
path.insert(0, label)
if label.parent is None:
break
label = label.parent
path: list[Label] = []
while True:
path.insert(0, label)
if label.parent is None:
break
label = label.parent
p_grid = [[str(c) for c in r] for r in grid]
p_grid = [[str(c) for c in r] for r in grid]
for i in range(len(grid)):
for j in range(len(grid[0])):
if per_cell[i, j]:
p_grid[i][j] = f"\033[94m{grid[i][j]}\033[0m"
for i in range(len(grid)):
for j in range(len(grid[0])):
if per_cell[i, j]:
p_grid[i][j] = f"\033[94m{grid[i][j]}\033[0m"
prev_label = path[0]
for label in path[1:]:
for r in range(
min(prev_label.row, label.row), max(prev_label.row, label.row) + 1
):
for c in range(
min(prev_label.col, label.col),
max(prev_label.col, label.col) + 1,
prev_label = path[0]
for label in path[1:]:
for r in range(
min(prev_label.row, label.row), max(prev_label.row, label.row) + 1
):
if (r, c) != (prev_label.row, prev_label.col):
p_grid[r][c] = f"\033[93m{grid[r][c]}\033[0m"
for c in range(
min(prev_label.col, label.col),
max(prev_label.col, label.col) + 1,
):
if (r, c) != (prev_label.row, prev_label.col):
p_grid[r][c] = f"\033[93m{grid[r][c]}\033[0m"
p_grid[label.row][label.col] = f"\033[91m{grid[label.row][label.col]}\033[0m"
p_grid[label.row][label.col] = (
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"
print("\n".join("".join(row) for row in p_grid))
for row in p_grid:
self.logger.info("".join(row))
def shortest_many_paths(self, grid: list[list[int]]) -> dict[tuple[int, int], int]:
n_rows, n_cols = len(grid), len(grid[0])
def shortest_many_paths(grid: list[list[int]]) -> dict[tuple[int, int], int]:
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]] = {}
queue: list[tuple[int, Label]] = [
(0, Label(row=n_rows - 1, col=n_cols - 1, direction="^", count=0))
]
queue: list[tuple[int, Label]] = [
(0, Label(row=n_rows - 1, col=n_cols - 1, direction="^", count=0))
]
while queue and len(visited) != n_rows * n_cols:
distance, label = heapq.heappop(queue)
while queue and len(visited) != n_rows * n_cols:
distance, label = heapq.heappop(queue)
if (label.row, label.col) in visited:
continue
visited[label.row, label.col] = (label, distance)
for direction, (c_row, c_col, i_direction) in MAPPINGS.items():
if label.direction == i_direction:
continue
else:
row, col = (label.row + c_row, label.col + c_col)
# exclude labels outside the grid or with too many moves in the same
# direction
if row not in range(0, n_rows) or col not in range(0, n_cols):
if (label.row, label.col) in visited:
continue
heapq.heappush(
queue,
(
visited[label.row, label.col] = (label, distance)
for direction, (c_row, c_col, i_direction) in MAPPINGS.items():
if label.direction == i_direction:
continue
else:
row, col = (label.row + c_row, label.col + c_col)
# exclude labels outside the grid or with too many moves in the same
# direction
if row not in range(0, n_rows) or col not in range(0, n_cols):
continue
heapq.heappush(
queue,
(
distance
+ sum(
grid[r][c]
for r in range(min(row, label.row), max(row, label.row) + 1)
for c in range(min(col, label.col), max(col, label.col) + 1)
)
- grid[row][col],
Label(
row=row,
col=col,
direction=direction,
count=0,
parent=label,
),
),
)
return {(r, c): visited[r, c][1] for r in range(n_rows) for c in range(n_cols)}
def shortest_path(
self,
grid: list[list[int]],
min_straight: int,
max_straight: int,
lower_bounds: dict[tuple[int, int], int],
) -> int:
n_rows, n_cols = len(grid), len(grid[0])
target = (len(grid) - 1, len(grid[0]) - 1)
# for each tuple (row, col, direction, count), the associated label when visited
visited: dict[tuple[int, int, str, int], Label] = {}
# list of all visited labels for a cell (with associated distance)
per_cell: dict[tuple[int, int], list[tuple[Label, int]]] = defaultdict(list)
# need to add two start labels, otherwise one of the two possible direction will
# not be possible
queue: list[tuple[int, int, Label]] = [
(lower_bounds[0, 0], 0, Label(row=0, col=0, direction="^", count=0)),
(lower_bounds[0, 0], 0, Label(row=0, col=0, direction="<", count=0)),
]
while queue:
_, distance, label = heapq.heappop(queue)
if (label.row, label.col, label.direction, label.count) in visited:
continue
visited[label.row, label.col, label.direction, label.count] = label
per_cell[label.row, label.col].append((label, distance))
if (label.row, label.col) == target:
break
for direction, (c_row, c_col, i_direction) in MAPPINGS.items():
# cannot move in the opposite direction
if label.direction == i_direction:
continue
# other direction, move 'min_straight' in the new direction
elif label.direction != direction:
row, col, count = (
label.row + min_straight * c_row,
label.col + min_straight * c_col,
min_straight,
)
# same direction, too many count
elif label.count == max_straight:
continue
# same direction, keep going and increment count
else:
row, col, count = (
label.row + c_row,
label.col + c_col,
label.count + 1,
)
# exclude labels outside the grid or with too many moves in the same
# direction
if row not in range(0, n_rows) or col not in range(0, n_cols):
continue
distance_to = (
distance
+ sum(
grid[r][c]
for r in range(min(row, label.row), max(row, label.row) + 1)
for c in range(min(col, label.col), max(col, label.col) + 1)
)
- grid[row][col],
Label(
row=row,
col=col,
direction=direction,
count=0,
parent=label,
- grid[label.row][label.col]
)
heapq.heappush(
queue,
(
distance_to + lower_bounds[row, col],
distance_to,
Label(
row=row,
col=col,
direction=direction,
count=count,
parent=label,
),
),
),
)
return {(r, c): visited[r, c][1] for r in range(n_rows) for c in range(n_cols)}
def shortest_path(
grid: list[list[int]],
min_straight: int,
max_straight: int,
lower_bounds: dict[tuple[int, int], int],
) -> int:
n_rows, n_cols = len(grid), len(grid[0])
target = (len(grid) - 1, len(grid[0]) - 1)
# for each tuple (row, col, direction, count), the associated label when visited
visited: dict[tuple[int, int, str, int], Label] = {}
# list of all visited labels for a cell (with associated distance)
per_cell: dict[tuple[int, int], list[tuple[Label, int]]] = defaultdict(list)
# need to add two start labels, otherwise one of the two possible direction will
# not be possible
queue: list[tuple[int, int, Label]] = [
(lower_bounds[0, 0], 0, Label(row=0, col=0, direction="^", count=0)),
(lower_bounds[0, 0], 0, Label(row=0, col=0, direction="<", count=0)),
]
while queue:
_, distance, label = heapq.heappop(queue)
if (label.row, label.col, label.direction, label.count) in visited:
continue
visited[label.row, label.col, label.direction, label.count] = label
per_cell[label.row, label.col].append((label, distance))
if (label.row, label.col) == target:
break
for direction, (c_row, c_col, i_direction) in MAPPINGS.items():
# cannot move in the opposite direction
if label.direction == i_direction:
continue
# other direction, move 'min_straight' in the new direction
elif label.direction != direction:
row, col, count = (
label.row + min_straight * c_row,
label.col + min_straight * c_col,
min_straight,
)
# same direction, too many count
elif label.count == max_straight:
continue
if self.verbose:
self.print_shortest_path(grid, target, per_cell)
# same direction, keep going and increment count
else:
row, col, count = (
label.row + c_row,
label.col + c_col,
label.count + 1,
)
# exclude labels outside the grid or with too many moves in the same
# direction
if row not in range(0, n_rows) or col not in range(0, n_cols):
continue
return per_cell[target][0][1]
distance_to = (
distance
+ sum(
grid[r][c]
for r in range(min(row, label.row), max(row, label.row) + 1)
for c in range(min(col, label.col), max(col, label.col) + 1)
)
- grid[label.row][label.col]
)
def solve(self, input: str) -> Iterator[Any]:
data = [[int(c) for c in r] for r in input.splitlines()]
estimates = self.shortest_many_paths(data)
heapq.heappush(
queue,
(
distance_to + lower_bounds[row, col],
distance_to,
Label(
row=row,
col=col,
direction=direction,
count=count,
parent=label,
),
),
)
# part 1
yield self.shortest_path(data, 1, 3, lower_bounds=estimates)
if VERBOSE:
print_shortest_path(grid, target, per_cell)
return per_cell[target][0][1]
data = [[int(c) for c in r] for r in sys.stdin.read().splitlines()]
estimates = shortest_many_paths(data)
# part 1
answer_1 = shortest_path(data, 1, 3, lower_bounds=estimates)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = shortest_path(data, 4, 10, lower_bounds=estimates)
print(f"answer 2 is {answer_2}")
# part 2
yield self.shortest_path(data, 4, 10, lower_bounds=estimates)

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,9 +1,6 @@
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
def reachable(
@@ -21,129 +18,133 @@ def reachable(
return tiles
map = sys.stdin.read().splitlines()
start = next(
(i, j) for i in range(len(map)) for j in range(len(map[i])) if map[i][j] == "S"
)
# part 1
answer_1 = len(reachable(map, {start}, 6 if len(map) < 20 else 64))
print(f"answer 1 is {answer_1}")
# part 2
# the initial map is a square and contains an empty rhombus whose diameter is the size
# 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 bigger
# rhombus every n steps
#
# we are going to find the number of cells reached for the initial rhombus, n steps
# after and n * 2 steps after
#
cycle = len(map)
rhombus = (len(map) - 3) // 2 + 1
values: list[int] = []
values.append(len(tiles := reachable(map, {start}, rhombus)))
values.append(len(tiles := reachable(map, tiles, cycle)))
values.append(len(tiles := reachable(map, tiles, cycle)))
if logging.root.getEffectiveLevel() == logging.INFO:
n_rows, n_cols = len(map), len(map[0])
rows = [
[
map[i % n_rows][j % n_cols] if (i, j) not in tiles else "O"
for j in range(-2 * cycle, 3 * cycle)
]
for i in range(-2 * cycle, 3 * cycle)
]
for i in range(len(rows)):
for j in range(len(rows[i])):
if (i // cycle) % 2 == (j // cycle) % 2:
rows[i][j] = f"\033[91m{rows[i][j]}\033[0m"
print("\n".join("".join(row) for row in rows))
logging.info(f"values to fit: {values}")
# version 1:
#
# after 3 cycles, the figure looks like the following:
#
# I M D
# I J A K D
# H A F A L
# C E A K B
# C G B
#
# after 4 cycles, the figure looks like the following:
#
# I M D
# I J A K D
# I J A B A K D
# H A B A B A L
# C E A B A N F
# C E A N F
# C G F
#
# the 'radius' of the rhombus is the number of cycles minus 1
#
# the 4 'corner' (M, H, L, G) are counted once, the blocks with a corner triangle (D, I,
# C, B) are each counted radius times, the blocks with everything but one corner (J, K,
# E, N) are each counted radius - 1 times
#
# there are two versions of the whole block, A and B in the above (or odd and even),
# depending on the number of cycles, either A or B will be in the center
#
counts = [
[
sum(
(i, j) in tiles
for i in range(ci * cycle, (ci + 1) * cycle)
for j in range(cj * cycle, (cj + 1) * cycle)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
map = input.splitlines()
start = next(
(i, j)
for i in range(len(map))
for j in range(len(map[i]))
if map[i][j] == "S"
)
for cj in range(-2, 3)
]
for ci in range(-2, 3)
]
radius = (26501365 - rhombus) // cycle - 1
A = counts[2][2] if radius % 2 == 0 else counts[2][1]
B = counts[2][2] if radius % 2 == 1 else counts[2][1]
answer_2 = (
(radius + 1) * A
+ radius * B
+ 2 * radius * (radius + 1) // 2 * A
+ 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, 1), (0, 3), (-1, 1), (-1, 3))) * (radius + 1)
+ 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}")
# part 1
yield len(reachable(map, {start}, 6 if len(map) < 20 else 64))
# version 2: fitting a polynomial
#
# the value we are interested in (26501365) can be written as R + K * C where R is the
# step at which we find the first rhombus, and K the repeat step, so instead of fitting
# for X values (R, R + K, R + 2 K), we are going to fit for (0, 1, 2), giving us much
# simpler equation for the a, b and c coefficient
#
# we get:
# - (a * 0² + b * 0 + c) = y1 => c = y1
# - (a * 1² + b * 1 + c) = y2 => a + b = y2 - y1
# => b = y2 - y1 - a
# - (a * 2² + b * 2 + c) = y3 => 4a + 2b = y3 - y1
# => 4a + 2(y2 - y1 - a) = y3 - y1
# => a = (y1 + y3) / 2 - y2
#
y1, y2, y3 = values
a, b, c = (y1 + y3) // 2 - y2, 2 * y2 - (3 * y1 + y3) // 2, y1
# part 2
n = (26501365 - rhombus) // cycle
answer_2 = a * n * n + b * n + c
print(f"answer 2 (v2) is {answer_2}")
# the initial map is a square and contains an empty rhombus whose diameter is
# the size 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
# bigger rhombus every n steps
#
# we are going to find the number of cells reached for the initial rhombus, n
# steps after and n * 2 steps after
#
cycle = len(map)
rhombus = (len(map) - 3) // 2 + 1
values: list[int] = []
values.append(len(tiles := reachable(map, {start}, rhombus)))
values.append(len(tiles := reachable(map, tiles, cycle)))
values.append(len(tiles := reachable(map, tiles, cycle)))
if self.verbose:
n_rows, n_cols = len(map), len(map[0])
rows = [
[
map[i % n_rows][j % n_cols] if (i, j) not in tiles else "O"
for j in range(-2 * cycle, 3 * cycle)
]
for i in range(-2 * cycle, 3 * cycle)
]
for i in range(len(rows)):
for j in range(len(rows[i])):
if (i // cycle) % 2 == (j // cycle) % 2:
rows[i][j] = f"\033[91m{rows[i][j]}\033[0m"
for row in rows:
self.logger.info("".join(row))
self.logger.info(f"values to fit: {values}")
# version 1:
#
# after 3 cycles, the figure looks like the following:
#
# I M D
# I J A K D
# H A F A L
# C E A K B
# C G B
#
# after 4 cycles, the figure looks like the following:
#
# I M D
# I J A K D
# I J A B A K D
# H A B A B A L
# C E A B A N F
# C E A N F
# C G F
#
# the 'radius' of the rhombus is the number of cycles minus 1
#
# the 4 'corner' (M, H, L, G) are counted once, the blocks with a corner triangle (D, I,
# C, B) are each counted radius times, the blocks with everything but one corner (J, K,
# E, N) are each counted radius - 1 times
#
# there are two versions of the whole block, A and B in the above (or odd and even),
# depending on the number of cycles, either A or B will be in the center
#
counts = [
[
sum(
(i, j) in tiles
for i in range(ci * cycle, (ci + 1) * cycle)
for j in range(cj * cycle, (cj + 1) * cycle)
)
for cj in range(-2, 3)
]
for ci in range(-2, 3)
]
radius = (26501365 - rhombus) // cycle - 1
A = counts[2][2] if radius % 2 == 0 else counts[2][1]
B = counts[2][2] if radius % 2 == 1 else counts[2][1]
answer_2 = (
(radius + 1) * A
+ radius * B
+ 2 * radius * (radius + 1) // 2 * A
+ 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, 1), (0, 3), (-1, 1), (-1, 3)))
* (radius + 1)
+ 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}")
# version 2: fitting a polynomial
#
# the value we are interested in (26501365) can be written as R + K * C where R is the
# step at which we find the first rhombus, and K the repeat step, so instead of fitting
# for X values (R, R + K, R + 2 K), we are going to fit for (0, 1, 2), giving us much
# simpler equation for the a, b and c coefficient
#
# we get:
# - (a * 0² + b * 0 + c) = y1 => c = y1
# - (a * 1² + b * 1 + c) = y2 => a + b = y2 - y1
# => b = y2 - y1 - a
# - (a * 2² + b * 2 + c) = y3 => 4a + 2b = y3 - y1
# => 4a + 2(y2 - y1 - a) = y3 - y1
# => a = (y1 + y3) / 2 - y2
#
y1, y2, y3 = values
a, b, c = (y1 + y3) // 2 - y2, 2 * y2 - (3 * y1 + y3) // 2, y1
n = (26501365 - rhombus) // cycle
yield a * n * n + b * n + c

View File

@@ -1,111 +1,109 @@
import itertools
import logging
import os
import string
import sys
from collections import defaultdict
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()
def _name(i: int) -> str:
if len(lines) < 26:
return string.ascii_uppercase[i]
return f"B{i:04d}"
def _name(i: int) -> str:
if len(lines) < 26:
return string.ascii_uppercase[i]
return f"B{i:04d}"
def build_supports(
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]],
) -> tuple[dict[int, set[int]], dict[int, set[int]]]:
# 1. compute locations where a brick of sand will land after falling by processing
# them in sorted order of bottom z location
levels: dict[tuple[int, int, int], int] = defaultdict(lambda: -1)
for i_brick, ((sx, sy, sz), (ex, ey, ez)) in enumerate(bricks):
assert sx <= ex and sy <= ey and sz <= ez
xs, ys = range(sx, ex + 1), range(sy, ey + 1)
def build_supports(
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]],
) -> tuple[dict[int, set[int]], dict[int, set[int]]]:
# 1. compute locations where a brick of sand will land after falling by processing
# them in sorted order of bottom z location
levels: dict[tuple[int, int, int], int] = defaultdict(lambda: -1)
for i_brick, ((sx, sy, sz), (ex, ey, ez)) in enumerate(bricks):
assert sx <= ex and sy <= ey and sz <= ez
for z in range(sz - 1, 0, -1):
if any(levels[x, y, z] >= 0 for x, y in itertools.product(xs, ys)):
break
sz, ez = sz - 1, ez - 1
xs, ys = range(sx, ex + 1), range(sy, ey + 1)
bricks[i_brick] = ((sx, sy, sz), (ex, ey, ez))
zs = range(sz, ez + 1)
for z in range(sz - 1, 0, -1):
if any(levels[x, y, z] >= 0 for x, y in itertools.product(xs, ys)):
break
sz, ez = sz - 1, ez - 1
for x, y, z in itertools.product(xs, ys, zs):
levels[x, y, z] = i_brick
bricks[i_brick] = ((sx, sy, sz), (ex, ey, ez))
zs = range(sz, ez + 1)
# 2. compute the bricks that supports any brick
supported_by: dict[int, set[int]] = {}
supports: dict[int, set[int]] = {
i_brick: set() for i_brick in range(len(bricks))
}
for i_brick, ((sx, sy, sz), (ex, ey, ez)) in enumerate(bricks):
name = _name(i_brick)
for x, y, z in itertools.product(xs, ys, zs):
levels[x, y, z] = i_brick
supported_by[i_brick] = {
v
for x, y in itertools.product(range(sx, ex + 1), range(sy, ey + 1))
if (v := levels[x, y, sz - 1]) != -1
}
self.logger.info(
f"{name} supported by {', '.join(map(_name, supported_by[i_brick]))}"
)
# 2. compute the bricks that supports any brick
supported_by: dict[int, set[int]] = {}
supports: dict[int, set[int]] = {i_brick: set() for i_brick in range(len(bricks))}
for i_brick, ((sx, sy, sz), (ex, ey, ez)) in enumerate(bricks):
name = _name(i_brick)
for support in supported_by[i_brick]:
supports[support].add(i_brick)
supported_by[i_brick] = {
v
for x, y in itertools.product(range(sx, ex + 1), range(sy, ey + 1))
if (v := levels[x, y, sz - 1]) != -1
}
logging.info(
f"{name} supported by {', '.join(map(_name, supported_by[i_brick]))}"
return supported_by, supports
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]] = []
for line in lines:
bricks.append(
(
tuple(int(c) for c in line.split("~")[0].split(",")), # type: ignore
tuple(int(c) for c in line.split("~")[1].split(",")), # type: ignore
)
)
# sort bricks by bottom z position to compute supports
bricks = sorted(bricks, key=lambda b: b[0][-1])
supported_by, supports = build_supports(bricks)
# part 1
yield len(bricks) - sum(
any(len(supported_by[supported]) == 1 for supported in supports_to)
for supports_to in supports.values()
)
for support in supported_by[i_brick]:
supports[support].add(i_brick)
# part 2
falling_in_chain: dict[int, set[int]] = {}
for i_brick in range(len(bricks)):
to_disintegrate: set[int] = {
supported
for supported in supports[i_brick]
if len(supported_by[supported]) == 1
}
return supported_by, supports
supported_by_copy = dict(supported_by)
falling_in_chain[i_brick] = set()
while to_disintegrate:
falling_in_chain[i_brick].update(to_disintegrate)
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]] = []
for line in lines:
bricks.append(
(
tuple(int(c) for c in line.split("~")[0].split(",")), # type: ignore
tuple(int(c) for c in line.split("~")[1].split(",")), # type: ignore
)
)
to_disintegrate_v: set[int] = set()
# sort bricks by bottom z position to compute supports
bricks = sorted(bricks, key=lambda b: b[0][-1])
supported_by, supports = build_supports(bricks)
for d_brick in to_disintegrate:
for supported in supports[d_brick]:
supported_by_copy[supported] = supported_by_copy[supported] - {
d_brick
}
# part 1
answer_1 = len(bricks) - sum(
any(len(supported_by[supported]) == 1 for supported in supports_to)
for supports_to in supports.values()
)
print(f"answer 1 is {answer_1}")
if not supported_by_copy[supported]:
to_disintegrate_v.add(supported)
# part 2
falling_in_chain: dict[int, set[int]] = {}
for i_brick in range(len(bricks)):
to_disintegrate: set[int] = {
supported
for supported in supports[i_brick]
if len(supported_by[supported]) == 1
}
to_disintegrate = to_disintegrate_v
supported_by_copy = dict(supported_by)
falling_in_chain[i_brick] = set()
while to_disintegrate:
falling_in_chain[i_brick].update(to_disintegrate)
to_disintegrate_v: set[int] = set()
for d_brick in to_disintegrate:
for supported in supports[d_brick]:
supported_by_copy[supported] = supported_by_copy[supported] - {d_brick}
if not supported_by_copy[supported]:
to_disintegrate_v.add(supported)
to_disintegrate = to_disintegrate_v
answer_2 = sum(len(falling) for falling in falling_in_chain.values())
print(f"answer 2 is {answer_2}")
yield sum(len(falling) for falling in falling_in_chain.values())

View File

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

View File

@@ -1,63 +1,68 @@
import sys
from typing import Any, Iterator
import numpy as np
from sympy import solve, symbols
lines = sys.stdin.read().splitlines()
positions = np.array(
[[int(c) for c in line.split("@")[0].strip().split(", ")] for line in lines]
)
velocities = np.array(
[[int(c) for c in line.split("@")[1].strip().split(", ")] for line in lines]
)
# part 1
low, high = [7, 27] if len(positions) <= 10 else [200000000000000, 400000000000000]
count = 0
for i1, (p1, v1) in enumerate(zip(positions, velocities)):
p, r = p1[:2], v1[:2]
q, s = positions[i1 + 1 :, :2], velocities[i1 + 1 :, :2]
rs = np.cross(r, s)
q, s, rs = q[m := (rs != 0)], s[m], rs[m]
t = np.cross((q - p), s) / rs
u = np.cross((q - p), r) / rs
t, u = t[m := ((t >= 0) & (u >= 0))], u[m]
c = p + np.expand_dims(t, 1) * r
count += np.all((low <= c) & (c <= high), axis=1).sum()
from ..base import BaseSolver
answer_1 = count
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# part 2
# equation
# p1 + t1 * v1 == p0 + t1 * v0
# p2 + t2 * v2 == p0 + t2 * v0
# p3 + t3 * v3 == p0 + t3 * v0
# ...
# pn + tn * vn == p0 + tn * v0
#
positions = np.array(
[[int(c) for c in line.split("@")[0].strip().split(", ")] for line in lines]
)
velocities = np.array(
[[int(c) for c in line.split("@")[1].strip().split(", ")] for line in lines]
)
# we can solve with only 3 lines since each lines contains 3
# equations (x / y / z), so 3 lines give 9 equations and 9
# variables: position (3), velocities (3) and times (3).
n = 3
# part 1
low, high = (
[7, 27] if len(positions) <= 10 else [200000000000000, 400000000000000]
)
x, y, z, vx, vy, vz, *ts = symbols(
"x y z vx vy vz " + " ".join(f"t{i}" for i in range(n + 1))
)
equations = []
for i1, ti in zip(range(n), ts):
for p, d, pi, di in zip((x, y, z), (vx, vy, vz), positions[i1], velocities[i1]):
equations.append(p + ti * d - pi - ti * di)
count = 0
for i1, (p1, v1) in enumerate(zip(positions, velocities)):
p, r = p1[:2], v1[:2]
r = solve(equations, [x, y, z, vx, vy, vz] + list(ts), dict=True)[0]
q, s = positions[i1 + 1 :, :2], velocities[i1 + 1 :, :2]
answer_2 = r[x] + r[y] + r[z]
print(f"answer 2 is {answer_2}")
rs = np.cross(r, s)
q, s, rs = q[m := (rs != 0)], s[m], rs[m]
t = np.cross((q - p), s) / rs
u = np.cross((q - p), r) / rs
t, u = t[m := ((t >= 0) & (u >= 0))], u[m]
c = p + np.expand_dims(t, 1) * r
count += np.all((low <= c) & (c <= high), axis=1).sum()
yield count
# part 2
# equation
# p1 + t1 * v1 == p0 + t1 * v0
# p2 + t2 * v2 == p0 + t2 * v0
# p3 + t3 * v3 == p0 + t3 * v0
# ...
# pn + tn * vn == p0 + tn * v0
#
# we can solve with only 3 lines since each lines contains 3
# equations (x / y / z), so 3 lines give 9 equations and 9
# variables: position (3), velocities (3) and times (3).
n = 3
x, y, z, vx, vy, vz, *ts = symbols(
"x y z vx vy vz " + " ".join(f"t{i}" for i in range(n + 1))
)
equations = []
for i1, ti in zip(range(n), ts):
for p, d, pi, di in zip(
(x, y, z), (vx, vy, vz), positions[i1], velocities[i1]
):
equations.append(p + ti * d - pi - ti * di)
r = solve(equations, [x, y, z, vx, vy, vz] + list(ts), dict=True)[0]
yield r[x] + r[y] + r[z]

View File

@@ -1,25 +1,23 @@
import sys
from typing import Any, Iterator
import networkx as nx
components = {
(p := line.split(": "))[0]: p[1].split() for line in sys.stdin.read().splitlines()
}
from ..base import BaseSolver
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)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
components = {
(p := line.split(": "))[0]: p[1].split() for line in input.splitlines()
}
cut = nx.minimum_edge_cut(graph)
graph.remove_edges_from(cut)
graph: "nx.Graph[str]" = nx.Graph()
graph.add_edges_from((u, v) for u, vs in components.items() for v in vs)
c1, c2 = nx.connected_components(graph)
cut = nx.minimum_edge_cut(graph)
graph.remove_edges_from(cut)
# part 1
answer_1 = len(c1) * len(c2)
print(f"answer 1 is {answer_1}")
c1, c2 = nx.connected_components(graph)
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")
# part 1
yield len(c1) * len(c2)

View File

@@ -1,53 +1,53 @@
import string
import sys
from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
NOT_A_SYMBOL = "." + string.digits
lines = sys.stdin.read().splitlines()
values: list[int] = []
gears: dict[tuple[int, int], list[int]] = defaultdict(list)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
for i, line in enumerate(lines):
j = 0
while j < len(line):
# skip everything until a digit is found (start of a number)
if line[j] not in string.digits:
j += 1
continue
values: list[int] = []
gears: dict[tuple[int, int], list[int]] = defaultdict(list)
# extract the range of the number and its value
k = j + 1
while k < len(line) and line[k] in string.digits:
k += 1
for i, line in enumerate(lines):
j = 0
while j < len(line):
# skip everything until a digit is found (start of a number)
if line[j] not in string.digits:
j += 1
continue
value = int(line[j:k])
# extract the range of the number and its value
k = j + 1
while k < len(line) and line[k] in string.digits:
k += 1
# lookup around the number if there is a symbol - we go through the number
# itself but that should not matter since it only contains digits
found = False
for i2 in range(max(0, i - 1), min(i + 1, len(lines) - 1) + 1):
for j2 in range(max(0, j - 1), min(k, len(line) - 1) + 1):
assert i2 >= 0 and i2 < len(lines)
assert j2 >= 0 and j2 < len(line)
value = int(line[j:k])
if lines[i2][j2] not in NOT_A_SYMBOL:
found = True
# lookup around the number if there is a symbol - we go through the number
# itself but that should not matter since it only contains digits
found = False
for i2 in range(max(0, i - 1), min(i + 1, len(lines) - 1) + 1):
for j2 in range(max(0, j - 1), min(k, len(line) - 1) + 1):
assert i2 >= 0 and i2 < len(lines)
assert j2 >= 0 and j2 < len(line)
if lines[i2][j2] == "*":
gears[i2, j2].append(value)
if lines[i2][j2] not in NOT_A_SYMBOL:
found = True
if found:
values.append(value)
if lines[i2][j2] == "*":
gears[i2, j2].append(value)
# continue starting from the end of the number
j = k
if found:
values.append(value)
# part 1
answer_1 = sum(values)
print(f"answer 1 is {answer_1}")
# continue starting from the end of the number
j = k
# 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}")
yield sum(values)
yield sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values()))

View File

@@ -1,5 +1,7 @@
import sys
from dataclasses import dataclass
from typing import Any, Iterator
from ..base import BaseSolver
@dataclass(frozen=True)
@@ -9,33 +11,34 @@ class Card:
values: list[int]
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
cards: list[Card] = []
for line in lines:
id_part, e_part = line.split(":")
numbers_s, values_s = e_part.split("|")
cards.append(
Card(
id=int(id_part.split()[1]),
numbers=[int(v.strip()) for v in numbers_s.strip().split()],
values=[int(v.strip()) for v in values_s.strip().split()],
)
)
cards: list[Card] = []
for line in lines:
id_part, e_part = line.split(":")
numbers_s, values_s = e_part.split("|")
cards.append(
Card(
id=int(id_part.split()[1]),
numbers=[int(v.strip()) for v in numbers_s.strip().split()],
values=[int(v.strip()) for v in values_s.strip().split()],
)
)
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
answer_1 = sum(2 ** (winning - 1) for winning in winnings if winning > 0)
print(f"answer 1 is {answer_1}")
# part 1
yield sum(2 ** (winning - 1) for winning in winnings if winning > 0)
# part 2
card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)}
card2values = {i: 0 for i in range(len(cards))}
# part 2
card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)}
card2values = {i: 0 for i in range(len(cards))}
for i in range(len(cards)):
card2values[i] += 1
for j in card2cards[i]:
card2values[j] += card2values[i]
for i in range(len(cards)):
card2values[i] += 1
for j in card2cards[i]:
card2values[j] += card2values[i]
print(f"answer 2 is {sum(card2values.values())}")
yield sum(card2values.values())

View File

@@ -1,5 +1,6 @@
import sys
from typing import Sequence
from typing import Any, Iterator, Sequence
from ..base import BaseSolver
MAP_ORDER = [
"seed",
@@ -12,55 +13,6 @@ MAP_ORDER = [
"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(
values: tuple[int, int], map: list[tuple[int, int, int]]
@@ -111,19 +63,71 @@ def find_range(
return ranges
def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]:
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])]
return seeds
class Solver(BaseSolver):
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]]] = {}
# part 1 - use find_range() with range of length 1
seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()]
answer_1 = min(start for start, _ in find_location_ranges(seeds_p1))
print(f"answer 1 is {answer_1}")
def find_location_ranges(
seeds: Sequence[tuple[int, int]],
) -> Sequence[tuple[int, int]]:
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])]
return seeds
# # part 2
parts = lines[0].split(":")[1].strip().split()
seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])]
answer_2 = min(start for start, _ in find_location_ranges(seeds_p2))
print(f"answer 2 is {answer_2}")
# 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
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))
# # part 2
parts = lines[0].split(":")[1].strip().split()
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))

View File

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

View File

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

View File

@@ -1,29 +1,30 @@
import itertools
import math
import sys
from typing import Any, Iterator
lines = sys.stdin.read().splitlines()
sequence = lines[0]
nodes = {
p[0]: {d: n for d, n in zip("LR", p[1].strip("()").split(", "))}
for line in lines[2:]
if (p := line.split(" = "))
}
from ..base import BaseSolver
def path(start: str):
path = [start]
it_seq = iter(itertools.cycle(sequence))
while not path[-1].endswith("Z"):
path.append(nodes[path[-1]][next(it_seq)])
return path
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
sequence = lines[0]
nodes = {
p[0]: {d: n for d, n in zip("LR", p[1].strip("()").split(", "))}
for line in lines[2:]
if (p := line.split(" = "))
}
# part 1
answer_1 = len(path(next(node for node in nodes if node.endswith("A")))) - 1
print(f"answer 1 is {answer_1}")
def path(start: str):
path = [start]
it_seq = iter(itertools.cycle(sequence))
while not path[-1].endswith("Z"):
path.append(nodes[path[-1]][next(it_seq)])
return path
# part 2
answer_2 = math.lcm(*(len(path(node)) - 1 for node in nodes if node.endswith("A")))
print(f"answer 2 is {answer_2}")
# part 1
yield len(path(next(node for node in nodes if node.endswith("A")))) - 1
# part 2
yield math.lcm(*(len(path(node)) - 1 for node in nodes if node.endswith("A")))

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,22 +1,23 @@
import sys
from typing import Any, Iterator
from ..base import BaseSolver
def is_safe(level: list[int]) -> bool:
diff = [a - b for a, b in zip(level[:-1], level[1:], strict=True)]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
def is_safe(level: list[int]) -> bool:
diff = [a - b for a, b in zip(level[:-1], level[1:], strict=True)]
return sum(d > 0 for d in diff) in (0, len(diff)) and all(
1 <= abs(d) <= 3 for d in diff
)
return sum(d > 0 for d in diff) in (0, len(diff)) and all(
1 <= abs(d) <= 3 for d in diff
)
def is_any_safe(level: list[int]) -> bool:
return any(
is_safe(level[:i] + level[i + 1 :]) for i in range(0, len(level))
)
def is_any_safe(level: list[int]) -> bool:
return any(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()]
levels = [[int(c) for c in r.split()] for r in sys.stdin.read().strip().splitlines()]
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}")
yield sum(is_safe(level) for level in levels)
yield sum(is_safe(level) or is_any_safe(level) for level in levels)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,34 +1,30 @@
import re
import sys
from typing import Iterator
from typing import Any, Iterator
from ..base import BaseSolver
def extract_multiply(line: str) -> Iterator[int]:
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))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
def extract_multiply(line: str) -> Iterator[int]:
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))
def valid_memory_blocks(line: str) -> Iterator[str]:
accumulate = True
while line:
if accumulate:
if (dont_i := line.find("don't()")) != -1:
yield line[:dont_i]
line, accumulate = line[dont_i:], False
else:
yield line
line = ""
else:
if (do_i := line.find("do()")) != -1:
line, accumulate = line[do_i:], True
else:
line = ""
def valid_memory_blocks(line: str) -> Iterator[str]:
accumulate = True
while line:
if accumulate:
if (dont_i := line.find("don't()")) != -1:
yield line[:dont_i]
line, accumulate = line[dont_i:], False
else:
yield line
line = ""
else:
if (do_i := line.find("do()")) != -1:
line, accumulate = line[do_i:], True
else:
line = ""
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}")
yield sum(extract_multiply(input))
yield sum(sum(extract_multiply(block)) for block in valid_memory_blocks(input))

View File

@@ -1,46 +1,37 @@
import sys
import itertools as it
from typing import Any, Iterator
import numpy as np
from ..base import BaseSolver
lines = np.array(list(map(list, sys.stdin.read().strip().splitlines())))
print(lines)
n = lines.shape[0]
answer_1 = 0
for i in range(n):
line = "".join(lines[i, :])
answer_1 += line.count("XMAS") + line.count("SAMX")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
n = len(lines)
column = "".join(lines[:, i])
answer_1 += column.count("XMAS") + column.count("SAMX")
diag = "".join(np.diagonal(lines, i))
answer_1 += diag.count("XMAS") + diag.count("SAMX")
diag = "".join(np.diagonal(lines[::-1, :], i))
answer_1 += diag.count("XMAS") + diag.count("SAMX")
if i != 0:
diag = "".join(np.diagonal(lines, -i))
answer_1 += diag.count("XMAS") + diag.count("SAMX")
diag = "".join(np.diagonal(lines[::-1, :], -i))
answer_1 += diag.count("XMAS") + diag.count("SAMX")
answer_2 = sum(
"".join(
(
lines[i - 1, j - 1],
lines[i - 1, j + 1],
lines[i + 1, j - 1],
lines[i + 1, j + 1],
yield sum(
line.count("XMAS") + line.count("SAMX")
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)
)
)
)
)
in {"MSMS", "SSMM", "MMSS", "SMSM"}
for i in range(1, n - 1)
for j in range(1, n - 1)
if lines[i, j] == "A"
)
print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")
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,10 +1,65 @@
import sys
from collections import defaultdict
from typing import Any, Iterator
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
answer_1 = ...
answer_2 = ...
def in_correct_order(update: list[int], requirements: dict[int, set[int]]) -> bool:
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}")
print(f"answer 2 is {answer_2}")
def to_correct_order(
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,10 +1,122 @@
import sys
import itertools as it
from typing import Any, Iterator, TypeAlias
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
answer_1 = ...
NodeType: TypeAlias = tuple[tuple[int, int], tuple[int, int]]
EdgesType: TypeAlias = dict[NodeType, tuple[NodeType, set[tuple[int, int]]]]
answer_2 = ...
ROTATE = {(-1, 0): (0, 1), (0, 1): (1, 0), (1, 0): (0, -1), (0, -1): (-1, 0)}
print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")
START_NODE: NodeType = ((-2, -2), (-1, 0))
FINAL_POS: tuple[int, int] = (-1, -1)
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,10 +1,50 @@
import sys
from typing import Any, Iterator
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
answer_1 = ...
answer_2 = ...
def evaluate(
target: int, numbers: list[int], concatenate: bool = False, current: int = 0
) -> bool:
if not numbers:
return current == target
print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")
if current > target:
return False
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,10 +1,76 @@
import sys
import itertools as it
from collections import defaultdict
from typing import Any, Iterator, cast
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
answer_1 = ...
answer_2 = ...
def compute_antinodes(
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
print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")
d_row, d_col = a2[0] - a1[0], a2[1] - a1[1]
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,10 +1,7 @@
import sys
from typing import Any, Iterator
lines = sys.stdin.read().splitlines()
from ..base import BaseSolver
answer_1 = ...
answer_2 = ...
print(f"answer 1 is {answer_1}")
print(f"answer 2 is {answer_2}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...

View File

@@ -1,14 +1,114 @@
import argparse
import importlib
import os
import json
import logging
import logging.handlers
import sys
from datetime import datetime, timedelta
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():
parser = argparse.ArgumentParser("Holt59 Advent-Of-Code Runner")
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("-a", "--api", action="store_true", help="API mode")
parser.add_argument(
"-u", "--user", type=str, default="holt59", help="user input to use"
)
@@ -31,6 +131,7 @@ def main():
args = parser.parse_args()
verbose: bool = args.verbose
api: bool = args.api
test: bool = args.test
stdin: bool = args.stdin
user: str = args.user
@@ -40,8 +141,10 @@ def main():
day: int = args.day
# TODO: change this
if verbose:
os.environ["AOC_VERBOSE"] = "True"
logging.basicConfig(
level=logging.INFO if verbose or api else logging.WARNING,
handlers=[LoggerAPIHandler()] if api else None,
)
if input_path is None:
input_path = Path(__file__).parent.joinpath(
@@ -49,11 +152,55 @@ def main():
)
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:
importlib.import_module(f".{year}.day{day}", __package__)
data = sys.stdin.read()
else:
with open(input_path) as fp:
sys.stdin = fp
importlib.import_module(f".{year}.day{day}", __package__)
data = fp.read()
sys.stdin = sys.__stdin__
start = datetime.now()
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

37
src/holt59/aoc/base.py Normal file
View File

@@ -0,0 +1,37 @@
from abc import abstractmethod
from logging import Logger
from typing import Any, Final, Iterable, Iterator, Protocol, Sequence, TypeVar, overload
_T = TypeVar("_T")
class ProgressHandler(Protocol):
@overload
def wrap(self, values: Sequence[_T]) -> Iterator[_T]:
...
@overload
def wrap(self, values: Iterable[_T], total: int) -> Iterator[_T]:
...
class BaseSolver:
def __init__(
self,
logger: Logger,
verbose: bool,
year: int,
day: int,
progress: ProgressHandler,
outputs: bool = False,
):
self.logger: Final = logger
self.verbose: Final = verbose
self.year: Final = year
self.day: Final = day
self.progress: Final = progress
self.outputs = outputs
@abstractmethod
def solve(self, input: str) -> Iterator[Any] | None:
...

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,130 @@
..............................#....#..........................................#....................#...........#..##..............
.......#............................#..........#...................................................#...................#........#.
......................................#..................#.................#....................................#...#............#
........#...................#.....................................................................................#...............
.............................#........................#.......................#........#............................#.............
........................##......................................#...............#...................................#.............
...#................#.......................#....#........................#.............................#.........................
..............................................................#............##......#.......#..#.....................#.....#.......
.........................#..#............##.......#.........#....................#.......................##...............#....#..
..............#....................................................................#....#........................................#
.................#............#.....#.....#............................................................#......................#...
.#.................#.................#.....................................#......................................................
...........##..#.......#.........#.#......#..................#.....................#.....#........................................
....................................................#........#........#....................................#......................
........#........#..........#..........................................................#.##.........#..........................#..
...........#............................#............##.............................#...............................#............#
............#......#...............#...........#................................................#.................................
............#..#.#......#............................................................................#.##....#............#.......
................#..............#......#...#........................#.......#.........................#............................
..........................................................................#............#........#......#.#......#.............#...
..........#...#...........................................................#......#..........................................#.....
.......................................#....#..............#...............#....#............#.............#......................
...........#............................................#................................#..............#.........................
..##...............#.................................................................................#.......#.........#.........#
..........#......#......#.........................................#..........#...##.................................#........#....
..........#.......................................................................#..........#...............##.#....#............
....#.............#..#......#..........................................................#......................#...................
.......................................................................#.............##......................#............#.......
............#..#.........#...............................................................................#........................
...............##..#..................#....#....................................................................#......#..........
......#...............................................................................#..#...............#........................
.....#............................................................................#...............#......#........................
#.......................#.........................#...........#....#...#.......#..................................................
.#....................#..............#........................#....................#........###............#.#.#............#.....
.......#...#...................#................................................................................#........##.......
............#.................#.........................................#.....................................#...................
..................#...........#...................................................................................................
......................#.......#...............................#......#...........................#..................#.............
...#..........#......#..........#.....................................................#.....#.................................#...
......#.......#..............................#..............#...............................#...............................#.....
....................................................................#..........#.....#.#...#................................#.....
..............................#...#.....................##.........................................#.#...................#........
........................#....#..............#....................................................................................#
............................#......##...........................#...........#...............................#.....................
.....................................................................................#............................................
#.....................#.................#...#.......##....................#.##.....#.#.......................#....................
....................#............................#.............................................................................#..
........................#.....................................#.............#..............#......................................
...............#....#...................#............................#..#...#.....................................................
.#...........#......#...........##.#...........^.....#........................#..........#.................#......................
...#.##.........#.#......................................#...............#............#......#....................................
.....................................................#......................#.........#.......................#...................
............................................##............#....................#............................#.....................
.............................#.........................##.............#...........................................................
........................................#.............................................................................#...........
.........................................#...............#........#..........................#.#........................#.........
............................................#.................#...............................#...........#.#.......#.............
....................#.............#.........................#..........................#............................#.............
.#.................................#..........#..#.......................................................#........................
..................#..............................................#............##.....#......................................#..#..
.......#...........................................#..................#...........................................................
.#.................................................................#....................................................#.........
...........#.#........#....#...............#.........................................#........................#.........#.........
...............#......#...........#....#....#...............................#..#......#........#.....................#......#.....
.......................................................#.........#.............................#..................#...............
#........................#.........#..........................#................................#...#.....................#.......#
.............................#....................................................................................................
...#..................#....#.............................................................................................#........
..................#........#...............#............................................#..........#..#........................#..
..#...................#..................................#.......#......................#.................................#......#
.........................................#.#......##..........................#.........#..#....................................#.
..........#....#............#.....##....................................................................................#.........
.....#.........##.................................................................................................................
.............................................................................................................#.............#......
...........#.......#..............#...#............#.......................#...#......#.....................................#.....
.......#...................#...........#.....#...#..........................#...........#.........#.............................#.
..............#.....................#..........................................................#..................................
..#............#.................................................#.....#.....................#....#.............................#.
.................#..................................#....#...............................#........................................
.......#..#...........#.#..........##.#.#......................................##.#.....................................#....#....
.....#.#...#......#.............................................................#.........#..#.....................#.............#
.....................................................#..........................................#.......................#.........
........................#....................#................................#......#...........................#......#.........
..........#...........................................................#.....#.#....#...............................#...........#..
........#...............#..............#......#..............................................................#.............#......
...................................................................................................................#..............
.....#..................................................................................##...................#.......#.....#......
...#..#..............#....................................#..#.#..........#.......................................................
#..................#......##.......#..............................................................................................
........................................#....#.........................................................................#..........
#.................................#...................................................#.........#.#...............................
..........................#.....#.#.....................#..........#...................#..#...........................#.....#.....
..........................#..............................#.......#................................................................
.........................#............................#.........#.........#............#............#.#..........................#
.#.#.....................#.....................................#............#.........#..........#.............#.................#
.........#..........................................#.........................................................#...................
..........................................................#.........................#....#...#......#........................#....
.......#.#.........#.............................................#.................#...................#.................#.#......
............#..#........#..#...........#.........................................................#..............#...#.............
..........................#..............#...................................................................#....................
......#.....................#...............#......................................#......#.................#.....................
.........................................................................#.#...........................#..#....#..................
...........#.............................#...#....#...............................................................................
.................................#.....#...............#....#.#.....................................#.........................#...
.#.......#..............#....#..............................................................................#...............##....
.................................#............................#................#...#.........#...................................#
...........#......#.......#..............#.........................#.#.....................................#.......#..............
.....................#....................................#...................#...................................................
...........#...................#.....................................................................................#........#...
..................#...#....................................................................................#..#.........#.........
.............#.................................................#.........#.#.#...............................................#....
..................#............................................#.......................................................#..........
..#......#...........#............................................#...........#............#......#.....................#.........
......#.......................##..........#................................................#...................................#..
.......................................................................#.........#........#....#........................#......#..
..........#...........#...#....................................#.#.............#.#............................#...#.....#.........
.##...............................#......#.....................................................#.........................#......#.
.#.#...#.................................................#......#....................#...........................................#
.........................#..................#.............................#......................................#................
...................................#.......................#..................................#.........#.........................
.........................#...........#...............#........#..................................#...........#..............#.....
...............................#.......................................#.......#.....#.................#....#..............#......
...#...........#.....#................................................#..................#........#.............................#.
..............#........#................................#...#................................#.......#.......#........#........#..
......#....#...................................#............#.........#..#.##..................##....................#......#.....
.....#...........................#................##.............#.#...........................................#..................
#....................................................................#..#......................#..........#.......................
...........#...#..............#.........#....................#.....................#........#.......#...#.#............#..........
.......#..............#........#..##............#...............#.............#.#.......................#.#......#....#...........
....#........#..................#...............#....................#...#...............................#..........#....#..##....

View File

@@ -0,0 +1,850 @@
408407: 40 770 70 8 1
1222314027: 4 40 6 66 2 4 7 8 5 519
79632370: 12 6 79 8 369 1
173138715990: 7 7 6 4 2 692 6 2 8 2 2 45
820782447: 273 593 984 3 5 489 2
6344588: 1 866 33 1 222 50
244480: 1 59 6 86 8 816 45 66
30431590286: 745 872 3 68 6 4 1 442
1178358827: 530 4 7 554 827
5472884572198: 380 5 3 71 85 5 36 94 1
1290391: 1 17 308 11 2
2279613269: 729 6 8 9 177 73 3
2590613018: 29 91 47 433 4 1 5
7528390101668: 17 8 939 47 83 744 71
102796125: 6 8 5 1 3 454 961 2 5
1199: 6 982 208
108411: 9 1 6 98 8 7 12 9 71 36 4
3589: 1 8 15 2 11 24 37
16052: 60 6 2 98 3 45 9 1 3 71 6
2795761: 706 396 1
1307185: 197 7 4 6 8 17 1 9 9 1 55
943: 920 3 20
89520: 75 58 52 3 4 5 16
9492769728: 6 325 2 234 564 8 1 3 9
168032403: 1 89 3 3 465 27 77 17
11444723221: 894 11 90 2 64 3 18
3987348: 45 3 88 61 47 840
399956: 99 897 74 4 72
1724814019: 1 8 6 9 5 36 6 78 585 1 8
484578732: 5 724 15 92 57 97 1
120769: 9 99 55 2 74
27834744915: 4 99 4 5 61 96 7 2 9 12
1105: 852 6 247
449470: 633 710 42
7827644: 220 6 593 4 3
53663904: 9 7 467 19 96
55595: 2 7 3 538 95
3050832153: 6 95 2 49 535 6
8458892982058: 469 938 499 36 1 5 1 7
9820815338177: 98 208 15 338 180
86872304424: 45 288 6 11 93 2 14 2 2
289200: 14 5 2 3 40 2
4683023: 88 4 72 9 7 21 7 42 5 72
1661: 9 2 788 127 652
164507151418: 95 6 1 9 8 8 554 4 1 2 7 9
23684882: 7 486 3 7 696
93392: 1 9 3 348 47
4472531682: 447 2 5 316 82
13106: 6 26 97 127 28 4 1 69
57641397: 12 32 131 13 98
1175: 116 8 7
167479498237: 3 13 62 656 6 6 863
1436391: 53 5 92 4 8 75 8 8 3
3078721: 8 3 91 16 5 4 87 321 9
5580949230: 9 30 6 94 85 9 56 5 9 21
12288573: 527 7 74 1 73 5 4 5 57 9
2525017: 40 801 4 328 78
153888: 43 5 49 9 730 96
8869: 7 612 267 7 2 1
60059346: 873 3 857 8 36 1 41 8
896553742361: 3 838 7 2 5 3 302 7 363
10924696802: 9 101 2 5 22 60 80 6 2
15976400: 2 7 8 8 4 40 3 4 1 50 22 4
930984: 860 9 5 5 3 28 7 48 8 3
255420: 18 22 7 113 495
604546: 529 593 87 5 43
14895576: 7 8 5 7 420 9 8 9 3 762
78330028659: 83 67 87 5 60 2 8 662
2600107133221: 9 46 938 98 79 299
4825632122: 8 668 2 602 1 75 47
1360891: 765 747 9 81 8
5967: 46 5 4 4 68
920353480: 9 4 23 9 751 27 61 62
6197067629: 9 7 195 560 1 878 585
5203411241: 904 5 68 9 3 1 8 94 71
170607390: 2 828 6 92 736 3 24 1
5898240: 41 726 769 5 5
1674650700: 83 7 414 89 505
42447: 424 45 2
59264679614: 9 6 5 264 67 9 537 77
3475000: 3 38 2 7 3 1 999
93749040857: 88 1 9 7 11 342 4 8 57
3502961033286: 21 5 1 5 75 1 67 171 66
87: 5 3 6
435638: 7 2 4 2 804 1 518
8507129374: 98 920 109 1 86
4014337: 2 24 4 7 4 407 2 24 8 6 9
762: 22 8 4 93 6
5207537: 67 9 4 9 49 39 62
494: 6 2 1 38
287567280: 4 8 40 6 95 13 539
861393517: 771 90 39 3 503 10 7
101961: 3 748 45 6 973
3330002: 490 7 67 97 4
10592773: 80 63 996 93 73
21000096: 56 800 72 78 6
6955758: 3 3 83 49 674 1 7 1
901589763: 3 424 914 651 7 3 12 8
8136221: 738 68 7 3 2 28 92 2 1
20737132707: 6 25 6 8 39 3 3 6 8 6 3
16843229: 698 65 25 883 7
5790: 90 7 8 3 9 944 2 3 9 3
557: 53 1 26
41: 5 7 6
46052474: 2 7 74 11 54 31 72 5 9
1232874: 24 3 344 60 5 35 1 1
836: 3 3 5 57 11 198
1318742655: 9 7 10 70 460 9 5 41 79
2213400387345: 372 70 85 387 344
447538349: 3 75 5 5 198 13 45 348
421305: 1 4 21 30 2
475727494328: 5 875 976 84 2 3 81 1 9
522664: 9 87 40 63 8 94 62 5 4
984484419769: 98 448 4 419 7 66
28440766430: 623 70 456 46 432
410257: 9 24 444
6224579: 71 17 573 9 80
2624: 1 8 5 1 64
485102: 70 462 3 5 2
3387672: 833 8 72 486 33 3 5 4
13939339950: 72 6 8 4 233 25 6
36188570398: 7 30 5 21 60 95 177
9450: 86 17 428 5 1
179686799: 57 574 9 8 5 568 2
9857: 70 2 9 9 62 61
101711: 2 7 6 6 3 8 668 45
1514: 19 91 2 400 9
49522: 4 144 332 11 378
20507987520: 512 6 8 4 7 875 19 2
49920136: 1 80 39 160 67 69
15280764: 3 820 40 22 7 732
17959201: 361 5 7 2 22 3 8 1 5 7 2 4
297052: 1 6 5 457 4
1066322: 73 49 8 23 67 324
7987968737287: 496 8 79 553 80 8 8 9 7
3725738880: 631 2 600 36 28 5 984
33867960: 62 8 6 9 9 1 6 78 2 35 1 6
12958044319: 719 882 1 9 3 18 1 9 19
1369536: 1 1 430 3 6 25 266 3 7
520: 7 88 9 5
195279357: 38 3 6 2 6 82 9 5 9 56
644193313: 4 8 3 6 8 140 7 4 4 22 5
337: 9 216 7 99 5
217374327: 96 3 1 6 9 4 37 3 419 57
177168: 1 77 9 1 2 497 22 2 4 3
511826: 945 3 2 80 89 76
271368121049: 92 53 3 1 662 443
121564804: 2 328 5 10 2 9 4 9 4 7 2 2
72253: 7 2 123 87 45
4948000293: 5 173 5 4 7 286 10 2 81
8271: 7 24 8 5 83 592 9 1 39 9
4885180322: 508 87 2 59 8 6 10
597626214290: 8 25 464 58 751 9 41
1153566: 8 393 742 10 565
3488312704: 7 5 424 8 304 8 6 99 3
576841707040: 795 205 403 18 40
22511: 26 1 2 82 5 2 304 887 1
13859: 529 924 526 7 4
2969996810: 3 815 4 2 963 942 806
128767: 1 6 4 8 6 8 20 8 5 1 56
213042: 6 679 321 211 776
54007: 744 2 9 4 433 6
89929: 390 46 33 5 64
11305189: 5 25 4 2 68 94
3580256: 9 49 1 81 59
4143480910: 31 15 5 85 88 903 7
28323629: 2 1 1 7 92 224 8 362 9
1408961706: 8 79 7 3 4 2 111 8 9 704
126793: 939 1 15 2 66 652 213
82831317: 3 380 8 7 245
26737: 7 709 3 6 6 4 4 845
82265531: 7 832 394 552 8
455486995: 6 523 7 9 4 576 19
2450459092: 2 9 9 4 7 5 2 607 10 9 1 1
5222992320: 9 909 60 108 4 364 65
140045802459: 273 7 5 45 802 433 26
170593: 4 2 83 57 6 277
18811455237: 9 4 2 1 145 3 1 4 9 74 1 8
497002: 15 481 97 5 29
11026974: 484 8 5 79 7 6 86 3 6
2379942152: 83 706 94 78 284
569: 81 352 6 81 49
23630: 647 35 17 70 97 54 2
7168521: 6 516 1 82 1 1 11
41597309: 7 33 671 6 195 58
51386: 50 81 5 571
330445804866: 67 4 363 49 793 48 67
797893: 71 3 84 839 52
3952185: 98 99 4 60 653 86
22660416505: 55 412 334 82 508
11666: 33 333 2 596 70 9
39908156: 1 28 5 14 717 98 6
25878142856: 48 77 7 4 2 140 2 856
83584512: 61 2 8 388 44
511729: 730 5 2 52 7
31059736924: 45 69 914 4 592 923 1
129340224: 413 1 751 104 4
499307: 68 9 81 8 33 7 9
8757: 3 5 68 7 69
5438484: 8 13 7 4 2 3 78 63 228
13986: 9 226 190 31 7 804
209498244: 571 69 10 6 2 443
10560: 34 34 51 3 9 159 503 6
529331671: 1 84 63 602 712 2 70
7990488064: 896 80 891 62 2
321138: 4 978 3 1 52 9 57 40 3 9
3767895689: 6 28 95 51 759 599
2315938: 321 6 4 8 5 239 12 6 2 8
3125737510: 96 454 18 84 54 324
10368172359241: 648 5 4 2 154 2 98 4 4 1
445292: 4 1 1 29 5 9 7 207 42 8
133644146231: 44 34 6 5 86 2 5 2 890 7
560709349225: 5 8 7 95 83 82 16 4 166
376533465: 92 9 491 5 371
77130302295: 8 68 19 985 9 435 757
20905: 6 9 3 101 1
814649598: 9 6 307 52 1 4 2 15 1 63
790078389: 89 1 79 790 78 387
89129901: 89 6 6 7 3 2 3 255 661
576: 2 9 14 319 5
2104319: 9 470 2 6 9 5 81 2 2 7 9
2904080: 3 2 19 6 2 9 2 9 481 6 85
29893521: 8 46 9 7 5 3 3 582 9 5 4 6
66945: 6 795 7 81 2
255203011: 1 372 129 1 942 17
2662327666: 7 664 67 230 89 1 662
5357852124: 267 11 78 2 2 3 212 4
103049: 2 3 4 55 8 87 1
14755216: 53 9 3 14 981 52 13 3
257122: 257 118 4
10217900: 417 7 4 70 5
136110748: 272 5 662 445 49
3581: 71 280 6 9
711: 59 18 489 69 76
14580: 85 4 6 47 9 913 5 720 5
12388730941: 9 3 2 6 9 8 389 5 1 846 2
1322760556: 4 4 53 45 8 267 287
1846678542: 13 9 8 7 1 1 66 785 44
1091375766: 9 278 2 38 1 3 2 767
733146778471: 6 2 8 9 8 9 2 751 1 1 97 6
28737222081: 84 394 3 5 64 681
76319806367: 780 5 9 9 9 797 4 9 1 96
46274856: 4 1 804 572 59
44101024: 228 47 6 271 43 16
353939245524: 7 119 1 3 5 3 436 7 7 8 5
22399091925: 297 4 2 5 852 5 3 5 6 44
4086: 2 7 2 74 93
536326: 4 355 49 903 29
93362: 2 5 37 14 851
11038817: 43 66 804 574 82 936
44229148720: 22 5 1 572 813 58 80 4
2982: 99 6 4 6 7
122909973: 3 2 7 9 73 2 1 3 999 3 6
772005: 527 1 8 481 3
52734797555: 2 89 475 9 55 667 1 17
596168243784: 3 16 1 9 2 69 82 43 781
770: 7 5 684 9 2
5956343: 7 46 41 34 2 45 51
873108: 1 5 41 5 1 4 843 9 2 9 8 6
617469: 86 1 40 7 7 62 8
793460: 4 3 1 8 292 785 73 537
146380: 5 28 4 183 664 300
97948555854: 2 8 4 6 5 8 4 4 691 5 1 86
76146205: 7 61 4 620 3
235951: 983 4 4 1 6
4100537553282: 64 94 4 1 8 7 213 3 2 85
35482272: 51 5 397 3 532
13291590297: 9 794 6 31 2 9 4 8 8 7 5 4
1433303694: 73 9 7 512 6 5 32 493
1580045514: 770 6 38 613 9
7160046529: 800 9 745 1 2 6 10 86 2
47470486: 21 55 218 5 5 20 5 6 8 7
2262482802: 707 4 8 82 1 6 4 3 1 1 7 2
702062: 195 9 4 29 35
190725307756: 4 54 7 6 76 79 6 7 756
82460: 303 2 22 9 3
1518239: 983 830 832 574 9
23055889402: 7 4 46 4 2 61 917 8 487
354363961: 484 47 7 89 73 61
2089734: 64 22 69 4 8 3 88
3779961539: 99 98 16 8 487
5458: 8 9 3 3 4 7 8
407677166: 750 4 7 807 6 7 31 4 8 2
39308163: 694 8 8 885 3
126747155: 6 132 154 2 8 251 7
20176891625: 806 276 59 87 907
20085: 5 3 48 998 19 59
418114382: 5 7 48 764 267 91 2
2059684553: 752 9 77 370 95
713077098: 99 8 3 3 2 32 7 7 3 3 80 7
6594374: 22 637 43 7 3
1562987934: 13 6 14 938 7 6 2 89 2
101849060331: 1 3 5 4 1 457 8 1 2 25 9 9
58979170: 5 230 584 6 91 2 8 15 8
1495368078: 98 5 702 516 9 19 4
511346649: 3 4 7 5 215 3 6 11 8 3 8 2
235445568573139: 4 2 4 99 2 554 573 138
7540930176: 6 9 310 8 16 2 68 1
3906210: 23 79 43 3 890
67364136525: 9 6 767 513 82 4 82 4
510557215: 9 64 9 885 97 1 548 69
341079556: 39 32 8 8 81 82 58
38723838: 88 44 3 84 1
8068: 42 43 4 844
22133800: 6 153 1 9 3 343 45 8
1315578: 2 6 1 74 5 3 280 6 6 6 4 3
12239357: 3 770 152 8 26 12 220
354910581706: 77 4 9 851 5 897 6 616
454: 9 16 8 4 2 30 88
1142: 4 72 567 101
9885: 3 95 25 5 8
28615342: 496 41 67 123 21 3 4
1029787: 7 147 7 42 45
13830: 7 6 9 740 2 2 1 124 3 39
2413: 1 2 45 3 5
19963791352: 3 9 30 4 3 2 92 1 927 1 2
22316186966528: 32 199 688 833 832
848710: 6 6 213 569 18 53 2
5844: 7 7 347 978 8
124818070161: 6 9 3 4 3 369 8 4 5 502 9
1528188: 9 806 6 3 1 4 6 26 4 2 4
38255: 42 3 852 1 3
98612039402: 5 892 6 34 7 680 955
28108634550: 593 60 5 500 79 49
5749: 804 89 65 2 3
5983513731: 318 294 220 96 86 8 8
844265928: 882 676 1 2 354 2
4754: 26 181 3 3 42
403: 7 7 1 8 6 19 194 99
20938590: 50 15 5 4 92 19 82 73
223318: 5 5 6 8 9 4 6 5 774 972 9
1996699: 2 5 786 221 247 1 692
24795: 9 908 51 108 23 45
598170468: 3 249 3 4 1 1 2 8 8 6 6
8848672: 442 433 7 2 1
315477: 4 9 9 73 186 995 60
12122640: 20 9 93 94 3 720
275664384: 36 2 8 6 2 8 102 4 153
23372414: 72 380 8 35 9 4 64
7008001: 936 7 92 838 6 8 4
37585141: 375 35 49 61 53 2
1514: 1 1 1 8 84
5460480346: 6 28 4 80 6 18 346
3541188: 843 2 5 1 838 2 8 621 7
4949142: 72 3 29 7 79 26
845: 5 92 7 4 374
4422080: 51 161 293 8 65
13000: 3 216 9 2 22
16076280: 5 8 39 69 120
25992255181724: 69 1 6 73 879 271 6 97
42367599: 10 2 6 1 5 8 6 3 370 9 6
377666: 327 613 4 4 59 7
612575190949: 6 7 9 1 4 7 4 3 9 10 9 949
215605: 53 3 5 77 8
25732162: 4 975 68 78 2 97
141265153: 397 93 71 50 3
4832698: 6 2 64 864 700
1799: 561 2 3 671 3
614467: 176 451 980 8
2469943: 382 7 869 5 8 2 8 7 49
18191713494361: 9 8 642 3 769 571 35 4
434950474717: 7 7 2 41 24 661 34 718
45990013: 40 771 5 213 15 7 13
4130365516679: 90 1 82 653 458 92 81
37308684: 79 198 561 8 9 49 83
1628: 9 41 4 81 70
22675440: 5 327 7 6 5 5 66 4 72 4 6
93431688605: 93 43 168 860 8
16157: 55 4 484 15 590
116564580: 9 1 3 5 5 8 9 5 3 99 5 68
237636110: 410 644 9 67 43
76969775: 6 3 3 580 2 72 713
218447713: 2 8 78 45 2 714
315041383: 28 6 7 69 4 3 2 196 37
47840846537: 6 7 6 541 207 89 1 39
988503654: 28 70 781 7 62 365 4
116232654: 19 372 6 643 9
111735066: 9 3 8 8 8 68 8 524 7 3 4 2
1817575499: 861 823 3 2 26 415 1
63390696446: 4 65 6 5 9 28 1 9 64 43 1
738580012: 29 4 82 322 6 6 9 61
1242147: 7 5 40 661 683 800
43738589966: 4 373 85 8 90 8 5 2 9 1 4
139972: 98 2 91 14 882 7
154495527: 14 11 4 954 6 6 7
51988: 1 518 2 6 6
498078: 5 1 568 7 85 88
8471: 3 172 5 17 43
1098720: 41 71 7 5 117 3 3 88 90
3220993: 590 954 66 2 991
206545581: 6 20 78 6 55 89 4 9 8 4
7841176: 4 36 196 699 476
5646390883: 467 490 59 90 883
3654642584: 9 396 90 58 6 256 2 5
130942625902: 23 5 4 8 2 6 995 5 91
22045656: 1 764 81 42 356
627478410: 742 57 8 845
603: 4 596 4
4957495: 77 34 641
13595839975496: 2 52 93 9 936 8 7 1 376
761098063: 69 9 871 30 5 980 63
131724535: 58 52 72 697 721
3159: 9 6 6 9 4 601 385 5 7 1
150715356: 2 6 66 4 6 5 43 328 6 6 6
3652185: 3 71 68 7 7 9 4 86 6 361
45630261: 9 994 38 73 51
1259546885792: 419 136 4 2 1 477 63
77263789755: 7 7 438 2 3 3 829 6 8 6
6312980161295: 99 9 920 5 4 3 4 954 6
874: 6 810 56
206794512427: 1 2 5 1 4 7 3 4 20 1 402 5
4241075177959: 29 914 71 9 86 4 8 2 8 4
1268233188: 6 4 78 4 7 2 5 1 8 7 7 244
254643: 3 391 646 49 67
57552457531: 793 827 5 5 29 33
243360: 45 766 20 3
21575: 1 17 1 63 16 2
4788137: 969 61 755 1 49 7
551226080: 491 924 243 4 5
54389104: 24 682 3 52 77
174052800193: 6 8 8 1 4 5 9 4 6 34 4 196
2029492: 3 245 7 547 5 47 74
31953: 7 99 14 3 110
15969771: 1 5 7 3 38 8 243 3 6 3 4 2
6419328101: 8 5 118 2 640 68 1 23 8
263669759: 9 3 86 768 672 8 3 24 4
7732933: 721 3 3 7 1 85 47 1 3 6 7
13049008058275: 4 16 9 626 7 6 5 582 7 5
582119: 9 572 17 4 866 72 8
267454667: 5 57 4 8 25 5 3 61 1 5 1 7
6499467: 3 4 5 3 80 8 8 40 4 8 2 95
78041738: 859 9 73 1 740
1799761: 1 4 3 4 65 5 4 5 6 1 61
581955071: 726 49 57 287 2
430510057: 643 670 37 5 89 999
3219088050: 208 28 1 31 23 5 5 762
38129143: 7 2 37 8 1 92 5 2 36 4 6
13920902: 46 301 67 35 4 97 7
134581504: 4 94 979 357 1
3215357: 2 782 8 425 354
923: 27 890 2 4
1211518464: 8 173 56 5 4 6 4 9 6 8 83
12704960836: 2 16 1 252 1 486 7 4 35
439941006: 47 8 8 2 7 1 20 5 2 33 4 9
7777528: 8 903 4 85 28
121083382: 4 74 764 8 72 3 17
9958: 7 88 452 752 5
34561803: 5 6 94 13 422 1 6
10334766686: 492 7 922 3 6 87
150289: 1 879 66 2 77 4 366
580552: 579 531 2 28 989
7410837: 340 7 562 8 38
18540512: 3 610 1 6 267 7 8 6 7 3 2
46119777: 28 854 581 10 9
461179926912: 1 2 593 223 762 913
5714938: 98 2 6 967 635 4 5 9 8 5
5550: 26 909 5 794 81
8394624803: 5 84 131 2 2 18 2 803
1399: 5 2 290 90 4 3 205 9 3 1
341160006: 2 5 4 34 737 138 7 40
11205967: 87 447 916 42 2 184
131378720351: 4 62 27 6 4 2 314 781
16932035756: 7 6 330 4 354 2 7 9 1 76
720: 14 1 6 71 629
2282737137: 19 2 67 343 163 1 54
2813767: 4 163 6 117 6 3 8 1 4 1 2
3068463: 51 6 8 461
1176192: 1 1 3 83 9 631 325 17
136012: 8 889 17 5 4 37
5879975453: 4 1 5 4 7 359 6 1 37 4 5 3
491608106: 8 83 18 330 3 280 909
1307268678: 2 32 81 25 2 9 221 457
12671: 370 3 76 4 1 806
8029550: 75 76 48 603 976
1410706997550: 653 1 9 452 6 15 8 4 5 6
3916639: 3 1 9 4 6 817 258 5 6 65
11530851: 5 24 2 5 7 9 549 7 2 741
115721: 96 5 10 1 533 938 2
98385277: 4 4 53 476 909 276
10133: 45 6 17 76 7 5 46 3
41756: 7 5 69 7 50 6
2144: 7 7 98 67 2
1446603: 6 63 72 63 8 886 651
139819152: 744 9 44 5 422
3828: 335 275 8 4 4 3 5 6 7 8 9
129353136666: 41 478 96 66 631 36
3438094: 343 269 253 281 8 53
7326: 18 9 15 2 18
15617004: 22 2 9 302 679 7
30917: 2 593 26 76 8
6092883: 7 2 7 6 9 1 34 1 64 7 6 8
551603000: 52 9 5 8 6 543 6 1 650 4
123918: 9 1 2 644 9 6
39657: 77 2 7 25 982
124840: 12 41 8 4 87 1 68 503
179966207842: 85 8 9 6 51 97 4 19 6 4 5
718854: 46 9 3 6 664 8 86 63
926545746: 9 265 457 4 6
674908382899: 41 1 66 9 9 1 6 3 289 9
1755015393918: 540 5 13 3 5 3 8 7 6 9 1 8
107210: 7 7 93 189 20
40936: 1 297 84 3 21 5 8 2 2 1 7
35354: 3 21 87 1 11 673 5
1576785524: 5 6 6 2 7 8 21 5 8 9 3 521
43218: 432 11 4 6
3322: 8 21 4 32 3
440344195: 8 9 788 8 17 8 3 81 53 5
1128: 8 3 33 24 72
36615177: 91 574 73 40 7
195: 9 8 66 51 6
141600555813: 14 39 5 6 90 82 9 5 4 6 7
1181669: 980 13 17 7 2
761996: 76 15 4 9 6
976585475: 673 517 95 79 82
3548311955: 51 694 89 11 952
9732638: 8 9 541 34 513
45902666880: 5 70 3 8 7 759 7 8 515 9
125069814644: 6 461 461 822 5 8 943
710696: 53 59 785 917 8
465599: 7 2 919 4 19 8 5 5 2 1
259967957955: 9 8 647 814 5 41 50 5
3845565568: 881 5 873 56 2 4 1
190592649449: 7 37 38 8 8 8 649 447
756057: 4 461 4 3 5 56 46 2 34
111361216: 28 5 2 5 2 68 5 9 5 1 46
110088843: 27 5 14 8 4 840
4387653: 39 71 8 367 570 52 1
2305381: 2 305 3 82
15439: 765 6 23 41 2
46691409877612: 58 3 6 42 59 3 347 8 14
10402885471: 30 448 42 516 26 3 5
367703352437: 664 879 45 5 7 1 2 434
432397019: 661 59 6 617 60
161713440: 156 799 97 180 7 122
200727: 9 52 206 50 32 5 3
4664366888: 466 1 436 68 85
4225790: 7 25 86 222 5 874
313967: 5 1 19 64 3 4 51 11
25424010: 5 5 376 48 10 13
869235228: 285 1 105 7 5 74 391 1
6422495: 6 9 90 88 1 695
231217: 61 78 176 6 338 74 6 4
175: 3 54 13
356649: 51 873 57 8 9
20662785: 76 941 982 1 2 285
668781667: 723 8 959 954
213680962: 18 6 89 8 5 2 3 94 1 570
409: 9 57 83 6 7 244
9100212539: 68 941 66 2 539
4425754: 1 7 85 37 5 43 635 11
7446: 95 5 7 666 94 1
24189389894: 7 3 3 2 87 3 55 8 446 96
73966855: 6 66 257 6 18 4 74 1 3 7
2754514531958: 459 6 514 5 31 958 2 1
2461425673: 659 498 48 4 933
5055058199: 2 9 9 29 82 10 7 7 7 23
9960104: 322 50 69 14 44
1154: 60 9 92 473 8 41
76056154128: 9 4 1 216 8 21 87 412 8
1690148990: 1 640 7 3 9 8 6 3 50 95 2
170115: 67 4 85 1 9 30 464 17 6
853869660: 1 9 4 87 30 6 8 9 4 4 8 8
6792: 81 71 2 8 9 95 574 9 8
6716711692856: 6 9 482 748 6 8 58 855
41172790: 1 40 524 73 790
17788396: 37 5 2 21 38 2 2 6 4 27 4
190790305: 8 293 365 223 426
1193801: 4 4 651 611 940
2422522291: 346 7 520 2 283 8
13308: 64 957 5 83 12
4293863484: 612 818 9 84 953
149017: 5 98 660 4 43 33 9 7 3 4
72: 9 7 2 38 13
226993231: 97 10 26 60 70 17 90
11072: 5 8 19 5 1
4978271: 71 7 8 269 5
992998: 3 22 4 7 3 44 1 9 12 368
313930837: 313 92 8 2 835
102854: 1 399 5 5 51 7 849 1
409530: 549 36 7 21 9
792211: 4 3 922 1 1
160645: 7 63 394 3 55 31 9 7 19
1951: 4 33 4 7 120 7 4 3 1 8 2 8
38349229: 18 5 5 9 22 4 4 7 247 7 2
24060: 3 8 4 8 92 113 104 1
239912: 655 214 276 60 8
2823624050: 98 9 3 8 4 8 41 763 8
61741: 686 9 1
12031786809: 911 1 3 1 99 6 4 11 5 3 8
34865617: 899 524 70 74 814
1249568: 15 76 259 3 895 544
1047251: 70 15 5 992 691
162436904: 203 4 1 8 426 2 1 42 8 3
2284918477: 923 643 3 4 521 7 3 6
12166392: 423 8 84 1 4 8 14 84
303534248989: 6 72 629 6 806 9 8 386
2099524667214: 72 90 9 4 581 2 402 8
21542441041: 8 9 7 8 561 2 200 9 7 7 3
6383320: 68 561 97 836 1 8
19518067475: 49 77 21 61 8 848
398142456: 8 2 6 241 1 4 6 6 15 4 9 2
4538716967: 74 9 6 58 2 6 1 9 91 965
216597935617: 30 313 806 243 4 63 5
74528982: 431 3 9 8 6 3 4 4 8 74 2
26089807308: 5 207 2 9 252 499 3 10
2094995: 3 4 314 397 503
347304287: 5 988 58 285
134526480: 13 93 63 7 7 5 4 1 60 44
47110131597: 673 70 131 59 7
33282630423: 528 295 715 6 63
63099288: 1 4 1 15 4 58 1 3 4 1 84 7
18351027: 25 810 5 302 3
15327: 3 509 29 402 77
718477: 79 8 3 9 8
156918: 6 4 53 296 33 5
13721761037: 163 741 7 4 4 9 5 6 284
3725196162: 73 63 81 610 5 54
84577739: 4 88 47 2 4 815 3 299
185618730: 1 4 4 925 79 1 3 1 8 7
1163675588: 4 3 7 14 8 1 4 84 31 9 7 5
379019877: 80 30 5 4 944 98
228721777: 9 29 1 6 9 2 1 9 1 2 984 5
6294235021: 86 9 7 6 9 67 1 9 4 18 3 3
158746596: 90 467 95 5 32 3
18697431689: 41 57 4 7 15 84 2 9
858844: 483 886 725 759 2
55947: 6 782 71
28295433: 19 541 362 17 4
1998070830: 8 476 5 583 72 4 6 45
5046835618: 787 27 62 26 9 618
1639: 1 50 7 3 2 6 7 665 9 9 2 7
7414: 13 34 776 9 8
12135987: 720 36 52 7 6 590 9
3592173389074: 98 61 8 9 55 64 846 6
148153: 16 5 3 85 21
19227110478: 6 6 7 608 6 4 4 6 50 71 4
1261831906: 98 58 2 64 7 908
8451760895: 4 2 4 9 7 879 707 89 8
37032: 2 2 2 96 635 2 1 2 1 47 6
24846: 7 273 1 573 4
100699: 37 214 401 28 1 14 5
774056700558: 1 767 252 6 47 94 73 6
4290567: 5 297 810
30499049925897: 11 9 9 9 6 8 85 305 896
6607390: 277 6 238 9 30 473
161281: 72 28 8 1 1
214267392: 66 9 585 6 8 4
3746933: 61 7 70 6 753 5 1
13362239: 90 432 825 992 1
355: 3 3 39 4
3528: 21 16 56 9
48083163: 8 290 2 580 1
125953633: 8 231 527 6 35
49621348: 492 74 1 346 3 48
328566351: 84 628 2 9 11 233 2 2
897549442: 997 277 9 54 88
1518061: 758 2 8 2 62
11728: 2 1 36 770 582 75 8
440725865: 8 6 35 5 314 5 54 3 1 6 7
17245369: 6 303 70 1 70 7 1 88 9
1128025843: 8 2 736 30 5 5 798 46
1387547: 6 330 7 17 543 987
1735611512: 779 88 805 753 2 1 5
59018886: 83 4 9 7 6 99 8 83 97 1 9
14972265: 8 56 23 763 77 87
98394814: 9 26 7 9 861 9 1 3 1 4
62171: 8 54 987 975 2
747726641: 392 89 25 987 1 9 8 7
193535: 4 9 96 8 7
8491504: 925 1 925 459 4
19943778: 11 1 41 335 121 3 555
1233154550: 89 517 335 7 762 8
104363998: 88 61 7 639 98
1211277625: 68 4 95 330 9 4 4 7 62 5
22727771: 250 4 504 638 18
272163135216: 704 355 94 4 23 42
21950784286: 7 6 9 3 3 270 4 48 2 2 6 6
472: 2 95 5 85 90 67 125 3
1915: 5 65 727 108 755
16669773: 7 984 623 6 9 3
105548118179: 387 439 345 9 62 182
6718: 3 1 3 11 43 56
84500240: 989 89 192 5 80
20729408: 4 1 182 32 1 4 127
2059431192: 5 1 9 324 9 1 376 3 8 9 7
6661244: 239 306 61 91 501
912840: 77 40 195 6 40
8462797: 82 5 905 7 8 528 6 3 8 6
518913992: 3 5 1 4 1 25 6 4 6 615 9 2
53096: 9 8 726 824
3277428146350: 3 27 74 207 74 463 4 8
688291: 593 829 484 33 11
3394: 4 6 9 36 52 4
1035101750: 419 73 43 9 787
21467269505: 4 95 5 76 388 3 4 8 3 3 7
13893516625: 28 199 248 222 8 3 22
19972600: 9 29 56 63 1 597
6298749: 1 3 9 2 8 282 3 2 8 461 9
22: 1 8 1 1 13
9070658525: 3 1 82 8 82 3 64 9 924 8
14490586: 2 4 30 84 53 9 4 95 2 6 3
706408: 4 53 12 22 411
57200: 7 679 28 1 1 5 16
1940489988: 4 4 7 3 5 6 7 22 899 88
317776438: 642 707 72 34 7
91211400641: 900 95 4 381 74 16 2 7
186: 171 8 7
11660572695: 3 721 7 73 17 317
12904914: 3 6 7 1 7 3 1 4 9 7 93 141
21267013269: 365 2 35 30 1 58 59 94
1730625: 949 777 4 625
36784: 98 5 75 31 3
145262: 309 6 1 830 37
888036: 2 9 9 4 8 311 12 8 3 4 9 5
107616111478: 442 11 34 927 651
292000: 81 5 6 7 1 6 1 980 8 4 4
3765: 2 787 420 46 3
46121893264: 9 4 9 90 9 45 6 6 1 2 4 40
5795330413: 81 4 2 9 607 25 60 9 6 4
36648062: 2 4 144 45 62
6836468: 683 55 8 76 94
1436309: 64 9 4 783 310
2196829: 540 8 5 1 2 58 3 811 2
11799356411: 4 9 5 4 7 8 156 558 9 5
1971634891: 3 7 123 2 21 6 4 7 2 2 4 3
9397275528: 12 791 1 9 7 5 99
274978673783: 55 862 11 617 58
13841: 3 4 4 78 165
120688822: 15 62 83 805 148 2
225387750: 4 4 3 1 9 3 5 5 8 875 6 7
393785: 3 880 55 37 180 67
1762656: 65 6 2 439 5 24
220788852: 29 2 54 7 88 51
43526370: 4 765 16 889 930
737100420299: 9 9 70 975 7 6 4 5 60 5
157961882: 3 2 987 8 76 959 7 5 4 5
1802: 7 5 2 21 8 93 720
7346461: 6 898 394 54 461
8503740: 9 5 2 92 85 7
5919855997: 8 2 333 7 584 855 998
390937659: 92 7 8 9 667 2 5 1 25 6 3
365744: 516 29 3 822 4 8
8057514: 7 5 45 5 7 5 16
28017586: 8 98 7 5 95 8 30 95 8 7
202759256: 4 134 40 70 9 9 6
471150: 39 470 469 4 43 25
513685: 2 8 7 5 61 220 71
1543834258722: 380 736 6 71 5 1 92 5
3384988870: 5 8 4 4 1 3 8 6 721 5 2 4
4764: 35 4 5 5 3 7 7 59 1 1 74 4
1184974: 28 19 42 8 6 53
4000143113: 35 8 703 238 556
418: 4 1 7 2
9557037: 61 1 4 391 3 992
105661643: 573 5 4 94 49
23214254: 382 4 9 6 252
5444: 46 4 4 3 4 7
737349: 94 5 32 8 49
5122916: 2 48 71 51 919
5821000: 79 736 63 2 6 5 3
8301678: 77 18 155 73 561
1501597072: 7 6 4 9 787 265 102 5 5
718052598: 491 7 6 4 4 8 4 4 454 9
178497: 4 1 6 11 6 413 52 4 99 3
540: 7 9 10 4 1 18
9103497120: 9 400 713 1 97 2 680 9
5630913090: 782 8 570 1 90
1054463962: 43 4 6 361 56 4 95 9 63
20116902: 8 244 15 7 721 4 74 2
898710: 70 26 102 126 581
14793708485071: 959 6 539 3 91 9 530
50330139441: 99 270 49 2 507
6807041: 41 527 3 5 21 836
1031: 76 27 1
93138: 9 218 95 8
258123: 57 44 4 658 5 700
46365920: 89 374 3 2 3 7 5 3 6 1 7 1
11859124: 2 5 28 798 55 8 2 6 90 2
23250824: 539 32 674 4 1 8 2 496
545806394: 3 7 8 1 5 484 6 8 1 9 6 49
10832: 185 8 21 23 4 7 53 7 76
100339266: 8 67 26 72 64
7412898: 92 66 8 91 7
257523634800: 9 566 32 964 9 815 60
185426200: 1 8 5 4 261 4 3 50 1 1 9
10433434471: 92 177 451 86 468
216571: 7 8 6 865 734 6 9
1104: 2 52 6 2 1
5318188: 23 2 317 671 513
1007609179: 34 7 1 45 141 3 29
323629: 7 1 5 8 1 378 9
3521036: 6 698 5 963 2 69
95660213: 1 7 948 98 2 620 13
64638: 6 46 37 1
17385402: 173 84 48 7 904 3 3 8
1616421504: 5 498 7 6 7 1 9 2 3 7 5 3
715025: 71 502 5
20047048: 1 260 158 488 8
216208384: 802 4 62 556 448
4235581375: 4 7 9 451 8 92 7 137 4 2
8823357451: 80 64 74 328 71 11
3406644959: 8 8 9 6 2 643 7 324 4 95
3356625: 670 433 892 5
516787406: 2 33 832 55 9 9 4 5 4 1 6
14601: 21 1 7 6 3 975
39390: 508 39 72 6 1
57737790013: 60 1 911 5 6 5 4 495 13
75067696812: 21 5 88 1 8 869 4 812
28721: 2 3 5 68 5 33
299147159: 19 72 7 9 61 925 7 308
47958: 6 290 162 2 4
4810465342: 87 463 55 335 5
1084622973: 2 8 8 4 6 2 3 5 7 963 5 2
940412473: 7 23 54 540 3 3 6 27 4 2
1251718: 6 69 2 25 72 9 1 9 252 3
2276640: 4 9 5 86 93 2 9 6 31 6 8
363950: 908 5 8 5 553
963651: 6 4 938 980 663 8
4385541040633: 515 946 85 40 633
1498665: 4 6 23 174 87 3
4678232686: 8 584 2 60 3 268 1 7
682407: 6 822 74 122 13
3718652394: 6 1 53 86 5 2 391
22789229870: 5 426 42 298 70
128646776484: 4 4 2 7 8 699 2 4 18 49
52417206331: 6 798 6 771 331
3040286: 538 593 1 389 1 4 2 6
198047: 9 45 27 9 2 499 713 1 5
21645166: 7 1 6 7 91 1 276 2 2 347
22206000: 6 6 72 3 95 731 749 6 5
1402633431: 253 924 2 39 6
24223828: 928 8 647 3 7 4
5140898383: 72 714 9 83 1 8 3
11209432: 70 8 2 70 24 33 1
5551629375: 9 7 9 7 782 7 5 5 9 8 612
119196: 80 70 29 3 1 7 299 1 1 3
15894764944: 595 667 504 4 494 2
1285987: 47 342 7 1 8
9717593: 94 28 289 59 5
263: 5 41 52 6
153682480: 630 9 497 89 3 56 5
1937661: 8 157 993 4 86 2 79
84669705: 851 449 5 801 81
875552: 8 751 99 50 303
96459: 160 1 7 6 6
562626: 92 95 54 3 6
19640: 24 55 8 2
67784330: 4 4 71 36 95 5 8 2 2 2 8 7
213868: 25 4 842
105733: 4 15 30 28 54

View File

@@ -0,0 +1,50 @@
.........................p........................
......................h....C............M.........
..............................p....U..............
..5..................p............................
..6z...........................................C..
...............c...........zV.....................
...5.....c........................................
.Z.............h........S...z....9................
.O............................9...z........M..C...
..O....5..............................F..M..C.....
..Z.........5.c...............M....V..............
........ZO................q.......................
s...O................h..Uq.....7V...........4.....
.q.g..............c.............p.......4.........
............hZ.............................4G.....
6s...........................U.Q.....3............
.......6.................9.......Q.............3..
....s..D.........................6................
.............................................FL...
..................................................
..g...D.........q.....f.......Q...F....L......7...
...............2.........f.............V.L...4....
...................2.s...................f3......G
....g...........................v......7P.........
..2..g.............d.....v...........P.......1....
..............u.........f.............L........G..
.........l.D....u...............d........o..P.....
..................8...............9..1......o...7.
............l.....................................
...................l...S...........F.......o..U...
.......................u...S......................
..........l....u...............m...........P....G.
......................................1.8.......o.
..................................................
..................v.......S................0......
.............v........d.....1.....................
..................................................
..........D....................................0..
...................m.............H..........0.....
...................................d......0.......
..................................................
....Q.............................................
................................H.................
........................H....................8....
..................................................
..................................................
.........................................8........
.......................H3.........................
............................m.....................
................................m.................

View File

@@ -0,0 +1,28 @@
47|53
97|13
97|61
97|47
75|29
61|13
75|53
29|13
97|29
53|29
61|53
97|53
61|29
47|13
75|47
97|75
47|61
75|61
47|29
75|13
53|13
75,47,61,53,29
97,61,53,29,13
75,29,13
75,97,47,61,53
61,13,29
97,13,75,29,47

View File

@@ -0,0 +1,10 @@
....#.....
.........#
..........
..#.......
.......#..
..........
.#..^.....
........#.
#.........
......#...

View File

@@ -0,0 +1,9 @@
190: 10 19
3267: 81 40 27
83: 17 5
156: 15 6
7290: 6 8 6 15
161011: 16 10 13
192: 17 8 14
21037: 9 7 18 13
292: 11 6 16 20

View File

@@ -0,0 +1,12 @@
............
........0...
.....0......
.......0....
....0.......
......A.....
............
............
........A...
.........A..
............
............