Refactor code for API (#3)
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
Co-authored-by: Mikael CAPELLE <mikael.capelle@thalesaleniaspace.com> Co-authored-by: Mikaël Capelle <capelle.mikael@gmail.com> Reviewed-on: #3
This commit is contained in:
parent
1caf93b38b
commit
87936d6422
16
poetry.lock
generated
16
poetry.lock
generated
@ -1245,6 +1245,20 @@ files = [
|
|||||||
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
|
||||||
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
|
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "types-networkx"
|
||||||
|
version = "3.4.2.20241115"
|
||||||
|
description = "Typing stubs for networkx"
|
||||||
|
optional = false
|
||||||
|
python-versions = ">=3.8"
|
||||||
|
files = [
|
||||||
|
{file = "types-networkx-3.4.2.20241115.tar.gz", hash = "sha256:d669b650cf6c6c9ec879a825449eb04a5c10742f3109177e1683f57ee49e0f59"},
|
||||||
|
{file = "types_networkx-3.4.2.20241115-py3-none-any.whl", hash = "sha256:f0c382924d6614e06bf0b1ca0b837b8f33faa58982bc086ea762efaf39aa98dd"},
|
||||||
|
]
|
||||||
|
|
||||||
|
[package.dependencies]
|
||||||
|
numpy = ">=1.20"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typing-extensions"
|
name = "typing-extensions"
|
||||||
version = "4.12.2"
|
version = "4.12.2"
|
||||||
@ -1281,4 +1295,4 @@ files = [
|
|||||||
[metadata]
|
[metadata]
|
||||||
lock-version = "2.0"
|
lock-version = "2.0"
|
||||||
python-versions = "^3.10"
|
python-versions = "^3.10"
|
||||||
content-hash = "b643261f91a781d77735e05f6d2ac1002867600c2df6393a9d1a15f5e1189109"
|
content-hash = "c91bc307ff4a5b3e8cd1976ebea211c9749fe09d563dd80861f70ce26826cda9"
|
||||||
|
@ -23,6 +23,7 @@ ruff = "^0.8.1"
|
|||||||
poethepoet = "^0.31.1"
|
poethepoet = "^0.31.1"
|
||||||
ipykernel = "^6.29.5"
|
ipykernel = "^6.29.5"
|
||||||
networkx-stubs = "^0.0.1"
|
networkx-stubs = "^0.0.1"
|
||||||
|
types-networkx = "^3.4.2.20241115"
|
||||||
|
|
||||||
[tool.poetry.scripts]
|
[tool.poetry.scripts]
|
||||||
holt59-aoc = "holt59.aoc.__main__:main"
|
holt59-aoc = "holt59.aoc.__main__:main"
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
line = sys.stdin.read().strip()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
floor = 0
|
|
||||||
floors = [(floor := floor + (1 if c == "(" else -1)) for c in line]
|
|
||||||
|
|
||||||
|
|
||||||
print(f"answer 1 is {floors[-1]}")
|
class Solver(BaseSolver):
|
||||||
print(f"answer 2 is {floors.index(-1)}")
|
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)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import itertools
|
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)
|
# 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
|
# CodeGolf answer https://codegolf.stackexchange.com/a/8479/42148
|
||||||
|
|
||||||
# fmt: off
|
# fmt: off
|
||||||
atoms = [
|
ATOMS: list[tuple[str, tuple[int, ...]]] = [
|
||||||
("22", (0, )), # 0
|
("22", (0, )), # 0
|
||||||
("13112221133211322112211213322112", (71, 90, 0, 19, 2, )), # 1
|
("13112221133211322112211213322112", (71, 90, 0, 19, 2, )), # 1
|
||||||
("312211322212221121123222112", (1, )), # 2
|
("312211322212221121123222112", (1, )), # 2
|
||||||
@ -105,7 +105,7 @@ atoms = [
|
|||||||
]
|
]
|
||||||
# fmt: on
|
# fmt: on
|
||||||
|
|
||||||
starters = [
|
STARTERS = [
|
||||||
"1",
|
"1",
|
||||||
"11",
|
"11",
|
||||||
"21",
|
"21",
|
||||||
@ -122,27 +122,26 @@ def look_and_say_length(s: str, n: int) -> int:
|
|||||||
if n == 0:
|
if n == 0:
|
||||||
return len(s)
|
return len(s)
|
||||||
|
|
||||||
if s in starters:
|
if s in STARTERS:
|
||||||
return look_and_say_length(
|
return look_and_say_length(
|
||||||
"".join(f"{len(list(g))}{k}" for k, g in itertools.groupby(s)), n - 1
|
"".join(f"{len(list(g))}{k}" for k, g in itertools.groupby(s)), n - 1
|
||||||
)
|
)
|
||||||
|
|
||||||
counts = {i: 0 for i in range(len(atoms))}
|
counts = {i: 0 for i in range(len(ATOMS))}
|
||||||
idx = next(i for i, (a, _) in enumerate(atoms) if s == a)
|
idx = next(i for i, (a, _) in enumerate(ATOMS) if s == a)
|
||||||
counts[idx] = 1
|
counts[idx] = 1
|
||||||
|
|
||||||
for _ in range(n):
|
for _ in range(n):
|
||||||
c2 = {i: 0 for i in range(len(atoms))}
|
c2 = {i: 0 for i in range(len(ATOMS))}
|
||||||
for i in counts:
|
for i in counts:
|
||||||
for j in atoms[i][1]:
|
for j in ATOMS[i][1]:
|
||||||
c2[j] += counts[i]
|
c2[j] += counts[i]
|
||||||
counts = c2
|
counts = c2
|
||||||
|
|
||||||
return sum(counts[i] * len(a[0]) for i, a in enumerate(atoms))
|
return sum(counts[i] * len(a[0]) for i, a in enumerate(ATOMS))
|
||||||
|
|
||||||
|
|
||||||
answer_1 = look_and_say_length(line, 40)
|
class Solver(BaseSolver):
|
||||||
print(f"answer 1 is {answer_1}")
|
def solve(self, input: str) -> Iterator[Any] | None:
|
||||||
|
yield look_and_say_length(input, 40)
|
||||||
answer_2 = look_and_say_length(line, 50)
|
yield look_and_say_length(input, 50)
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def is_valid(p: str) -> bool:
|
def is_valid(p: str) -> bool:
|
||||||
@ -40,10 +42,8 @@ def find_next_password(p: str) -> str:
|
|||||||
return p
|
return p
|
||||||
|
|
||||||
|
|
||||||
line = sys.stdin.read().strip()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
answer_1 = find_next_password(line)
|
answer_1 = find_next_password(input)
|
||||||
print(f"answer 1 is {answer_1}")
|
yield answer_1
|
||||||
|
yield find_next_password(increment(answer_1))
|
||||||
answer_2 = find_next_password(increment(answer_1))
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
from typing import Any, Iterator, TypeAlias
|
||||||
from typing import TypeAlias
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
JsonObject: TypeAlias = dict[str, "JsonObject"] | list["JsonObject"] | int | str
|
JsonObject: TypeAlias = dict[str, "JsonObject"] | list["JsonObject"] | int | str
|
||||||
|
|
||||||
@ -18,10 +19,9 @@ def json_sum(value: JsonObject, ignore: str | None = None) -> int:
|
|||||||
return 0
|
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)
|
yield json_sum(data)
|
||||||
print(f"answer 1 is {answer_1}")
|
yield json_sum(data, "red")
|
||||||
|
|
||||||
answer_2 = json_sum(data, "red")
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Literal, cast
|
from typing import Any, Iterator, Literal, cast
|
||||||
|
|
||||||
import parse # type: ignore
|
import parse # type: ignore
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def max_change_in_happiness(happiness: dict[str, dict[str, int]]) -> int:
|
def max_change_in_happiness(happiness: dict[str, dict[str, int]]) -> int:
|
||||||
guests = list(happiness)
|
guests = list(happiness)
|
||||||
@ -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)
|
happiness: dict[str, dict[str, int]] = defaultdict(dict)
|
||||||
for line in lines:
|
for line in lines:
|
||||||
u1, gain_or_loose, hap, u2 = cast(
|
u1, gain_or_loose, hap, u2 = cast(
|
||||||
tuple[str, Literal["gain", "lose"], int, str],
|
tuple[str, Literal["gain", "lose"], int, str],
|
||||||
parse.parse( # type: ignore
|
parse.parse( # type: ignore
|
||||||
"{} would {} {:d} happiness units by sitting next to {}.", line
|
"{} would {} {:d} happiness units by sitting next to {}.", line
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
happiness[u1][u2] = hap if gain_or_loose == "gain" else -hap
|
happiness[u1][u2] = hap if gain_or_loose == "gain" else -hap
|
||||||
|
|
||||||
|
yield max_change_in_happiness(happiness)
|
||||||
|
for guest in list(happiness):
|
||||||
|
happiness["me"][guest] = 0
|
||||||
|
happiness[guest]["me"] = 0
|
||||||
|
|
||||||
answer_1 = max_change_in_happiness(happiness)
|
yield 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}")
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import sys
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Literal, cast
|
from typing import Any, Iterator, Literal, cast
|
||||||
|
|
||||||
import parse # type: ignore
|
import parse # type: ignore
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Reindeer:
|
class Reindeer:
|
||||||
@ -13,50 +14,50 @@ class Reindeer:
|
|||||||
rest_time: int
|
rest_time: int
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
reindeers: list[Reindeer] = []
|
reindeers: list[Reindeer] = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
reindeer, speed, speed_time, rest_time = cast(
|
reindeer, speed, speed_time, rest_time = cast(
|
||||||
tuple[str, int, int, int],
|
tuple[str, int, int, int],
|
||||||
parse.parse( # type: ignore
|
parse.parse( # type: ignore
|
||||||
"{} can fly {:d} km/s for {:d} seconds, "
|
"{} can fly {:d} km/s for {:d} seconds, "
|
||||||
"but then must rest for {:d} seconds.",
|
"but then must rest for {:d} seconds.",
|
||||||
line,
|
line,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
reindeers.append(
|
reindeers.append(
|
||||||
Reindeer(name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time)
|
Reindeer(
|
||||||
)
|
name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
target = 1000 if len(reindeers) <= 2 else 2503
|
target = 1000 if len(reindeers) <= 2 else 2503
|
||||||
|
|
||||||
states: dict[Reindeer, tuple[Literal["resting", "flying"], int]] = {
|
states: dict[Reindeer, tuple[Literal["resting", "flying"], int]] = {
|
||||||
reindeer: ("resting", 0) for reindeer in reindeers
|
reindeer: ("resting", 0) for reindeer in reindeers
|
||||||
}
|
}
|
||||||
distances: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
|
distances: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
|
||||||
points: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
|
points: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
|
||||||
|
|
||||||
for time in range(target):
|
for time in self.progress.wrap(range(target)):
|
||||||
for reindeer in reindeers:
|
for reindeer in reindeers:
|
||||||
if states[reindeer][0] == "flying":
|
if states[reindeer][0] == "flying":
|
||||||
distances[reindeer] += reindeer.speed
|
distances[reindeer] += reindeer.speed
|
||||||
|
|
||||||
top_distance = max(distances.values())
|
top_distance = max(distances.values())
|
||||||
for reindeer in reindeers:
|
for reindeer in reindeers:
|
||||||
if distances[reindeer] == top_distance:
|
if distances[reindeer] == top_distance:
|
||||||
points[reindeer] += 1
|
points[reindeer] += 1
|
||||||
|
|
||||||
for reindeer in reindeers:
|
for reindeer in reindeers:
|
||||||
if states[reindeer][1] == time:
|
if states[reindeer][1] == time:
|
||||||
if states[reindeer][0] == "resting":
|
if states[reindeer][0] == "resting":
|
||||||
states[reindeer] = ("flying", time + reindeer.fly_time)
|
states[reindeer] = ("flying", time + reindeer.fly_time)
|
||||||
else:
|
else:
|
||||||
states[reindeer] = ("resting", time + reindeer.rest_time)
|
states[reindeer] = ("resting", time + reindeer.rest_time)
|
||||||
|
|
||||||
|
yield max(distances.values())
|
||||||
answer_1 = max(distances.values())
|
yield max(points.values()) - 1
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
answer_2 = max(points.values()) - 1
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import math
|
import math
|
||||||
import sys
|
from typing import Any, Iterator, Sequence, cast
|
||||||
from typing import Sequence, cast
|
|
||||||
|
|
||||||
import parse # type: ignore
|
import parse # type: ignore
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def score(ingredients: list[list[int]], teaspoons: Sequence[int]) -> int:
|
def score(ingredients: list[list[int]], teaspoons: Sequence[int]) -> int:
|
||||||
return math.prod(
|
return math.prod(
|
||||||
@ -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]] = []
|
ingredients: list[list[int]] = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
_, *scores = cast(
|
_, *scores = cast(
|
||||||
tuple[str, int, int, int, int, int],
|
tuple[str, int, int, int, int, int],
|
||||||
parse.parse( # type: ignore
|
parse.parse( # type: ignore
|
||||||
"{}: capacity {:d}, durability {:d}, flavor {:d}, "
|
"{}: capacity {:d}, durability {:d}, flavor {:d}, "
|
||||||
"texture {:d}, calories {:d}",
|
"texture {:d}, calories {:d}",
|
||||||
line,
|
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.append(scores)
|
||||||
|
|
||||||
|
total_teaspoons = 100
|
||||||
|
calories: list[int] = []
|
||||||
|
scores: list[int] = []
|
||||||
|
|
||||||
answer_1 = max(scores)
|
for a in range(total_teaspoons + 1):
|
||||||
print(f"answer 1 is {answer_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)
|
scores.append(score(ingredients, teaspoons))
|
||||||
print(f"answer 2 is {answer_2}")
|
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)
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
import operator as op
|
import operator as op
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import Callable
|
from typing import Any, Callable, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
MFCSAM: dict[str, int] = {
|
MFCSAM: dict[str, int] = {
|
||||||
"children": 3,
|
"children": 3,
|
||||||
@ -17,18 +18,10 @@ MFCSAM: dict[str, int] = {
|
|||||||
"perfumes": 1,
|
"perfumes": 1,
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = sys.stdin.readlines()
|
|
||||||
|
|
||||||
aunts: list[dict[str, int]] = [
|
def match(
|
||||||
{
|
aunts: list[dict[str, int]], operators: dict[str, Callable[[int, int], bool]]
|
||||||
match[1]: int(match[2])
|
) -> int:
|
||||||
for match in re.findall(R"((?P<compound>[^:, ]+): (?P<quantity>\d+))", line)
|
|
||||||
}
|
|
||||||
for line in lines
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def match(operators: dict[str, Callable[[int, int], bool]]) -> int:
|
|
||||||
return next(
|
return next(
|
||||||
i
|
i
|
||||||
for i, aunt in enumerate(aunts, start=1)
|
for i, aunt in enumerate(aunts, start=1)
|
||||||
@ -36,16 +29,29 @@ def match(operators: dict[str, Callable[[int, int], bool]]) -> int:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
answer_1 = match(defaultdict(lambda: op.eq))
|
class Solver(BaseSolver):
|
||||||
print(f"answer 1 is {answer_1}")
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
answer_2 = match(
|
aunts: list[dict[str, int]] = [
|
||||||
defaultdict(
|
{
|
||||||
lambda: op.eq,
|
match[1]: int(match[2])
|
||||||
trees=op.gt,
|
for match in re.findall(
|
||||||
cats=op.gt,
|
R"((?P<compound>[^:, ]+): (?P<quantity>\d+))", line
|
||||||
pomeranians=op.lt,
|
)
|
||||||
goldfish=op.lt,
|
}
|
||||||
)
|
for line in lines
|
||||||
)
|
]
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
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,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
from typing import Iterator
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int, ...]]:
|
def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int, ...]]:
|
||||||
@ -16,15 +17,18 @@ def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int,
|
|||||||
yield (containers[i],) + combination
|
yield (containers[i],) + combination
|
||||||
|
|
||||||
|
|
||||||
containers = [int(c) for c in sys.stdin.read().split()]
|
class Solver(BaseSolver):
|
||||||
total = 25 if len(containers) <= 5 else 150
|
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)
|
yield len(combinations)
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
min_containers = min(len(combination) for combination in combinations)
|
min_containers = min(len(combination) for combination in combinations)
|
||||||
|
yield sum(
|
||||||
answer_2 = sum(1 for combination in combinations if len(combination) == min_containers)
|
1 for combination in combinations if len(combination) == min_containers
|
||||||
print(f"answer 2 is {answer_2}")
|
)
|
||||||
|
@ -1,66 +1,66 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
grid0 = np.array([[c == "#" for c in line] for line in sys.stdin.read().splitlines()])
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# add an always off circle around
|
|
||||||
grid0 = np.concatenate(
|
class Solver(BaseSolver):
|
||||||
[
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
|
grid0 = np.array([[c == "#" for c in line] for line in input.splitlines()])
|
||||||
np.concatenate(
|
|
||||||
|
# add an always off circle around
|
||||||
|
grid0 = np.concatenate(
|
||||||
[
|
[
|
||||||
np.zeros((1, grid0.shape[1]), dtype=bool),
|
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
|
||||||
grid0,
|
np.concatenate(
|
||||||
np.zeros((1, grid0.shape[1]), dtype=bool),
|
[
|
||||||
]
|
np.zeros((1, grid0.shape[1]), dtype=bool),
|
||||||
),
|
grid0,
|
||||||
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
|
np.zeros((1, grid0.shape[1]), dtype=bool),
|
||||||
],
|
]
|
||||||
axis=1,
|
),
|
||||||
)
|
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
|
||||||
|
],
|
||||||
|
axis=1,
|
||||||
|
)
|
||||||
|
|
||||||
moves = list(itertools.product([-1, 0, 1], repeat=2))
|
moves = list(itertools.product([-1, 0, 1], repeat=2))
|
||||||
moves.remove((0, 0))
|
moves.remove((0, 0))
|
||||||
|
|
||||||
jjs, iis = np.meshgrid(
|
jjs, iis = np.meshgrid(
|
||||||
np.arange(1, grid0.shape[0] - 1, dtype=int),
|
np.arange(1, grid0.shape[0] - 1, dtype=int),
|
||||||
np.arange(1, grid0.shape[1] - 1, dtype=int),
|
np.arange(1, grid0.shape[1] - 1, dtype=int),
|
||||||
)
|
)
|
||||||
iis, jjs = iis.flatten(), jjs.flatten()
|
iis, jjs = iis.flatten(), jjs.flatten()
|
||||||
|
|
||||||
ins = iis[:, None] + np.array(moves)[:, 0]
|
ins = iis[:, None] + np.array(moves)[:, 0]
|
||||||
jns = jjs[:, None] + np.array(moves)[:, 1]
|
jns = jjs[:, None] + np.array(moves)[:, 1]
|
||||||
|
|
||||||
|
def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
|
||||||
|
neighbors_on = grid[ins, jns].sum(axis=1)
|
||||||
|
cells_on = grid[iis, jjs]
|
||||||
|
|
||||||
def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
|
grid = np.zeros_like(grid)
|
||||||
neighbors_on = grid[ins, jns].sum(axis=1)
|
grid[iis, jjs] = (neighbors_on == 3) | (cells_on & (neighbors_on == 2))
|
||||||
cells_on = grid[iis, jjs]
|
|
||||||
|
|
||||||
grid = np.zeros_like(grid)
|
return grid
|
||||||
grid[iis, jjs] = (neighbors_on == 3) | (cells_on & (neighbors_on == 2))
|
|
||||||
|
|
||||||
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 = 5 if len(grid) < 10 else 100
|
||||||
n_steps = 4 if len(grid) < 10 else 100
|
grid = grid0
|
||||||
for _ in range(n_steps):
|
for _ in range(n_steps):
|
||||||
grid = game_of_life(grid)
|
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
|
||||||
|
grid = game_of_life(grid)
|
||||||
|
|
||||||
answer_1 = grid.sum()
|
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
|
yield sum(cell for line in grid for cell in line)
|
||||||
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}")
|
|
||||||
|
@ -1,56 +1,58 @@
|
|||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
replacements_s, molecule = sys.stdin.read().split("\n\n")
|
from ..base import BaseSolver
|
||||||
|
|
||||||
REPLACEMENTS: dict[str, list[str]] = defaultdict(list)
|
|
||||||
for replacement_s in replacements_s.splitlines():
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
answer_2 = count
|
class Solver(BaseSolver):
|
||||||
print(f"answer 2 is {count}")
|
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
|
||||||
|
@ -1,20 +1,24 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
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))
|
lw, wh, hl = (length * width, width * height, height * length)
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
answer_2 = np.sum(
|
yield np.sum(2 * (lw + wh + hl) + np.min(np.stack([lw, wh, hl]), axis=0))
|
||||||
length * width * height
|
|
||||||
+ 2 * np.min(np.stack([length + width, length + height, height + width]), axis=0)
|
yield np.sum(
|
||||||
)
|
length * width * height
|
||||||
print(f"answer 2 is {answer_2}")
|
+ 2
|
||||||
|
* np.min(
|
||||||
|
np.stack([length + width, length + height, height + width]), axis=0
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import itertools
|
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
|
count = 0
|
||||||
k = 1
|
k = 1
|
||||||
while k * k < n:
|
while k * k < n:
|
||||||
@ -21,8 +21,9 @@ def presents(n: int, elf: int, max: int = target) -> int:
|
|||||||
return count
|
return count
|
||||||
|
|
||||||
|
|
||||||
answer_1 = next(n for n in itertools.count(1) if presents(n, 10) >= target)
|
class Solver(BaseSolver):
|
||||||
print(f"answer 1 is {answer_1}")
|
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)
|
yield next(n for n in itertools.count(1) if presents(n, 10, target) >= target)
|
||||||
print(f"answer 2 is {answer_2}")
|
yield next(n for n in itertools.count(1) if presents(n, 11, 50) >= target)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import sys
|
|
||||||
from math import ceil
|
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]
|
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_attack = int(lines[1].split(":")[1].strip())
|
||||||
boss_armor = int(lines[2].split(":")[1].strip())
|
boss_armor = int(lines[2].split(":")[1].strip())
|
||||||
boss_hp = int(lines[0].split(":")[1].strip())
|
boss_hp = int(lines[0].split(":")[1].strip())
|
||||||
|
|
||||||
|
min_cost, max_cost = 1_000_000, 0
|
||||||
|
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
|
cost, player_attack, player_armor = (
|
||||||
for equipments in itertools.product(WEAPONS, ARMORS, RINGS, RINGS):
|
sum(equipment[1:][k] for equipment in equipments) for k in range(3)
|
||||||
if equipments[-1][0] != "" and equipments[-2] == equipments[-1]:
|
)
|
||||||
continue
|
|
||||||
|
|
||||||
cost, player_attack, player_armor = (
|
if ceil(boss_hp / max(1, player_attack - boss_armor)) <= ceil(
|
||||||
sum(equipment[1:][k] for equipment in equipments) for k in range(3)
|
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(
|
yield min_cost
|
||||||
player_hp / max(1, boss_attack - player_armor)
|
yield max_cost
|
||||||
):
|
|
||||||
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}")
|
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import heapq
|
import heapq
|
||||||
import sys
|
from typing import Any, Iterator, Literal, TypeAlias, cast
|
||||||
from typing import Literal, TypeAlias, cast
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
PlayerType: TypeAlias = Literal["player", "boss"]
|
PlayerType: TypeAlias = Literal["player", "boss"]
|
||||||
SpellType: TypeAlias = Literal["magic missile", "drain", "shield", "poison", "recharge"]
|
SpellType: TypeAlias = Literal["magic missile", "drain", "shield", "poison", "recharge"]
|
||||||
@ -62,17 +63,6 @@ def play(
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
visited.add((player, player_hp, player_mana, player_armor, boss_hp, buffs))
|
visited.add((player, player_hp, player_mana, player_armor, boss_hp, buffs))
|
||||||
|
|
||||||
if hard_mode and player == "player":
|
|
||||||
player_hp = max(0, player_hp - 1)
|
|
||||||
|
|
||||||
if player_hp == 0:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if boss_hp == 0:
|
|
||||||
winning_node = spells
|
|
||||||
continue
|
|
||||||
|
|
||||||
new_buffs: list[tuple[BuffType, int]] = []
|
new_buffs: list[tuple[BuffType, int]] = []
|
||||||
for buff, length in buffs:
|
for buff, length in buffs:
|
||||||
length = length - 1
|
length = length - 1
|
||||||
@ -88,6 +78,16 @@ def play(
|
|||||||
if length > 0:
|
if length > 0:
|
||||||
new_buffs.append((buff, length))
|
new_buffs.append((buff, length))
|
||||||
|
|
||||||
|
if hard_mode and player == "player":
|
||||||
|
player_hp = player_hp - 1
|
||||||
|
|
||||||
|
if player_hp <= 0:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if boss_hp <= 0:
|
||||||
|
winning_node = spells
|
||||||
|
continue
|
||||||
|
|
||||||
buffs = tuple(new_buffs)
|
buffs = tuple(new_buffs)
|
||||||
|
|
||||||
if player == "boss":
|
if player == "boss":
|
||||||
@ -155,23 +155,28 @@ def play(
|
|||||||
return winning_node
|
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_hp = 50
|
||||||
player_mana = 500
|
player_mana = 500
|
||||||
player_armor = 0
|
player_armor = 0
|
||||||
|
|
||||||
boss_hp = int(lines[0].split(":")[1].strip())
|
boss_hp = int(lines[0].split(":")[1].strip())
|
||||||
boss_attack = int(lines[1].split(":")[1].strip())
|
boss_attack = int(lines[1].split(":")[1].strip())
|
||||||
|
|
||||||
answer_1 = sum(
|
yield sum(
|
||||||
c
|
c
|
||||||
for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, False)
|
for _, c in play(
|
||||||
)
|
player_hp, player_mana, player_armor, boss_hp, boss_attack, False
|
||||||
print(f"answer 1 is {answer_1}")
|
)
|
||||||
|
)
|
||||||
|
|
||||||
# 1242 (not working)
|
# 1242 (not working)
|
||||||
answer_2 = sum(
|
yield sum(
|
||||||
c for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, True)
|
c
|
||||||
)
|
for _, c in play(
|
||||||
print(f"answer 2 is {answer_2}")
|
player_hp, player_mana, player_armor, boss_hp, boss_attack, True
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import sys
|
|
||||||
from collections import defaultdict
|
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]:
|
def process(directions: str) -> dict[tuple[int, int], int]:
|
||||||
@ -27,8 +27,7 @@ def process(directions: str) -> dict[tuple[int, int], int]:
|
|||||||
return counts
|
return counts
|
||||||
|
|
||||||
|
|
||||||
answer_1 = len(process(line))
|
class Solver(BaseSolver):
|
||||||
print(f"answer 1 is {answer_1}")
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
yield len(process(input))
|
||||||
answer_2 = len(process(line[::2]) | process(line[1::2]))
|
yield len(process(input[::2]) | process(input[1::2]))
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
import hashlib
|
import hashlib
|
||||||
import itertools
|
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(
|
class Solver(BaseSolver):
|
||||||
i for i in it if hashlib.md5(f"{line}{i}".encode()).hexdigest().startswith("000000")
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
)
|
it = iter(itertools.count(1))
|
||||||
print(f"answer 2 is {answer_2}")
|
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")
|
||||||
|
)
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
VOWELS = "aeiou"
|
VOWELS = "aeiou"
|
||||||
FORBIDDEN = {"ab", "cd", "pq", "xy"}
|
FORBIDDEN = {"ab", "cd", "pq", "xy"}
|
||||||
@ -27,10 +29,8 @@ def is_nice_2(s: str) -> bool:
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
answer_1 = sum(map(is_nice_1, lines))
|
lines = input.splitlines()
|
||||||
print(f"answer 1 is {answer_1}")
|
yield sum(map(is_nice_1, lines))
|
||||||
|
yield sum(map(is_nice_2, lines))
|
||||||
answer_2 = sum(map(is_nice_2, lines))
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,33 +1,32 @@
|
|||||||
import sys
|
from typing import Any, Iterator, Literal, cast
|
||||||
from typing import Literal, cast
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import parse # type: ignore
|
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:
|
class Solver(BaseSolver):
|
||||||
case "turn on":
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
lights_1[sx:ex, sy:ey] = True
|
lights_1 = np.zeros((1000, 1000), dtype=bool)
|
||||||
lights_2[sx:ex, sy:ey] += 1
|
lights_2 = np.zeros((1000, 1000), dtype=int)
|
||||||
case "turn off":
|
for line in input.splitlines():
|
||||||
lights_1[sx:ex, sy:ey] = False
|
action, sx, sy, ex, ey = cast(
|
||||||
lights_2[sx:ex, sy:ey] = np.maximum(lights_2[sx:ex, sy:ey] - 1, 0)
|
tuple[Literal["turn on", "turn off", "toggle"], int, int, int, int],
|
||||||
case "toggle":
|
parse.parse("{} {:d},{:d} through {:d},{:d}", line), # type: ignore
|
||||||
lights_1[sx:ex, sy:ey] = ~lights_1[sx:ex, sy:ey]
|
)
|
||||||
lights_2[sx:ex, sy:ey] += 2
|
ex, ey = ex + 1, ey + 1
|
||||||
|
|
||||||
answer_1 = lights_1.sum()
|
match action:
|
||||||
print(f"answer 1 is {answer_1}")
|
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()
|
yield lights_1.sum()
|
||||||
print(f"answer 2 is {answer_2}")
|
yield lights_2.sum()
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import logging
|
|
||||||
import operator
|
import operator
|
||||||
import os
|
from typing import Any, Callable, Iterator
|
||||||
import sys
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
|
|
||||||
|
|
||||||
OPERATORS = {
|
OPERATORS = {
|
||||||
"AND": operator.and_,
|
"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]
|
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(
|
def process(
|
||||||
signals: Signals,
|
signals: Signals,
|
||||||
values: dict[str, int],
|
values: dict[str, int],
|
||||||
@ -91,11 +45,52 @@ def process(
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
values_1 = process(signals.copy(), values.copy())
|
class Solver(BaseSolver):
|
||||||
logging.info("\n" + "\n".join(f"{k}: {values_1[k]}" for k in sorted(values_1)))
|
def solve(self, input: str) -> Iterator[Any] | None:
|
||||||
answer_1 = values_1["a"]
|
lines = input.splitlines()
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
values_2 = process(signals.copy(), values | {"b": values_1["a"]})
|
signals: Signals = {}
|
||||||
answer_2 = values_2["a"]
|
values: dict[str, int] = {"": 0}
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
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"]
|
||||||
|
@ -1,35 +1,32 @@
|
|||||||
import logging
|
from typing import Any, Iterator
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
|
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
answer_1 = sum(
|
yield sum(
|
||||||
# left and right quotes (not in memory)
|
# left and right quotes (not in memory)
|
||||||
2
|
2
|
||||||
# each \\ adds one character in the literals (compared to memory)
|
# each \\ adds one character in the literals (compared to memory)
|
||||||
+ line.count(R"\\")
|
+ line.count(R"\\")
|
||||||
# each \" adds one character in the literals (compared to memory)
|
# each \" adds one character in the literals (compared to memory)
|
||||||
+ line[1:-1].count(R"\"")
|
+ line[1:-1].count(R"\"")
|
||||||
# each \xFF adds 3 characters in the literals (compared to memory), but we must not
|
# 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
|
# 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
|
# 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"))
|
+ 3 * (line.count(R"\x") - line.count(R"\\x") + line.count(R"\\\x"))
|
||||||
for line in lines
|
for line in lines
|
||||||
)
|
)
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
answer_2 = sum(
|
yield sum(
|
||||||
# needs to wrap in quotes (2 characters)
|
# needs to wrap in quotes (2 characters)
|
||||||
2
|
2
|
||||||
# needs to escape every \ with an extra \
|
# needs to escape every \ with an extra \
|
||||||
+ line.count("\\")
|
+ line.count("\\")
|
||||||
# needs to escape every " with an extra \ (including the first and last ones)
|
# needs to escape every " with an extra \ (including the first and last ones)
|
||||||
+ line.count('"')
|
+ line.count('"')
|
||||||
for line in lines
|
for line in lines
|
||||||
)
|
)
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import cast
|
from typing import Any, Iterator, cast
|
||||||
|
|
||||||
import parse # type: ignore
|
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 = {
|
class Solver(BaseSolver):
|
||||||
route: sum(distances[o][d] for o, d in zip(route[:-1], route[1:]))
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
for route in map(tuple, itertools.permutations(distances))
|
lines = input.splitlines()
|
||||||
}
|
|
||||||
|
|
||||||
answer_1 = min(distance_of_routes.values())
|
distances: dict[str, dict[str, int]] = defaultdict(dict)
|
||||||
print(f"answer 1 is {answer_1}")
|
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())
|
distance_of_routes = {
|
||||||
print(f"answer 2 is {answer_2}")
|
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())
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
values = [int(line) for line in lines]
|
|
||||||
|
|
||||||
# part 1
|
class Solver(BaseSolver):
|
||||||
answer_1 = sum(v2 > v1 for v1, v2 in zip(values[:-1], values[1:]))
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
print(f"answer 1 is {answer_1}")
|
lines = input.splitlines()
|
||||||
|
|
||||||
# part 2
|
values = [int(line) for line in lines]
|
||||||
runnings = [sum(values[i : i + 3]) for i in range(len(values) - 2)]
|
|
||||||
answer_2 = sum(v2 > v1 for v1, v2 in zip(runnings[:-1], runnings[1:]))
|
# part 1
|
||||||
print(f"answer 2 is {answer_2}")
|
yield sum(v2 > v1 for v1, v2 in zip(values[:-1], values[1:]))
|
||||||
|
|
||||||
|
# part 2
|
||||||
|
runnings = [sum(values[i : i + 3]) for i in range(len(values) - 2)]
|
||||||
|
yield sum(v2 > v1 for v1, v2 in zip(runnings[:-1], runnings[1:]))
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,41 +1,38 @@
|
|||||||
import sys
|
|
||||||
from math import prod
|
from math import prod
|
||||||
from typing import Literal, TypeAlias, cast
|
from typing import Any, Iterator, Literal, TypeAlias, cast
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
Command: TypeAlias = Literal["forward", "up", "down"]
|
Command: TypeAlias = Literal["forward", "up", "down"]
|
||||||
|
|
||||||
commands: list[tuple[Command, int]] = [
|
|
||||||
(cast(Command, (p := line.split())[0]), int(p[1])) for line in lines
|
|
||||||
]
|
|
||||||
|
|
||||||
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
def depth_and_position(use_aim: bool):
|
commands: list[tuple[Command, int]] = [
|
||||||
aim, pos, depth = 0, 0, 0
|
(cast(Command, (p := line.split())[0]), int(p[1])) for line in lines
|
||||||
for command, value in commands:
|
]
|
||||||
d_depth = 0
|
|
||||||
match command:
|
|
||||||
case "forward":
|
|
||||||
pos += value
|
|
||||||
depth += value * aim
|
|
||||||
case "up":
|
|
||||||
d_depth = -value
|
|
||||||
case "down":
|
|
||||||
d_depth = value
|
|
||||||
|
|
||||||
if use_aim:
|
def depth_and_position(use_aim: bool):
|
||||||
aim += d_depth
|
aim, pos, depth = 0, 0, 0
|
||||||
else:
|
for command, value in commands:
|
||||||
depth += value
|
d_depth = 0
|
||||||
|
match command:
|
||||||
|
case "forward":
|
||||||
|
pos += value
|
||||||
|
depth += value * aim
|
||||||
|
case "up":
|
||||||
|
d_depth = -value
|
||||||
|
case "down":
|
||||||
|
d_depth = value
|
||||||
|
|
||||||
return depth, pos
|
if use_aim:
|
||||||
|
aim += d_depth
|
||||||
|
else:
|
||||||
|
depth += value
|
||||||
|
|
||||||
|
return depth, pos
|
||||||
|
|
||||||
# part 1
|
yield prod(depth_and_position(False))
|
||||||
answer_1 = prod(depth_and_position(False))
|
yield prod(depth_and_position(True))
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
|
||||||
answer_2 = prod(depth_and_position(True))
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# part 1
|
|
||||||
answer_1 = ...
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
class Solver(BaseSolver):
|
||||||
answer_2 = ...
|
def solve(self, input: str) -> Iterator[Any]: ...
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import sys
|
|
||||||
from collections import Counter
|
from collections import Counter
|
||||||
from typing import Literal
|
from typing import Any, Iterator, Literal
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def generator_rating(
|
def generator_rating(
|
||||||
@ -20,20 +21,23 @@ def generator_rating(
|
|||||||
return values[0]
|
return values[0]
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
|
# part 1
|
||||||
|
most_and_least_common = [
|
||||||
|
tuple(
|
||||||
|
Counter(line[col] for line in lines).most_common(2)[m][0]
|
||||||
|
for m in range(2)
|
||||||
|
)
|
||||||
|
for col in range(len(lines[0]))
|
||||||
|
]
|
||||||
|
gamma_rate = int("".join(most for most, _ in most_and_least_common), base=2)
|
||||||
|
epsilon_rate = int("".join(least for _, least in most_and_least_common), base=2)
|
||||||
|
yield gamma_rate * epsilon_rate
|
||||||
|
|
||||||
# part 1
|
# part 2
|
||||||
most_and_least_common = [
|
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
|
||||||
tuple(Counter(line[col] for line in lines).most_common(2)[m][0] for m in range(2))
|
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
|
||||||
for col in range(len(lines[0]))
|
yield oxygen_generator_rating * co2_scrubber_rating
|
||||||
]
|
|
||||||
gamma_rate = int("".join(most for most, _ in most_and_least_common), base=2)
|
|
||||||
epsilon_rate = int("".join(least for _, least in most_and_least_common), base=2)
|
|
||||||
print(f"answer 1 is {gamma_rate * epsilon_rate}")
|
|
||||||
|
|
||||||
# part 2
|
|
||||||
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
|
|
||||||
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
|
|
||||||
answer_2 = oxygen_generator_rating * co2_scrubber_rating
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,45 +1,52 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
numbers = [int(c) for c in lines[0].split(",")]
|
|
||||||
|
|
||||||
boards = np.asarray(
|
class Solver(BaseSolver):
|
||||||
[
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
[[int(c) for c in line.split()] for line in lines[start : start + 5]]
|
lines = input.splitlines()
|
||||||
for start in range(2, len(lines), 6)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
# (round, score) for each board (-1 when not found)
|
numbers = [int(c) for c in lines[0].split(",")]
|
||||||
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
|
|
||||||
marked = np.zeros_like(boards, dtype=bool)
|
|
||||||
|
|
||||||
for round, number in enumerate(numbers):
|
boards = np.asarray(
|
||||||
# mark boards
|
[
|
||||||
marked[boards == number] = True
|
[[int(c) for c in line.split()] for line in lines[start : start + 5]]
|
||||||
|
for start in range(2, len(lines), 6)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
# check each board for winning
|
# (round, score) for each board (-1 when not found)
|
||||||
for index in range(len(boards)):
|
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
|
||||||
if winning_rounds[index][0] > 0:
|
marked = np.zeros_like(boards, dtype=bool)
|
||||||
continue
|
|
||||||
|
|
||||||
if np.any(np.all(marked[index], axis=0) | np.all(marked[index], axis=1)):
|
for round, number in enumerate(numbers):
|
||||||
winning_rounds[index] = (
|
# mark boards
|
||||||
round,
|
marked[boards == number] = True
|
||||||
number * int(np.sum(boards[index][~marked[index]])),
|
|
||||||
)
|
|
||||||
|
|
||||||
# all boards are winning - break
|
# check each board for winning
|
||||||
if np.all(marked.all(axis=1) | marked.all(axis=2)):
|
for index in range(len(boards)):
|
||||||
break
|
if winning_rounds[index][0] > 0:
|
||||||
|
continue
|
||||||
|
|
||||||
# part 1
|
if np.any(
|
||||||
(_, score) = min(winning_rounds, key=lambda w: w[0])
|
np.all(marked[index], axis=0) | np.all(marked[index], axis=1)
|
||||||
print(f"answer 1 is {score}")
|
):
|
||||||
|
winning_rounds[index] = (
|
||||||
|
round,
|
||||||
|
number * int(np.sum(boards[index][~marked[index]])),
|
||||||
|
)
|
||||||
|
|
||||||
# part 2
|
# all boards are winning - break
|
||||||
(_, score) = max(winning_rounds, key=lambda w: w[0])
|
if np.all(marked.all(axis=1) | marked.all(axis=2)):
|
||||||
print(f"answer 2 is {score}")
|
break
|
||||||
|
|
||||||
|
# part 1
|
||||||
|
(_, score) = min(winning_rounds, key=lambda w: w[0])
|
||||||
|
yield score
|
||||||
|
|
||||||
|
# part 2
|
||||||
|
(_, score) = max(winning_rounds, key=lambda w: w[0])
|
||||||
|
yield score
|
||||||
|
@ -1,48 +1,48 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
lines: list[str] = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
|
|
||||||
(
|
|
||||||
(
|
|
||||||
int(line.split(" -> ")[0].split(",")[0]),
|
|
||||||
int(line.split(" -> ")[0].split(",")[1]),
|
|
||||||
),
|
|
||||||
(
|
|
||||||
int(line.split(" -> ")[1].split(",")[0]),
|
|
||||||
int(line.split(" -> ")[1].split(",")[1]),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
for line in lines
|
|
||||||
]
|
|
||||||
|
|
||||||
np_sections = np.array(sections).reshape(-1, 4)
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
x_min, x_max, y_min, y_max = (
|
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
|
||||||
min(np_sections[:, 0].min(), np_sections[:, 2].min()),
|
(
|
||||||
max(np_sections[:, 0].max(), np_sections[:, 2].max()),
|
(
|
||||||
min(np_sections[:, 1].min(), np_sections[:, 3].min()),
|
int(line.split(" -> ")[0].split(",")[0]),
|
||||||
max(np_sections[:, 1].max(), np_sections[:, 3].max()),
|
int(line.split(" -> ")[0].split(",")[1]),
|
||||||
)
|
),
|
||||||
|
(
|
||||||
|
int(line.split(" -> ")[1].split(",")[0]),
|
||||||
|
int(line.split(" -> ")[1].split(",")[1]),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
for line in lines
|
||||||
|
]
|
||||||
|
|
||||||
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
|
np_sections = np.array(sections).reshape(-1, 4)
|
||||||
counts_2 = counts_1.copy()
|
|
||||||
|
|
||||||
for (x1, y1), (x2, y2) in sections:
|
x_max, y_max = (
|
||||||
x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1)
|
max(np_sections[:, 0].max(), np_sections[:, 2].max()),
|
||||||
y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1)
|
max(np_sections[:, 1].max(), np_sections[:, 3].max()),
|
||||||
|
)
|
||||||
|
|
||||||
if x1 == x2 or y1 == y2:
|
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
|
||||||
counts_1[list(y_rng), list(x_rng)] += 1
|
counts_2 = counts_1.copy()
|
||||||
counts_2[list(y_rng), list(x_rng)] += 1
|
|
||||||
elif abs(x2 - x1) == abs(y2 - y1):
|
|
||||||
for i, j in zip(y_rng, x_rng):
|
|
||||||
counts_2[i, j] += 1
|
|
||||||
|
|
||||||
answer_1 = (counts_1 >= 2).sum()
|
for (x1, y1), (x2, y2) in sections:
|
||||||
print(f"answer 1 is {answer_1}")
|
x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1)
|
||||||
|
y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1)
|
||||||
|
|
||||||
answer_2 = (counts_2 >= 2).sum()
|
if x1 == x2 or y1 == y2:
|
||||||
print(f"answer 2 is {answer_2}")
|
counts_1[list(y_rng), list(x_rng)] += 1
|
||||||
|
counts_2[list(y_rng), list(x_rng)] += 1
|
||||||
|
elif abs(x2 - x1) == abs(y2 - y1):
|
||||||
|
for i, j in zip(y_rng, x_rng):
|
||||||
|
counts_2[i, j] += 1
|
||||||
|
|
||||||
|
yield (counts_1 >= 2).sum()
|
||||||
|
yield (counts_2 >= 2).sum()
|
||||||
|
@ -1,21 +1,21 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
values = [int(c) for c in sys.stdin.read().strip().split(",")]
|
from ..base import BaseSolver
|
||||||
|
|
||||||
days = 256
|
|
||||||
lanterns = {day: 0 for day in range(days)}
|
|
||||||
for value in values:
|
|
||||||
for day in range(value, days, 7):
|
|
||||||
lanterns[day] += 1
|
|
||||||
|
|
||||||
for day in range(days):
|
class Solver(BaseSolver):
|
||||||
for day2 in range(day + 9, days, 7):
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
lanterns[day2] += lanterns[day]
|
values = [int(c) for c in input.split(",")]
|
||||||
|
|
||||||
# part 1
|
days = 256
|
||||||
answer_1 = sum(v for k, v in lanterns.items() if k < 80) + len(values)
|
lanterns = {day: 0 for day in range(days)}
|
||||||
print(f"answer 1 is {answer_1}")
|
for value in values:
|
||||||
|
for day in range(value, days, 7):
|
||||||
|
lanterns[day] += 1
|
||||||
|
|
||||||
# part 2
|
for day in range(days):
|
||||||
answer_2 = sum(lanterns.values()) + len(values)
|
for day2 in range(day + 9, days, 7):
|
||||||
print(f"answer 2 is {answer_2}")
|
lanterns[day2] += lanterns[day]
|
||||||
|
|
||||||
|
yield sum(v for k, v in lanterns.items() if k < 80) + len(values)
|
||||||
|
yield sum(lanterns.values()) + len(values)
|
||||||
|
@ -1,19 +1,22 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
positions = [int(c) for c in sys.stdin.read().strip().split(",")]
|
from ..base import BaseSolver
|
||||||
|
|
||||||
min_position, max_position = min(positions), max(positions)
|
|
||||||
|
|
||||||
# part 1
|
class Solver(BaseSolver):
|
||||||
answer_1 = min(
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
sum(abs(p - position) for p in positions)
|
positions = [int(c) for c in input.split(",")]
|
||||||
for position in range(min_position, max_position + 1)
|
|
||||||
)
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
min_position, max_position = min(positions), max(positions)
|
||||||
answer_2 = min(
|
|
||||||
sum(abs(p - position) * (abs(p - position) + 1) // 2 for p in positions)
|
# part 1
|
||||||
for position in range(min_position, max_position + 1)
|
yield min(
|
||||||
)
|
sum(abs(p - position) for p in positions)
|
||||||
print(f"answer 2 is {answer_2}")
|
for position in range(min_position, max_position + 1)
|
||||||
|
)
|
||||||
|
|
||||||
|
# part 2
|
||||||
|
yield min(
|
||||||
|
sum(abs(p - position) * (abs(p - position) + 1) // 2 for p in positions)
|
||||||
|
for position in range(min_position, max_position + 1)
|
||||||
|
)
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import os
|
from typing import Any, Iterator
|
||||||
import sys
|
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
|
|
||||||
digits = {
|
digits = {
|
||||||
"abcefg": 0,
|
"abcefg": 0,
|
||||||
@ -17,71 +16,74 @@ digits = {
|
|||||||
"abcdfg": 9,
|
"abcdfg": 9,
|
||||||
}
|
}
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
|
||||||
|
|
||||||
# part 1
|
class Solver(BaseSolver):
|
||||||
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
answer_1 = sum(
|
lines = input.splitlines()
|
||||||
len(p) in lengths for line in lines for p in line.split("|")[1].strip().split()
|
|
||||||
)
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
# part 1
|
||||||
values: list[int] = []
|
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
|
||||||
|
yield sum(
|
||||||
|
len(p) in lengths
|
||||||
|
for line in lines
|
||||||
|
for p in line.split("|")[1].strip().split()
|
||||||
|
)
|
||||||
|
|
||||||
for line in lines:
|
# part 2
|
||||||
parts = line.split("|")
|
values: list[int] = []
|
||||||
broken_digits = sorted(parts[0].strip().split(), key=len)
|
|
||||||
|
|
||||||
per_length = {
|
for line in lines:
|
||||||
k: list(v)
|
parts = line.split("|")
|
||||||
for k, v in itertools.groupby(sorted(broken_digits, key=len), key=len)
|
broken_digits = sorted(parts[0].strip().split(), key=len)
|
||||||
}
|
|
||||||
|
|
||||||
# a can be found immediately
|
per_length = {
|
||||||
a = next(u for u in per_length[3][0] if u not in per_length[2][0])
|
k: list(v)
|
||||||
|
for k, v in itertools.groupby(sorted(broken_digits, key=len), key=len)
|
||||||
|
}
|
||||||
|
|
||||||
# c and f have only two possible values corresponding to the single entry of
|
# a can be found immediately
|
||||||
# length 2
|
a = next(u for u in per_length[3][0] if u not in per_length[2][0])
|
||||||
cf = list(per_length[2][0])
|
|
||||||
|
|
||||||
# the only digit of length 4 contains bcdf, so we can deduce bd by removing cf
|
# c and f have only two possible values corresponding to the single entry of
|
||||||
bd = [u for u in per_length[4][0] if u not in cf]
|
# length 2
|
||||||
|
cf = list(per_length[2][0])
|
||||||
|
|
||||||
# the 3 digits of length 5 have a, d and g in common
|
# the only digit of length 4 contains bcdf, so we can deduce bd by removing cf
|
||||||
adg = [u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])]
|
bd = [u for u in per_length[4][0] if u not in cf]
|
||||||
|
|
||||||
# we can remove a
|
# the 3 digits of length 5 have a, d and g in common
|
||||||
dg = [u for u in adg if u != a]
|
adg = [
|
||||||
|
u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])
|
||||||
|
]
|
||||||
|
|
||||||
# we can deduce d and g
|
# we can remove a
|
||||||
d = next(u for u in dg if u in bd)
|
dg = [u for u in adg if u != a]
|
||||||
g = next(u for u in dg if u != d)
|
|
||||||
|
|
||||||
# then b
|
# we can deduce d and g
|
||||||
b = next(u for u in bd if u != d)
|
d = next(u for u in dg if u in bd)
|
||||||
|
g = next(u for u in dg if u != d)
|
||||||
|
|
||||||
# f is in the three 6-length digits, while c is only in 2
|
# then b
|
||||||
f = next(u for u in cf if all(u in p for p in per_length[6]))
|
b = next(u for u in bd if u != d)
|
||||||
|
|
||||||
# c is not f
|
# f is in the three 6-length digits, while c is only in 2
|
||||||
c = next(u for u in cf if u != f)
|
f = next(u for u in cf if all(u in p for p in per_length[6]))
|
||||||
|
|
||||||
# e is the last one
|
# c is not f
|
||||||
e = next(u for u in "abcdefg" if u not in {a, b, c, d, f, g})
|
c = next(u for u in cf if u != f)
|
||||||
|
|
||||||
mapping = dict(zip((a, b, c, d, e, f, g), "abcdefg"))
|
# e is the last one
|
||||||
|
e = next(u for u in "abcdefg" if u not in {a, b, c, d, f, g})
|
||||||
|
|
||||||
value = 0
|
mapping = dict(zip((a, b, c, d, e, f, g), "abcdefg"))
|
||||||
for number in parts[1].strip().split():
|
|
||||||
digit = "".join(sorted(mapping[c] for c in number))
|
|
||||||
value = 10 * value + digits[digit]
|
|
||||||
|
|
||||||
if VERBOSE:
|
value = 0
|
||||||
print(value)
|
for number in parts[1].strip().split():
|
||||||
|
digit = "".join(sorted(mapping[c] for c in number))
|
||||||
|
value = 10 * value + digits[digit]
|
||||||
|
|
||||||
values.append(value)
|
self.logger.info(f"value for '{line}' is {value}")
|
||||||
|
|
||||||
|
values.append(value)
|
||||||
|
|
||||||
answer_2 = sum(values)
|
yield sum(values)
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,18 +1,18 @@
|
|||||||
import sys
|
|
||||||
from math import prod
|
from math import prod
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
values = [[int(c) for c in row] for row in sys.stdin.read().splitlines()]
|
from ..base import BaseSolver
|
||||||
n_rows, n_cols = len(values), len(values[0])
|
|
||||||
|
|
||||||
|
|
||||||
def neighbors(point: tuple[int, int]):
|
def neighbors(point: tuple[int, int], n_rows: int, n_cols: int):
|
||||||
i, j = point
|
i, j = point
|
||||||
for di, dj in ((-1, 0), (+1, 0), (0, -1), (0, +1)):
|
for di, dj in ((-1, 0), (+1, 0), (0, -1), (0, +1)):
|
||||||
if 0 <= i + di < n_rows and 0 <= j + dj < n_cols:
|
if 0 <= i + di < n_rows and 0 <= j + dj < n_cols:
|
||||||
yield (i + di, j + dj)
|
yield (i + di, j + dj)
|
||||||
|
|
||||||
|
|
||||||
def basin(start: tuple[int, int]) -> set[tuple[int, int]]:
|
def basin(values: list[list[int]], start: tuple[int, int]) -> set[tuple[int, int]]:
|
||||||
|
n_rows, n_cols = len(values), len(values[0])
|
||||||
visited: set[tuple[int, int]] = set()
|
visited: set[tuple[int, int]] = set()
|
||||||
queue = [start]
|
queue = [start]
|
||||||
|
|
||||||
@ -23,22 +23,25 @@ def basin(start: tuple[int, int]) -> set[tuple[int, int]]:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
visited.add((i, j))
|
visited.add((i, j))
|
||||||
queue.extend(neighbors((i, j)))
|
queue.extend(neighbors((i, j), n_rows, n_cols))
|
||||||
|
|
||||||
return visited
|
return visited
|
||||||
|
|
||||||
|
|
||||||
low_points = [
|
class Solver(BaseSolver):
|
||||||
(i, j)
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
for i in range(n_rows)
|
values = [[int(c) for c in row] for row in input.splitlines()]
|
||||||
for j in range(n_cols)
|
n_rows, n_cols = len(values), len(values[0])
|
||||||
if all(values[ti][tj] > values[i][j] for ti, tj in neighbors((i, j)))
|
|
||||||
]
|
|
||||||
|
|
||||||
# part 1
|
low_points = [
|
||||||
answer_1 = sum(values[i][j] + 1 for i, j in low_points)
|
(i, j)
|
||||||
print(f"answer 1 is {answer_1}")
|
for i in range(n_rows)
|
||||||
|
for j in range(n_cols)
|
||||||
|
if all(
|
||||||
|
values[ti][tj] > values[i][j]
|
||||||
|
for ti, tj in neighbors((i, j), n_rows, n_cols)
|
||||||
|
)
|
||||||
|
]
|
||||||
|
|
||||||
# part 2
|
yield sum(values[i][j] + 1 for i, j in low_points)
|
||||||
answer_2 = prod(sorted(len(basin(point)) for point in low_points)[-3:])
|
yield prod(sorted(len(basin(values, point)) for point in low_points)[-3:])
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
blocks = sys.stdin.read().split("\n\n")
|
from ..base import BaseSolver
|
||||||
values = sorted(sum(map(int, block.split())) for block in blocks)
|
|
||||||
|
|
||||||
print(f"answer 1 is {values[-1]}")
|
|
||||||
print(f"answer 2 is {sum(values[-3:])}")
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
blocks = input.split("\n\n")
|
||||||
|
values = sorted(sum(map(int, block.split())) for block in blocks)
|
||||||
|
|
||||||
|
yield values[-1]
|
||||||
|
yield sum(values[-3:])
|
||||||
|
@ -1,38 +1,43 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
cycle = 1
|
|
||||||
x = 1
|
|
||||||
|
|
||||||
values = {cycle: x}
|
|
||||||
|
|
||||||
for line in lines:
|
|
||||||
cycle += 1
|
|
||||||
|
|
||||||
if line == "noop":
|
|
||||||
pass
|
|
||||||
else:
|
|
||||||
r = int(line.split()[1])
|
|
||||||
|
|
||||||
values[cycle] = x
|
|
||||||
|
|
||||||
cycle += 1
|
|
||||||
x += r
|
|
||||||
|
|
||||||
values[cycle] = x
|
|
||||||
|
|
||||||
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
|
|
||||||
for i in range(6):
|
class Solver(BaseSolver):
|
||||||
for j in range(40):
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
v = values[1 + i * 40 + j]
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
|
|
||||||
if j >= v - 1 and j <= v + 1:
|
cycle, x = 1, 1
|
||||||
print("#", end="")
|
values = {cycle: x}
|
||||||
else:
|
|
||||||
print(".", end="")
|
|
||||||
|
|
||||||
print()
|
for line in lines:
|
||||||
|
cycle += 1
|
||||||
|
|
||||||
|
if line == "noop":
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
r = int(line.split()[1])
|
||||||
|
|
||||||
|
values[cycle] = x
|
||||||
|
|
||||||
|
cycle += 1
|
||||||
|
x += r
|
||||||
|
|
||||||
|
values[cycle] = x
|
||||||
|
|
||||||
|
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
|
||||||
|
yield answer_1
|
||||||
|
|
||||||
|
yield (
|
||||||
|
"\n"
|
||||||
|
+ "\n".join(
|
||||||
|
"".join(
|
||||||
|
"#"
|
||||||
|
if j >= (v := values[1 + i * 40 + j]) - 1 and j <= v + 1
|
||||||
|
else "."
|
||||||
|
for j in range(40)
|
||||||
|
)
|
||||||
|
for i in range(6)
|
||||||
|
)
|
||||||
|
+ "\n"
|
||||||
|
)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import copy
|
import copy
|
||||||
import sys
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
from typing import Callable, Final, Mapping, Sequence
|
from typing import Any, Callable, Final, Iterator, Mapping, Sequence
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
class Monkey:
|
class Monkey:
|
||||||
@ -119,24 +120,28 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
|
|||||||
return sorted_levels[-2] * sorted_levels[-1]
|
return sorted_levels[-2] * sorted_levels[-1]
|
||||||
|
|
||||||
|
|
||||||
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
monkeys = [parse_monkey(block.splitlines()) for block in input.split("\n\n")]
|
||||||
|
|
||||||
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
|
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
|
||||||
answer_1 = monkey_business(
|
yield monkey_business(
|
||||||
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
|
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
|
||||||
)
|
)
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
|
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
|
||||||
# use the product of all "divisible by" test so that the test remains valid
|
# use the product of all "divisible by" test so that the test remains valid
|
||||||
#
|
#
|
||||||
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
|
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
|
||||||
#
|
#
|
||||||
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
|
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
|
||||||
# we use the product of all test value
|
# we use the product of all test value
|
||||||
#
|
#
|
||||||
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
|
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
|
||||||
answer_2 = monkey_business(
|
yield monkey_business(
|
||||||
run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
|
run(
|
||||||
)
|
copy.deepcopy(monkeys),
|
||||||
print(f"answer 2 is {answer_2}")
|
10_000,
|
||||||
|
me_worry_fn=lambda w: w % total_test_value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import heapq
|
import heapq
|
||||||
import sys
|
from typing import Any, Callable, Iterator, TypeVar
|
||||||
from typing import Callable, Iterator, TypeVar
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
Node = TypeVar("Node")
|
Node = TypeVar("Node")
|
||||||
|
|
||||||
@ -68,30 +69,6 @@ def make_path(parents: dict[Node, Node], start: Node, end: Node) -> list[Node] |
|
|||||||
return list(reversed(path))
|
return list(reversed(path))
|
||||||
|
|
||||||
|
|
||||||
def print_path(path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
|
|
||||||
end = path[-1]
|
|
||||||
|
|
||||||
graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
|
|
||||||
graph[end[0]][end[1]] = "E"
|
|
||||||
|
|
||||||
for i in range(0, len(path) - 1):
|
|
||||||
cr, cc = path[i]
|
|
||||||
nr, nc = path[i + 1]
|
|
||||||
|
|
||||||
if cr == nr and nc == cc - 1:
|
|
||||||
graph[cr][cc] = "<"
|
|
||||||
elif cr == nr and nc == cc + 1:
|
|
||||||
graph[cr][cc] = ">"
|
|
||||||
elif cr == nr - 1 and nc == cc:
|
|
||||||
graph[cr][cc] = "v"
|
|
||||||
elif cr == nr + 1 and nc == cc:
|
|
||||||
graph[cr][cc] = "^"
|
|
||||||
else:
|
|
||||||
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
|
|
||||||
|
|
||||||
print("\n".join("".join(row) for row in graph))
|
|
||||||
|
|
||||||
|
|
||||||
def neighbors(
|
def neighbors(
|
||||||
grid: list[list[int]], node: tuple[int, int], up: bool
|
grid: list[list[int]], node: tuple[int, int], up: bool
|
||||||
) -> Iterator[tuple[int, int]]:
|
) -> Iterator[tuple[int, int]]:
|
||||||
@ -118,46 +95,74 @@ def neighbors(
|
|||||||
|
|
||||||
# === main code ===
|
# === main code ===
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
|
||||||
|
|
||||||
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
|
class Solver(BaseSolver):
|
||||||
|
def print_path(self, path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
|
||||||
|
end = path[-1]
|
||||||
|
|
||||||
start: tuple[int, int] | None = None
|
graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
|
||||||
end: tuple[int, int] | None = None
|
graph[end[0]][end[1]] = "E"
|
||||||
|
|
||||||
# for part 2
|
for i in range(0, len(path) - 1):
|
||||||
start_s: list[tuple[int, int]] = []
|
cr, cc = path[i]
|
||||||
|
nr, nc = path[i + 1]
|
||||||
|
|
||||||
for i_row, row in enumerate(grid):
|
if cr == nr and nc == cc - 1:
|
||||||
for i_col, col in enumerate(row):
|
graph[cr][cc] = "<"
|
||||||
if chr(col + ord("a")) == "S":
|
elif cr == nr and nc == cc + 1:
|
||||||
start = (i_row, i_col)
|
graph[cr][cc] = ">"
|
||||||
start_s.append(start)
|
elif cr == nr - 1 and nc == cc:
|
||||||
elif chr(col + ord("a")) == "E":
|
graph[cr][cc] = "v"
|
||||||
end = (i_row, i_col)
|
elif cr == nr + 1 and nc == cc:
|
||||||
elif col == 0:
|
graph[cr][cc] = "^"
|
||||||
start_s.append((i_row, i_col))
|
else:
|
||||||
|
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
|
||||||
|
|
||||||
assert start is not None
|
for row in graph:
|
||||||
assert end is not None
|
self.logger.info("".join(row))
|
||||||
|
|
||||||
# fix values
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
grid[start[0]][start[1]] = 0
|
lines = input.splitlines()
|
||||||
grid[end[0]][end[1]] = ord("z") - ord("a")
|
|
||||||
|
|
||||||
|
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
|
||||||
|
|
||||||
lengths_1, parents_1 = dijkstra(
|
start: tuple[int, int] | None = None
|
||||||
start=start, neighbors=lambda n: neighbors(grid, n, True), cost=lambda lhs, rhs: 1
|
end: tuple[int, int] | None = None
|
||||||
)
|
|
||||||
path_1 = make_path(parents_1, start, end)
|
|
||||||
assert path_1 is not None
|
|
||||||
|
|
||||||
print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
|
# for part 2
|
||||||
|
start_s: list[tuple[int, int]] = []
|
||||||
|
|
||||||
print(f"answer 1 is {lengths_1[end] - 1}")
|
for i_row, row in enumerate(grid):
|
||||||
|
for i_col, col in enumerate(row):
|
||||||
|
if chr(col + ord("a")) == "S":
|
||||||
|
start = (i_row, i_col)
|
||||||
|
start_s.append(start)
|
||||||
|
elif chr(col + ord("a")) == "E":
|
||||||
|
end = (i_row, i_col)
|
||||||
|
elif col == 0:
|
||||||
|
start_s.append((i_row, i_col))
|
||||||
|
|
||||||
lengths_2, parents_2 = dijkstra(
|
assert start is not None
|
||||||
start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1
|
assert end is not None
|
||||||
)
|
|
||||||
answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s)
|
# fix values
|
||||||
print(f"answer 2 is {answer_2}")
|
grid[start[0]][start[1]] = 0
|
||||||
|
grid[end[0]][end[1]] = ord("z") - ord("a")
|
||||||
|
|
||||||
|
lengths_1, parents_1 = dijkstra(
|
||||||
|
start=start,
|
||||||
|
neighbors=lambda n: neighbors(grid, n, True),
|
||||||
|
cost=lambda lhs, rhs: 1,
|
||||||
|
)
|
||||||
|
path_1 = make_path(parents_1, start, end)
|
||||||
|
assert path_1 is not None
|
||||||
|
|
||||||
|
self.print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
|
||||||
|
yield lengths_1[end] - 1
|
||||||
|
|
||||||
|
lengths_2, _ = dijkstra(
|
||||||
|
start=end,
|
||||||
|
neighbors=lambda n: neighbors(grid, n, False),
|
||||||
|
cost=lambda lhs, rhs: 1,
|
||||||
|
)
|
||||||
|
yield min(lengths_2.get(start, float("inf")) for start in start_s)
|
||||||
|
@ -1,11 +1,8 @@
|
|||||||
import json
|
import json
|
||||||
import sys
|
|
||||||
from functools import cmp_to_key
|
from functools import cmp_to_key
|
||||||
from typing import TypeAlias, cast
|
from typing import Any, Iterator, TypeAlias, cast
|
||||||
|
|
||||||
blocks = sys.stdin.read().strip().split("\n\n")
|
from ..base import BaseSolver
|
||||||
|
|
||||||
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
|
|
||||||
|
|
||||||
Packet: TypeAlias = list[int | list["Packet"]]
|
Packet: TypeAlias = list[int | list["Packet"]]
|
||||||
|
|
||||||
@ -28,14 +25,18 @@ def compare(lhs: Packet, rhs: Packet) -> int:
|
|||||||
return len(rhs) - len(lhs)
|
return len(rhs) - len(lhs)
|
||||||
|
|
||||||
|
|
||||||
answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
|
class Solver(BaseSolver):
|
||||||
print(f"answer_1 is {answer_1}")
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
blocks = input.split("\n\n")
|
||||||
|
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
|
||||||
|
|
||||||
dividers = [[[2]], [[6]]]
|
yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
|
||||||
|
|
||||||
packets = [packet for packets in pairs for packet in packets]
|
dividers = [[[2]], [[6]]]
|
||||||
packets.extend(dividers)
|
|
||||||
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
|
|
||||||
|
|
||||||
d_index = [packets.index(d) + 1 for d in dividers]
|
packets = [packet for packets in pairs for packet in packets]
|
||||||
print(f"answer 2 is {d_index[0] * d_index[1]}")
|
packets.extend(dividers)
|
||||||
|
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
|
||||||
|
|
||||||
|
d_index = [packets.index(d) + 1 for d in dividers]
|
||||||
|
yield d_index[0] * d_index[1]
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import sys
|
|
||||||
from enum import Enum, auto
|
from enum import Enum, auto
|
||||||
from typing import Callable, cast
|
from typing import Any, Callable, Iterator, cast
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
class Cell(Enum):
|
class Cell(Enum):
|
||||||
@ -12,26 +13,6 @@ class Cell(Enum):
|
|||||||
return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self]
|
return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self]
|
||||||
|
|
||||||
|
|
||||||
def print_blocks(blocks: dict[tuple[int, int], Cell]):
|
|
||||||
"""
|
|
||||||
Print the given set of blocks on a grid.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
blocks: Set of blocks to print.
|
|
||||||
"""
|
|
||||||
x_min, y_min, x_max, y_max = (
|
|
||||||
min(x for x, _ in blocks),
|
|
||||||
0,
|
|
||||||
max(x for x, _ in blocks),
|
|
||||||
max(y for _, y in blocks),
|
|
||||||
)
|
|
||||||
|
|
||||||
for y in range(y_min, y_max + 1):
|
|
||||||
print(
|
|
||||||
"".join(str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1))
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def flow(
|
def flow(
|
||||||
blocks: dict[tuple[int, int], Cell],
|
blocks: dict[tuple[int, int], Cell],
|
||||||
stop_fn: Callable[[int, int], bool],
|
stop_fn: Callable[[int, int], bool],
|
||||||
@ -84,57 +65,75 @@ def flow(
|
|||||||
|
|
||||||
# === inputs ===
|
# === inputs ===
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
|
||||||
|
|
||||||
paths: list[list[tuple[int, int]]] = []
|
class Solver(BaseSolver):
|
||||||
for line in lines:
|
def print_blocks(self, blocks: dict[tuple[int, int], Cell]):
|
||||||
parts = line.split(" -> ")
|
"""
|
||||||
paths.append(
|
Print the given set of blocks on a grid.
|
||||||
[
|
|
||||||
cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
|
|
||||||
for part in parts
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
|
Args:
|
||||||
|
blocks: Set of blocks to print.
|
||||||
|
"""
|
||||||
|
x_min, y_min, x_max, y_max = (
|
||||||
|
min(x for x, _ in blocks),
|
||||||
|
0,
|
||||||
|
max(x for x, _ in blocks),
|
||||||
|
max(y for _, y in blocks),
|
||||||
|
)
|
||||||
|
|
||||||
blocks: dict[tuple[int, int], Cell] = {}
|
for y in range(y_min, y_max + 1):
|
||||||
for path in paths:
|
self.logger.info(
|
||||||
for start, end in zip(path[:-1], path[1:]):
|
"".join(
|
||||||
x_start = min(start[0], end[0])
|
str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1)
|
||||||
x_end = max(start[0], end[0]) + 1
|
)
|
||||||
y_start = min(start[1], end[1])
|
)
|
||||||
y_end = max(start[1], end[1]) + 1
|
|
||||||
|
|
||||||
for x in range(x_start, x_end):
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
for y in range(y_start, y_end):
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
blocks[x, y] = Cell.ROCK
|
|
||||||
|
|
||||||
print_blocks(blocks)
|
paths: list[list[tuple[int, int]]] = []
|
||||||
print()
|
for line in lines:
|
||||||
|
parts = line.split(" -> ")
|
||||||
|
paths.append(
|
||||||
|
[
|
||||||
|
cast(
|
||||||
|
tuple[int, int], tuple(int(c.strip()) for c in part.split(","))
|
||||||
|
)
|
||||||
|
for part in parts
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
x_min, y_min, x_max, y_max = (
|
blocks: dict[tuple[int, int], Cell] = {}
|
||||||
min(x for x, _ in blocks),
|
for path in paths:
|
||||||
0,
|
for start, end in zip(path[:-1], path[1:]):
|
||||||
max(x for x, _ in blocks),
|
x_start = min(start[0], end[0])
|
||||||
max(y for _, y in blocks),
|
x_end = max(start[0], end[0]) + 1
|
||||||
)
|
y_start = min(start[1], end[1])
|
||||||
|
y_end = max(start[1], end[1]) + 1
|
||||||
|
|
||||||
# === part 1 ===
|
for x in range(x_start, x_end):
|
||||||
|
for y in range(y_start, y_end):
|
||||||
|
blocks[x, y] = Cell.ROCK
|
||||||
|
|
||||||
blocks_1 = flow(
|
self.print_blocks(blocks)
|
||||||
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
|
|
||||||
)
|
|
||||||
print_blocks(blocks_1)
|
|
||||||
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
|
|
||||||
print()
|
|
||||||
|
|
||||||
# === part 2 ===
|
y_max = max(y for _, y in blocks)
|
||||||
|
|
||||||
blocks_2 = flow(
|
# === part 1 ===
|
||||||
blocks.copy(),
|
|
||||||
stop_fn=lambda x, y: x == 500 and y == 0,
|
blocks_1 = flow(
|
||||||
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
|
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
|
||||||
)
|
)
|
||||||
blocks_2[500, 0] = Cell.SAND
|
self.print_blocks(blocks_1)
|
||||||
print_blocks(blocks_2)
|
yield sum(v == Cell.SAND for v in blocks_1.values())
|
||||||
print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")
|
|
||||||
|
# === part 2 ===
|
||||||
|
|
||||||
|
blocks_2 = flow(
|
||||||
|
blocks.copy(),
|
||||||
|
stop_fn=lambda x, y: x == 500 and y == 0,
|
||||||
|
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
|
||||||
|
)
|
||||||
|
blocks_2[500, 0] = Cell.SAND
|
||||||
|
self.print_blocks(blocks_2)
|
||||||
|
yield sum(v == Cell.SAND for v in blocks_2.values())
|
||||||
|
@ -1,90 +1,95 @@
|
|||||||
import sys
|
import itertools as it
|
||||||
from typing import Any
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import parse # type: ignore
|
import parse # type: ignore
|
||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
def part1(sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int) -> int:
|
|
||||||
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(
|
class Solver(BaseSolver):
|
||||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
|
def part1(
|
||||||
) -> tuple[int, int, int]:
|
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int
|
||||||
from tqdm import trange
|
) -> 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(it.chain(*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():
|
for (sx, sy), (bx, by) in sensor_to_beacon.items():
|
||||||
d = abs(sx - bx) + abs(sy - by)
|
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, # type: ignore
|
||||||
|
ctname=f"ct_{sx}_{sy}",
|
||||||
|
)
|
||||||
|
|
||||||
if dx >= 0:
|
m.set_objective("min", x + y)
|
||||||
its.append((max(0, sx - dx), min(sx + dx, xy_max)))
|
|
||||||
|
|
||||||
its = sorted(its)
|
s = m.solve()
|
||||||
_, e = its[0]
|
assert s is not None
|
||||||
|
|
||||||
for si, ei in its[1:]:
|
vx = int(s.get_value(x))
|
||||||
if si > e + 1:
|
vy = int(s.get_value(y))
|
||||||
return si - 1, y, 4_000_000 * (si - 1) + y
|
return vx, vy, 4_000_000 * vx + vy
|
||||||
if ei > e:
|
|
||||||
e = ei
|
|
||||||
|
|
||||||
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(
|
for line in lines:
|
||||||
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
|
r: dict[str, str] = parse.parse( # type: ignore
|
||||||
) -> tuple[int, int, int]:
|
"Sensor at x={sx}, y={sy}: closest beacon is at x={bx}, y={by}", line
|
||||||
from docplex.mp.model import Model
|
)
|
||||||
|
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():
|
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
|
||||||
d = abs(sx - bx) + abs(sy - by)
|
x, y, a2 = self.part2_intervals(sensor_to_beacon, xy_max)
|
||||||
m.add_constraint(m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}") # type: ignore
|
self.logger.info(f"answer 2 is {a2} (x={x}, y={y})")
|
||||||
|
yield a2
|
||||||
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})")
|
|
||||||
|
@ -3,12 +3,13 @@ from __future__ import annotations
|
|||||||
import heapq
|
import heapq
|
||||||
import itertools
|
import itertools
|
||||||
import re
|
import re
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from typing import FrozenSet, NamedTuple
|
from typing import Any, FrozenSet, Iterator, NamedTuple
|
||||||
|
|
||||||
from tqdm import tqdm
|
from tqdm import tqdm
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
class Pipe(NamedTuple):
|
class Pipe(NamedTuple):
|
||||||
name: str
|
name: str
|
||||||
@ -36,8 +37,8 @@ def breadth_first_search(pipes: dict[str, Pipe], pipe: Pipe) -> dict[Pipe, int]:
|
|||||||
Runs a BFS from the given pipe and return the shortest distance (in term of hops)
|
Runs a BFS from the given pipe and return the shortest distance (in term of hops)
|
||||||
to all other pipes.
|
to all other pipes.
|
||||||
"""
|
"""
|
||||||
queue = [(0, pipe_1)]
|
queue = [(0, pipe)]
|
||||||
visited = set()
|
visited: set[Pipe] = set()
|
||||||
distances: dict[Pipe, int] = {}
|
distances: dict[Pipe, int] = {}
|
||||||
|
|
||||||
while len(distances) < len(pipes):
|
while len(distances) < len(pipes):
|
||||||
@ -122,37 +123,37 @@ def part_2(
|
|||||||
# === MAIN ===
|
# === MAIN ===
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
|
|
||||||
|
pipes: dict[str, Pipe] = {}
|
||||||
|
for line in lines:
|
||||||
|
r = re.match(
|
||||||
|
R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
|
||||||
|
line,
|
||||||
|
)
|
||||||
|
assert r
|
||||||
|
|
||||||
pipes: dict[str, Pipe] = {}
|
g = r.groups()
|
||||||
for line in lines:
|
|
||||||
r = re.match(
|
|
||||||
R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
|
|
||||||
line,
|
|
||||||
)
|
|
||||||
assert r
|
|
||||||
|
|
||||||
g = r.groups()
|
pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", "))
|
||||||
|
|
||||||
pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", "))
|
# compute distances from one valve to any other
|
||||||
|
distances: dict[tuple[Pipe, Pipe], int] = {}
|
||||||
|
for pipe_1 in pipes.values():
|
||||||
|
distances.update(
|
||||||
|
{
|
||||||
|
(pipe_1, pipe_2): distance
|
||||||
|
for pipe_2, distance in breadth_first_search(pipes, pipe_1).items()
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# compute distances from one valve to any other
|
# valves with flow
|
||||||
distances: dict[tuple[Pipe, Pipe], int] = {}
|
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
|
||||||
for pipe_1 in pipes.values():
|
|
||||||
distances.update(
|
|
||||||
{
|
|
||||||
(pipe_1, pipe_2): distance
|
|
||||||
for pipe_2, distance in breadth_first_search(pipes, pipe_1).items()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
# valves with flow
|
# 1651, 1653
|
||||||
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
|
yield part_1(pipes["AA"], 30, distances, relevant_pipes)
|
||||||
|
|
||||||
|
# 1707, 2223
|
||||||
# 1651, 1653
|
yield part_2(pipes["AA"], 26, distances, relevant_pipes)
|
||||||
print(part_1(pipes["AA"], 30, distances, relevant_pipes))
|
|
||||||
|
|
||||||
# 1707, 2223
|
|
||||||
print(part_2(pipes["AA"], 26, distances, relevant_pipes))
|
|
||||||
|
@ -1,12 +1,16 @@
|
|||||||
import sys
|
from typing import Any, Iterator, Sequence, TypeAlias, TypeVar
|
||||||
from typing import Sequence, TypeVar
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
T = TypeVar("T")
|
T = TypeVar("T")
|
||||||
|
|
||||||
|
Tower: TypeAlias = NDArray[np.bool]
|
||||||
|
|
||||||
def print_tower(tower: np.ndarray, out: str = "#"):
|
|
||||||
|
def print_tower(tower: Tower, out: str = "#"):
|
||||||
print("-" * (tower.shape[1] + 2))
|
print("-" * (tower.shape[1] + 2))
|
||||||
non_empty = False
|
non_empty = False
|
||||||
for row in reversed(range(1, tower.shape[0])):
|
for row in reversed(range(1, tower.shape[0])):
|
||||||
@ -17,7 +21,7 @@ def print_tower(tower: np.ndarray, out: str = "#"):
|
|||||||
print("+" + "-" * tower.shape[1] + "+")
|
print("+" + "-" * tower.shape[1] + "+")
|
||||||
|
|
||||||
|
|
||||||
def tower_height(tower: np.ndarray) -> int:
|
def tower_height(tower: Tower) -> int:
|
||||||
return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1)
|
return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1)
|
||||||
|
|
||||||
|
|
||||||
@ -45,8 +49,8 @@ def build_tower(
|
|||||||
n_rocks: int,
|
n_rocks: int,
|
||||||
jets: str,
|
jets: str,
|
||||||
early_stop: bool = False,
|
early_stop: bool = False,
|
||||||
init: np.ndarray = np.ones(WIDTH, dtype=bool),
|
init: Tower = np.ones(WIDTH, dtype=bool),
|
||||||
) -> tuple[np.ndarray, int, int, dict[int, int]]:
|
) -> tuple[Tower, int, int, dict[int, int]]:
|
||||||
tower = EMPTY_BLOCKS.copy()
|
tower = EMPTY_BLOCKS.copy()
|
||||||
tower[0, :] = init
|
tower[0, :] = init
|
||||||
|
|
||||||
@ -95,26 +99,24 @@ def build_tower(
|
|||||||
return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights
|
return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights
|
||||||
|
|
||||||
|
|
||||||
line = sys.stdin.read().strip()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
tower, *_ = build_tower(2022, input)
|
||||||
|
yield tower_height(tower)
|
||||||
|
|
||||||
tower, *_ = build_tower(2022, line)
|
TOTAL_ROCKS = 1_000_000_000_000
|
||||||
answer_1 = tower_height(tower)
|
_tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, input, True)
|
||||||
print(f"answer 1 is {answer_1}")
|
assert prev_1 > 0
|
||||||
|
|
||||||
TOTAL_ROCKS = 1_000_000_000_000
|
# 2767 1513
|
||||||
tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, line, True)
|
remaining_rocks = TOTAL_ROCKS - n_rocks_1
|
||||||
assert prev_1 > 0
|
n_repeat_rocks = n_rocks_1 - prev_1
|
||||||
|
n_repeat_towers = remaining_rocks // n_repeat_rocks
|
||||||
|
|
||||||
# 2767 1513
|
base_height = heights_1[prev_1]
|
||||||
remaining_rocks = TOTAL_ROCKS - n_rocks_1
|
repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
|
||||||
n_repeat_rocks = n_rocks_1 - prev_1
|
remaining_height = (
|
||||||
n_repeat_towers = remaining_rocks // n_repeat_rocks
|
heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
|
||||||
|
)
|
||||||
|
|
||||||
base_height = heights_1[prev_1]
|
yield base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
|
||||||
repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
|
|
||||||
remaining_height = (
|
|
||||||
heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
|
|
||||||
)
|
|
||||||
|
|
||||||
answer_2 = base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,50 +1,58 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
xyz = np.asarray(
|
from ..base import BaseSolver
|
||||||
[
|
|
||||||
tuple(int(x) for x in row.split(",")) # type: ignore
|
|
||||||
for row in sys.stdin.read().splitlines()
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
xyz = xyz - xyz.min(axis=0) + 1
|
|
||||||
|
|
||||||
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
|
class Solver(BaseSolver):
|
||||||
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
xyz = np.asarray(
|
||||||
|
[
|
||||||
|
tuple(int(x) for x in row.split(",")) # type: ignore
|
||||||
|
for row in input.splitlines()
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
n_dims = len(cubes.shape)
|
xyz = xyz - xyz.min(axis=0) + 1
|
||||||
|
|
||||||
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
|
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
|
||||||
|
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
|
||||||
|
|
||||||
answer_1 = sum(
|
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
|
||||||
1 for x, y, z in xyz for dx, dy, dz in faces if not cubes[x + dx, y + dy, z + dz]
|
|
||||||
)
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
visited = np.zeros_like(cubes, dtype=bool)
|
yield sum(
|
||||||
queue = [(0, 0, 0)]
|
1
|
||||||
|
for x, y, z in xyz
|
||||||
|
for dx, dy, dz in faces
|
||||||
|
if not cubes[x + dx, y + dy, z + dz]
|
||||||
|
)
|
||||||
|
|
||||||
n_faces = 0
|
visited = np.zeros_like(cubes, dtype=bool)
|
||||||
while queue:
|
queue = [(0, 0, 0)]
|
||||||
x, y, z = queue.pop(0)
|
|
||||||
|
|
||||||
if visited[x, y, z]:
|
n_faces = 0
|
||||||
continue
|
while queue:
|
||||||
|
x, y, z = queue.pop(0)
|
||||||
|
|
||||||
visited[x, y, z] = True
|
if visited[x, y, z]:
|
||||||
|
continue
|
||||||
|
|
||||||
for dx, dy, dz in faces:
|
visited[x, y, z] = True
|
||||||
nx, ny, nz = x + dx, y + dy, z + dz
|
|
||||||
if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if visited[nx, ny, nz]:
|
for dx, dy, dz in faces:
|
||||||
continue
|
nx, ny, nz = x + dx, y + dy, z + dz
|
||||||
|
if not all(
|
||||||
|
n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
if cubes[nx, ny, nz]:
|
if visited[nx, ny, nz]:
|
||||||
n_faces += 1
|
continue
|
||||||
else:
|
|
||||||
queue.append((nx, ny, nz))
|
if cubes[nx, ny, nz]:
|
||||||
print(f"answer 2 is {n_faces}")
|
n_faces += 1
|
||||||
|
else:
|
||||||
|
queue.append((nx, ny, nz))
|
||||||
|
|
||||||
|
yield n_faces
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import sys
|
from typing import Any, Iterator, Literal
|
||||||
from typing import Any, Literal
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import parse # pyright: ignore[reportMissingTypeStubs]
|
import parse # pyright: ignore[reportMissingTypeStubs]
|
||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
Reagent = Literal["ore", "clay", "obsidian", "geode"]
|
Reagent = Literal["ore", "clay", "obsidian", "geode"]
|
||||||
REAGENTS: tuple[Reagent, ...] = (
|
REAGENTS: tuple[Reagent, ...] = (
|
||||||
"ore",
|
"ore",
|
||||||
@ -62,29 +63,6 @@ def dominates(lhs: State, rhs: State):
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
|
||||||
|
|
||||||
blueprints: list[dict[Reagent, IntOfReagent]] = []
|
|
||||||
for line in lines:
|
|
||||||
r: list[int] = parse.parse( # type: ignore
|
|
||||||
"Blueprint {}: "
|
|
||||||
"Each ore robot costs {:d} ore. "
|
|
||||||
"Each clay robot costs {:d} ore. "
|
|
||||||
"Each obsidian robot costs {:d} ore and {:d} clay. "
|
|
||||||
"Each geode robot costs {:d} ore and {:d} obsidian.",
|
|
||||||
line,
|
|
||||||
)
|
|
||||||
|
|
||||||
blueprints.append(
|
|
||||||
{
|
|
||||||
"ore": {"ore": r[1]},
|
|
||||||
"clay": {"ore": r[2]},
|
|
||||||
"obsidian": {"ore": r[3], "clay": r[4]},
|
|
||||||
"geode": {"ore": r[5], "obsidian": r[6]},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
|
def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
|
||||||
# since we can only build one robot per time, we do not need more than X robots
|
# since we can only build one robot per time, we do not need more than X robots
|
||||||
# of type K where X is the maximum number of K required among all robots, e.g.,
|
# of type K where X is the maximum number of K required among all robots, e.g.,
|
||||||
@ -173,11 +151,31 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
|
|||||||
return max(state.reagents["geode"] for state in state_after_t[max_time])
|
return max(state.reagents["geode"] for state in state_after_t[max_time])
|
||||||
|
|
||||||
|
|
||||||
answer_1 = sum(
|
class Solver(BaseSolver):
|
||||||
(i_blueprint + 1) * run(blueprint, 24)
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
for i_blueprint, blueprint in enumerate(blueprints)
|
blueprints: list[dict[Reagent, IntOfReagent]] = []
|
||||||
)
|
for line in input.splitlines():
|
||||||
print(f"answer 1 is {answer_1}")
|
r: list[int] = parse.parse( # type: ignore
|
||||||
|
"Blueprint {}: "
|
||||||
|
"Each ore robot costs {:d} ore. "
|
||||||
|
"Each clay robot costs {:d} ore. "
|
||||||
|
"Each obsidian robot costs {:d} ore and {:d} clay. "
|
||||||
|
"Each geode robot costs {:d} ore and {:d} obsidian.",
|
||||||
|
line,
|
||||||
|
)
|
||||||
|
|
||||||
answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)
|
blueprints.append(
|
||||||
print(f"answer 2 is {answer_2}")
|
{
|
||||||
|
"ore": {"ore": r[1]},
|
||||||
|
"clay": {"ore": r[2]},
|
||||||
|
"obsidian": {"ore": r[3], "clay": r[4]},
|
||||||
|
"geode": {"ore": r[5], "obsidian": r[6]},
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
yield sum(
|
||||||
|
(i_blueprint + 1) * run(blueprint, 24)
|
||||||
|
for i_blueprint, blueprint in enumerate(blueprints)
|
||||||
|
)
|
||||||
|
|
||||||
|
yield (run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32))
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def score_1(ux: int, vx: int) -> int:
|
def score_1(ux: int, vx: int) -> int:
|
||||||
@ -33,21 +35,23 @@ def score_2(ux: int, vx: int) -> int:
|
|||||||
return (ux + vx - 1) % 3 + 1 + vx * 3
|
return (ux + vx - 1) % 3 + 1 + vx * 3
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.readlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
|
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
|
||||||
# modulo-3 arithmetic
|
# modulo-3 arithmetic
|
||||||
#
|
#
|
||||||
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
|
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
|
||||||
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
|
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
|
||||||
#
|
#
|
||||||
|
|
||||||
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
|
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
|
||||||
# easier manipulation
|
# easier manipulation
|
||||||
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
|
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
|
||||||
|
|
||||||
# part 1 - 13526
|
# part 1 - 13526
|
||||||
print(f"answer 1 is {sum(score_1(*v) for v in values)}")
|
yield sum(score_1(*v) for v in values)
|
||||||
|
|
||||||
# part 2 - 14204
|
# part 2 - 14204
|
||||||
print(f"answer 2 is {sum(score_2(*v) for v in values)}")
|
yield sum(score_2(*v) for v in values)
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
class Number:
|
class Number:
|
||||||
@ -65,10 +67,9 @@ def decrypt(numbers: list[Number], key: int, rounds: int) -> int:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
numbers = [Number(int(x)) for i, x in enumerate(sys.stdin.readlines())]
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
numbers = [Number(int(x)) for x in input.splitlines()]
|
||||||
|
|
||||||
answer_1 = decrypt(numbers, 1, 1)
|
yield decrypt(numbers, 1, 1)
|
||||||
print(f"answer 1 is {answer_1}")
|
yield decrypt(numbers, 811589153, 10)
|
||||||
|
|
||||||
answer_2 = decrypt(numbers, 811589153, 10)
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import operator
|
import operator
|
||||||
import sys
|
from typing import Any, Callable, Iterator
|
||||||
from typing import Callable
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
|
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
|
||||||
@ -77,31 +78,31 @@ def invert(
|
|||||||
return monkeys
|
return monkeys
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
|
|
||||||
monkeys: dict[str, int | tuple[str, str, str]] = {}
|
monkeys: dict[str, int | tuple[str, str, str]] = {}
|
||||||
|
|
||||||
op_monkeys: set[str] = set()
|
op_monkeys: set[str] = set()
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
parts = line.split(":")
|
parts = line.split(":")
|
||||||
name = parts[0].strip()
|
name = parts[0].strip()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
value = int(parts[1].strip())
|
value = int(parts[1].strip())
|
||||||
monkeys[name] = value
|
monkeys[name] = value
|
||||||
except ValueError:
|
except ValueError:
|
||||||
op1, ope, op2 = parts[1].strip().split()
|
op1, ope, op2 = parts[1].strip().split()
|
||||||
monkeys[name] = (op1, ope, op2)
|
monkeys[name] = (op1, ope, op2)
|
||||||
|
|
||||||
op_monkeys.add(name)
|
op_monkeys.add(name)
|
||||||
|
|
||||||
|
yield compute(monkeys.copy(), "root")
|
||||||
|
|
||||||
answer_1 = compute(monkeys.copy(), "root")
|
# assume the second operand of 'root' can be computed, and the first one depends on
|
||||||
print(f"answer 1 is {answer_1}")
|
# humn, which is the case is my input and the test input
|
||||||
|
assert isinstance(monkeys["root"], tuple)
|
||||||
# assume the second operand of 'root' can be computed, and the first one depends on
|
p1, _, p2 = monkeys["root"] # type: ignore
|
||||||
# humn, which is the case is my input and the test input
|
yield compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
|
||||||
p1, _, p2 = monkeys["root"] # type: ignore
|
|
||||||
answer_2 = compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,223 +1,243 @@
|
|||||||
import re
|
import re
|
||||||
import sys
|
from typing import Any, Callable, Iterator
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
VOID, EMPTY, WALL = 0, 1, 2
|
VOID, EMPTY, WALL = 0, 1, 2
|
||||||
TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}
|
TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}
|
||||||
|
|
||||||
SCORES = {"E": 0, "S": 1, "W": 2, "N": 3}
|
SCORES = {"E": 0, "S": 1, "W": 2, "N": 3}
|
||||||
|
|
||||||
|
|
||||||
board_map_s, direction_s = sys.stdin.read().split("\n\n")
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
board_map_s, direction_s = input.split("\n\n")
|
||||||
|
|
||||||
# board
|
# board
|
||||||
board_lines = board_map_s.splitlines()
|
board_lines = board_map_s.splitlines()
|
||||||
max_line = max(len(line) for line in board_lines)
|
max_line = max(len(line) for line in board_lines)
|
||||||
board = np.array(
|
board = np.array(
|
||||||
[
|
[
|
||||||
[TILE_FROM_CHAR[c] for c in row] + [VOID] * (max_line - len(row))
|
[TILE_FROM_CHAR[c] for c in row] + [VOID] * (max_line - len(row))
|
||||||
for row in board_map_s.splitlines()
|
for row in board_map_s.splitlines()
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
||||||
directions = [
|
directions = [
|
||||||
int(p1) if p2 else p1 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
|
int(p1) if p2 else p1
|
||||||
]
|
for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
|
||||||
|
]
|
||||||
|
|
||||||
|
# find on each row and column the first and last non-void
|
||||||
|
row_first_non_void = np.argmax(board != VOID, axis=1)
|
||||||
|
row_last_non_void = (
|
||||||
|
board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1
|
||||||
|
)
|
||||||
|
col_first_non_void = np.argmax(board != VOID, axis=0)
|
||||||
|
col_last_non_void = (
|
||||||
|
board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
|
||||||
|
)
|
||||||
|
|
||||||
# find on each row and column the first and last non-void
|
faces = np.zeros_like(board)
|
||||||
row_first_non_void = np.argmax(board != VOID, axis=1)
|
size = np.gcd(board.shape[0], board.shape[1])
|
||||||
row_last_non_void = board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1
|
for row in range(0, board.shape[0], size):
|
||||||
col_first_non_void = np.argmax(board != VOID, axis=0)
|
for col in range(row_first_non_void[row], row_last_non_void[row], size):
|
||||||
col_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
|
faces[row : row + size, col : col + size] = faces.max() + 1
|
||||||
|
|
||||||
|
SIZE = np.gcd(*board.shape)
|
||||||
|
|
||||||
faces = np.zeros_like(board)
|
# TODO: deduce this from the actual cube...
|
||||||
size = np.gcd(board.shape[0], board.shape[1])
|
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
|
||||||
for row in range(0, board.shape[0], size):
|
|
||||||
for col in range(row_first_non_void[row], row_last_non_void[row], size):
|
|
||||||
faces[row : row + size, col : col + size] = faces.max() + 1
|
|
||||||
|
|
||||||
SIZE = np.gcd(*board.shape)
|
if board.shape == (12, 16): # example
|
||||||
|
faces_wrap = {
|
||||||
|
1: {
|
||||||
|
"W": lambda y, x: (4, 4 + y, "S"), # 3N
|
||||||
|
"N": lambda y, x: (4, 11 - x, "S"), # 2N
|
||||||
|
"E": lambda y, x: (11 - y, 15, "W"), # 6E
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
"W": lambda y, x: (11, 19 - y, "N"), # 6S
|
||||||
|
"N": lambda y, x: (0, 11 - y, "S"), # 1N
|
||||||
|
"S": lambda y, x: (11, 11 - x, "N"), # 5S
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
"N": lambda y, x: (x - 4, 8, "E"), # 1W
|
||||||
|
"S": lambda y, x: (15 - x, 8, "E"), # 5W
|
||||||
|
},
|
||||||
|
4: {"E": lambda y, x: (8, 19 - y, "S")}, # 6N
|
||||||
|
5: {
|
||||||
|
"W": lambda y, x: (7, 15 - y, "N"), # 3S
|
||||||
|
"S": lambda y, x: (7, 11 - x, "N"), # 2S
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
"N": lambda y, x: (19 - x, 11, "W"), # 4E
|
||||||
|
"E": lambda y, x: (11 - y, 11, "W"), # 1E
|
||||||
|
"S": lambda y, x: (19 - x, 0, "E"), # 2W
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
# TODO: deduce this from the actual cube...
|
|
||||||
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
|
|
||||||
|
|
||||||
if board.shape == (12, 16): # example
|
|
||||||
faces_wrap = {
|
|
||||||
1: {
|
|
||||||
"W": lambda y, x: (4, 4 + y, "S"), # 3N
|
|
||||||
"N": lambda y, x: (4, 11 - x, "S"), # 2N
|
|
||||||
"E": lambda y, x: (11 - y, 15, "W"), # 6E
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
"W": lambda y, x: (11, 19 - y, "N"), # 6S
|
|
||||||
"N": lambda y, x: (0, 11 - y, "S"), # 1N
|
|
||||||
"S": lambda y, x: (11, 11 - x, "N"), # 5S
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
"N": lambda y, x: (x - 4, 8, "E"), # 1W
|
|
||||||
"S": lambda y, x: (15 - x, 8, "E"), # 5W
|
|
||||||
},
|
|
||||||
4: {"E": lambda y, x: (8, 19 - y, "S")}, # 6N
|
|
||||||
5: {
|
|
||||||
"W": lambda y, x: (7, 15 - y, "N"), # 3S
|
|
||||||
"S": lambda y, x: (7, 11 - x, "N"), # 2S
|
|
||||||
},
|
|
||||||
6: {
|
|
||||||
"N": lambda y, x: (19 - x, 11, "W"), # 4E
|
|
||||||
"E": lambda y, x: (11 - y, 11, "W"), # 1E
|
|
||||||
"S": lambda y, x: (19 - x, 0, "E"), # 2W
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
else:
|
|
||||||
faces_wrap = {
|
|
||||||
1: {
|
|
||||||
"W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W
|
|
||||||
"N": lambda y, x: (2 * SIZE + x, 0, "E"), # 6W
|
|
||||||
},
|
|
||||||
2: {
|
|
||||||
"N": lambda y, x: (4 * SIZE - 1, x - 2 * SIZE, "N"), # 6S
|
|
||||||
"E": lambda y, x: (3 * SIZE - y - 1, 2 * SIZE - 1, "W"), # 5E
|
|
||||||
"S": lambda y, x: (x - SIZE, 2 * SIZE - 1, "W"), # 3E
|
|
||||||
},
|
|
||||||
3: {
|
|
||||||
"W": lambda y, x: (2 * SIZE, y - SIZE, "S"), # 4N
|
|
||||||
"E": lambda y, x: (SIZE - 1, SIZE + y, "N"), # 2S
|
|
||||||
},
|
|
||||||
4: {
|
|
||||||
"W": lambda y, x: (3 * SIZE - y - 1, SIZE, "E"), # 1W
|
|
||||||
"N": lambda y, x: (SIZE + x, SIZE, "E"), # 3W
|
|
||||||
},
|
|
||||||
5: {
|
|
||||||
"E": lambda y, x: (3 * SIZE - y - 1, 3 * SIZE - 1, "W"), # 2E
|
|
||||||
"S": lambda y, x: (2 * SIZE + x, SIZE - 1, "W"), # 6E
|
|
||||||
},
|
|
||||||
6: {
|
|
||||||
"W": lambda y, x: (0, y - 2 * SIZE, "S"), # 1N
|
|
||||||
"E": lambda y, x: (3 * SIZE - 1, y - 2 * SIZE, "N"), # 5S
|
|
||||||
"S": lambda y, x: (0, x + 2 * SIZE, "S"), # 2N
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
|
|
||||||
if r0 == "E":
|
|
||||||
return y0, row_first_non_void[y0], r0
|
|
||||||
elif r0 == "S":
|
|
||||||
return col_first_non_void[x0], x0, r0
|
|
||||||
elif r0 == "W":
|
|
||||||
return y0, row_last_non_void[y0], r0
|
|
||||||
elif r0 == "N":
|
|
||||||
return col_last_non_void[x0], x0, r0
|
|
||||||
|
|
||||||
assert False
|
|
||||||
|
|
||||||
|
|
||||||
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
|
|
||||||
cube = faces[y0, x0]
|
|
||||||
assert r0 in faces_wrap[cube]
|
|
||||||
return faces_wrap[cube][r0](y0, x0)
|
|
||||||
|
|
||||||
|
|
||||||
def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int, str]:
|
|
||||||
y0 = 0
|
|
||||||
x0 = np.where(board[0] == EMPTY)[0][0]
|
|
||||||
r0 = "E"
|
|
||||||
|
|
||||||
for direction in directions:
|
|
||||||
if isinstance(direction, int):
|
|
||||||
while direction > 0:
|
|
||||||
if r0 == "E":
|
|
||||||
xi = np.where(board[y0, x0 + 1 : x0 + direction + 1] == WALL)[0]
|
|
||||||
if len(xi):
|
|
||||||
x0 = x0 + xi[0]
|
|
||||||
direction = 0
|
|
||||||
elif (
|
|
||||||
x0 + direction < board.shape[1]
|
|
||||||
and board[y0, x0 + direction] == EMPTY
|
|
||||||
):
|
|
||||||
x0 = x0 + direction
|
|
||||||
direction = 0
|
|
||||||
else:
|
|
||||||
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
|
||||||
if board[y0_t, x0_t] == WALL:
|
|
||||||
x0 = row_last_non_void[y0]
|
|
||||||
direction = 0
|
|
||||||
else:
|
|
||||||
direction = direction - (row_last_non_void[y0] - x0) - 1
|
|
||||||
y0, x0, r0 = y0_t, x0_t, r0_t
|
|
||||||
elif r0 == "S":
|
|
||||||
yi = np.where(board[y0 + 1 : y0 + direction + 1, x0] == WALL)[0]
|
|
||||||
if len(yi):
|
|
||||||
y0 = y0 + yi[0]
|
|
||||||
direction = 0
|
|
||||||
elif (
|
|
||||||
y0 + direction < board.shape[0]
|
|
||||||
and board[y0 + direction, x0] == EMPTY
|
|
||||||
):
|
|
||||||
y0 = y0 + direction
|
|
||||||
direction = 0
|
|
||||||
else:
|
|
||||||
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
|
||||||
if board[y0_t, x0_t] == WALL:
|
|
||||||
y0 = col_last_non_void[x0]
|
|
||||||
direction = 0
|
|
||||||
else:
|
|
||||||
direction = direction - (col_last_non_void[x0] - y0) - 1
|
|
||||||
y0, x0, r0 = y0_t, x0_t, r0_t
|
|
||||||
elif r0 == "W":
|
|
||||||
left = max(x0 - direction - 1, 0)
|
|
||||||
xi = np.where(board[y0, left:x0] == WALL)[0]
|
|
||||||
if len(xi):
|
|
||||||
x0 = left + xi[-1] + 1
|
|
||||||
direction = 0
|
|
||||||
elif x0 - direction >= 0 and board[y0, x0 - direction] == EMPTY:
|
|
||||||
x0 = x0 - direction
|
|
||||||
direction = 0
|
|
||||||
else:
|
|
||||||
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
|
||||||
if board[y0_t, x0_t] == WALL:
|
|
||||||
x0 = row_first_non_void[y0]
|
|
||||||
direction = 0
|
|
||||||
else:
|
|
||||||
direction = direction - (x0 - row_first_non_void[y0]) - 1
|
|
||||||
y0, x0, r0 = y0_t, x0_t, r0_t
|
|
||||||
elif r0 == "N":
|
|
||||||
top = max(y0 - direction - 1, 0)
|
|
||||||
yi = np.where(board[top:y0, x0] == WALL)[0]
|
|
||||||
if len(yi):
|
|
||||||
y0 = top + yi[-1] + 1
|
|
||||||
direction = 0
|
|
||||||
elif y0 - direction >= 0 and board[y0 - direction, x0] == EMPTY:
|
|
||||||
y0 = y0 - direction
|
|
||||||
direction = 0
|
|
||||||
else:
|
|
||||||
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
|
||||||
if board[y0_t, x0_t] == WALL:
|
|
||||||
y0 = col_first_non_void[x0]
|
|
||||||
direction = 0
|
|
||||||
else:
|
|
||||||
direction = direction - (y0 - col_first_non_void[x0]) - 1
|
|
||||||
y0, x0, r0 = y0_t, x0_t, r0_t
|
|
||||||
else:
|
else:
|
||||||
r0 = {
|
faces_wrap = {
|
||||||
"E": {"L": "N", "R": "S"},
|
1: {
|
||||||
"N": {"L": "W", "R": "E"},
|
"W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W
|
||||||
"W": {"L": "S", "R": "N"},
|
"N": lambda y, x: (2 * SIZE + x, 0, "E"), # 6W
|
||||||
"S": {"L": "E", "R": "W"},
|
},
|
||||||
}[r0][direction]
|
2: {
|
||||||
|
"N": lambda y, x: (4 * SIZE - 1, x - 2 * SIZE, "N"), # 6S
|
||||||
|
"E": lambda y, x: (3 * SIZE - y - 1, 2 * SIZE - 1, "W"), # 5E
|
||||||
|
"S": lambda y, x: (x - SIZE, 2 * SIZE - 1, "W"), # 3E
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
"W": lambda y, x: (2 * SIZE, y - SIZE, "S"), # 4N
|
||||||
|
"E": lambda y, x: (SIZE - 1, SIZE + y, "N"), # 2S
|
||||||
|
},
|
||||||
|
4: {
|
||||||
|
"W": lambda y, x: (3 * SIZE - y - 1, SIZE, "E"), # 1W
|
||||||
|
"N": lambda y, x: (SIZE + x, SIZE, "E"), # 3W
|
||||||
|
},
|
||||||
|
5: {
|
||||||
|
"E": lambda y, x: (3 * SIZE - y - 1, 3 * SIZE - 1, "W"), # 2E
|
||||||
|
"S": lambda y, x: (2 * SIZE + x, SIZE - 1, "W"), # 6E
|
||||||
|
},
|
||||||
|
6: {
|
||||||
|
"W": lambda y, x: (0, y - 2 * SIZE, "S"), # 1N
|
||||||
|
"E": lambda y, x: (3 * SIZE - 1, y - 2 * SIZE, "N"), # 5S
|
||||||
|
"S": lambda y, x: (0, x + 2 * SIZE, "S"), # 2N
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
return y0, x0, r0
|
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
|
||||||
|
if r0 == "E":
|
||||||
|
return y0, row_first_non_void[y0], r0
|
||||||
|
elif r0 == "S":
|
||||||
|
return col_first_non_void[x0], x0, r0
|
||||||
|
elif r0 == "W":
|
||||||
|
return y0, row_last_non_void[y0], r0
|
||||||
|
elif r0 == "N":
|
||||||
|
return col_last_non_void[x0], x0, r0
|
||||||
|
|
||||||
|
assert False
|
||||||
|
|
||||||
y1, x1, r1 = run(wrap_part_1)
|
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
|
||||||
answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
|
cube = faces[y0, x0]
|
||||||
print(f"answer 1 is {answer_1}")
|
assert r0 in faces_wrap[cube]
|
||||||
|
return faces_wrap[cube][r0](y0, x0)
|
||||||
|
|
||||||
y2, x2, r2 = run(wrap_part_2)
|
def run(
|
||||||
answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
|
wrap: Callable[[int, int, str], tuple[int, int, str]],
|
||||||
print(f"answer 2 is {answer_2}")
|
) -> tuple[int, int, str]:
|
||||||
|
y0 = 0
|
||||||
|
x0 = np.where(board[0] == EMPTY)[0][0]
|
||||||
|
r0 = "E"
|
||||||
|
|
||||||
|
for direction in directions:
|
||||||
|
if isinstance(direction, int):
|
||||||
|
while direction > 0:
|
||||||
|
if r0 == "E":
|
||||||
|
xi = np.where(
|
||||||
|
board[y0, x0 + 1 : x0 + direction + 1] == WALL
|
||||||
|
)[0]
|
||||||
|
if len(xi):
|
||||||
|
x0 = x0 + xi[0]
|
||||||
|
direction = 0
|
||||||
|
elif (
|
||||||
|
x0 + direction < board.shape[1]
|
||||||
|
and board[y0, x0 + direction] == EMPTY
|
||||||
|
):
|
||||||
|
x0 = x0 + direction
|
||||||
|
direction = 0
|
||||||
|
else:
|
||||||
|
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
||||||
|
if board[y0_t, x0_t] == WALL:
|
||||||
|
x0 = row_last_non_void[y0]
|
||||||
|
direction = 0
|
||||||
|
else:
|
||||||
|
direction = (
|
||||||
|
direction - (row_last_non_void[y0] - x0) - 1
|
||||||
|
)
|
||||||
|
y0, x0, r0 = y0_t, x0_t, r0_t
|
||||||
|
elif r0 == "S":
|
||||||
|
yi = np.where(
|
||||||
|
board[y0 + 1 : y0 + direction + 1, x0] == WALL
|
||||||
|
)[0]
|
||||||
|
if len(yi):
|
||||||
|
y0 = y0 + yi[0]
|
||||||
|
direction = 0
|
||||||
|
elif (
|
||||||
|
y0 + direction < board.shape[0]
|
||||||
|
and board[y0 + direction, x0] == EMPTY
|
||||||
|
):
|
||||||
|
y0 = y0 + direction
|
||||||
|
direction = 0
|
||||||
|
else:
|
||||||
|
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
||||||
|
if board[y0_t, x0_t] == WALL:
|
||||||
|
y0 = col_last_non_void[x0]
|
||||||
|
direction = 0
|
||||||
|
else:
|
||||||
|
direction = (
|
||||||
|
direction - (col_last_non_void[x0] - y0) - 1
|
||||||
|
)
|
||||||
|
y0, x0, r0 = y0_t, x0_t, r0_t
|
||||||
|
elif r0 == "W":
|
||||||
|
left = max(x0 - direction - 1, 0)
|
||||||
|
xi = np.where(board[y0, left:x0] == WALL)[0]
|
||||||
|
if len(xi):
|
||||||
|
x0 = left + xi[-1] + 1
|
||||||
|
direction = 0
|
||||||
|
elif (
|
||||||
|
x0 - direction >= 0
|
||||||
|
and board[y0, x0 - direction] == EMPTY
|
||||||
|
):
|
||||||
|
x0 = x0 - direction
|
||||||
|
direction = 0
|
||||||
|
else:
|
||||||
|
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
||||||
|
if board[y0_t, x0_t] == WALL:
|
||||||
|
x0 = row_first_non_void[y0]
|
||||||
|
direction = 0
|
||||||
|
else:
|
||||||
|
direction = (
|
||||||
|
direction - (x0 - row_first_non_void[y0]) - 1
|
||||||
|
)
|
||||||
|
y0, x0, r0 = y0_t, x0_t, r0_t
|
||||||
|
elif r0 == "N":
|
||||||
|
top = max(y0 - direction - 1, 0)
|
||||||
|
yi = np.where(board[top:y0, x0] == WALL)[0]
|
||||||
|
if len(yi):
|
||||||
|
y0 = top + yi[-1] + 1
|
||||||
|
direction = 0
|
||||||
|
elif (
|
||||||
|
y0 - direction >= 0
|
||||||
|
and board[y0 - direction, x0] == EMPTY
|
||||||
|
):
|
||||||
|
y0 = y0 - direction
|
||||||
|
direction = 0
|
||||||
|
else:
|
||||||
|
y0_t, x0_t, r0_t = wrap(y0, x0, r0)
|
||||||
|
if board[y0_t, x0_t] == WALL:
|
||||||
|
y0 = col_first_non_void[x0]
|
||||||
|
direction = 0
|
||||||
|
else:
|
||||||
|
direction = (
|
||||||
|
direction - (y0 - col_first_non_void[x0]) - 1
|
||||||
|
)
|
||||||
|
y0, x0, r0 = y0_t, x0_t, r0_t
|
||||||
|
else:
|
||||||
|
r0 = {
|
||||||
|
"E": {"L": "N", "R": "S"},
|
||||||
|
"N": {"L": "W", "R": "E"},
|
||||||
|
"W": {"L": "S", "R": "N"},
|
||||||
|
"S": {"L": "E", "R": "W"},
|
||||||
|
}[r0][direction]
|
||||||
|
|
||||||
|
return y0, x0, r0
|
||||||
|
|
||||||
|
y1, x1, r1 = run(wrap_part_1)
|
||||||
|
yield 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
|
||||||
|
|
||||||
|
y2, x2, r2 = run(wrap_part_2)
|
||||||
|
yield 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
Directions = list[
|
Directions = list[
|
||||||
tuple[
|
tuple[
|
||||||
@ -18,7 +20,7 @@ DIRECTIONS: Directions = [
|
|||||||
|
|
||||||
|
|
||||||
def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]:
|
def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]:
|
||||||
ys, xs = {y for y, x in positions}, {x for y, x in positions}
|
ys, xs = {y for y, _x in positions}, {x for _y, x in positions}
|
||||||
return min(ys), min(xs), max(ys), max(xs)
|
return min(ys), min(xs), max(ys), max(xs)
|
||||||
|
|
||||||
|
|
||||||
@ -69,35 +71,38 @@ def round(
|
|||||||
directions.append(directions.pop(0))
|
directions.append(directions.pop(0))
|
||||||
|
|
||||||
|
|
||||||
POSITIONS = {
|
class Solver(BaseSolver):
|
||||||
(i, j)
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
for i, row in enumerate(sys.stdin.read().splitlines())
|
POSITIONS = {
|
||||||
for j, col in enumerate(row)
|
(i, j)
|
||||||
if col == "#"
|
for i, row in enumerate(input.splitlines())
|
||||||
}
|
for j, col in enumerate(row)
|
||||||
|
if col == "#"
|
||||||
|
}
|
||||||
|
|
||||||
# === part 1 ===
|
# === part 1 ===
|
||||||
|
|
||||||
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
|
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
|
||||||
for r in range(10):
|
for _ in range(10):
|
||||||
round(p1, d1)
|
round(p1, d1)
|
||||||
|
|
||||||
min_y, min_x, max_y, max_x = min_max_yx(p1)
|
min_y, min_x, max_y, max_x = min_max_yx(p1)
|
||||||
answer_1 = sum(
|
yield sum(
|
||||||
(y, x) not in p1 for y in range(min_y, max_y + 1) for x in range(min_x, max_x + 1)
|
(y, x) not in p1
|
||||||
)
|
for y in range(min_y, max_y + 1)
|
||||||
print(f"answer 1 is {answer_1}")
|
for x in range(min_x, max_x + 1)
|
||||||
|
)
|
||||||
|
|
||||||
# === part 2 ===
|
# === part 2 ===
|
||||||
|
|
||||||
p2, d2 = POSITIONS.copy(), DIRECTIONS.copy()
|
p2, d2 = POSITIONS.copy(), DIRECTIONS.copy()
|
||||||
answer_2 = 0
|
answer_2 = 0
|
||||||
while True:
|
while True:
|
||||||
answer_2 += 1
|
answer_2 += 1
|
||||||
backup = p2.copy()
|
backup = p2.copy()
|
||||||
round(p2, d2)
|
round(p2, d2)
|
||||||
|
|
||||||
if backup == p2:
|
if backup == p2:
|
||||||
break
|
break
|
||||||
|
|
||||||
print(f"answer 2 is {answer_2}")
|
yield answer_2
|
||||||
|
@ -1,98 +1,117 @@
|
|||||||
import heapq
|
import heapq
|
||||||
import math
|
import math
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
winds = {
|
|
||||||
(i - 1, j - 1, lines[i][j])
|
|
||||||
for i in range(1, len(lines) - 1)
|
|
||||||
for j in range(1, len(lines[i]) - 1)
|
|
||||||
if lines[i][j] != "."
|
|
||||||
}
|
|
||||||
|
|
||||||
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
|
|
||||||
CYCLE = math.lcm(n_rows, n_cols)
|
|
||||||
|
|
||||||
east_winds = [{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)]
|
|
||||||
west_winds = [{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)]
|
|
||||||
north_winds = [
|
|
||||||
{i for i in range(n_rows) if (i, j, "^") in winds} for j in range(n_cols)
|
|
||||||
]
|
|
||||||
south_winds = [
|
|
||||||
{i for i in range(n_rows) if (i, j, "v") in winds} for j in range(n_cols)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
|
class Solver(BaseSolver):
|
||||||
def heuristic(y: int, x: int) -> int:
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
return abs(end[0] - y) + abs(end[1] - x)
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
|
|
||||||
# (distance + heuristic, distance, (start_pos, cycle))
|
winds = {
|
||||||
queue = [(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))]
|
(i - 1, j - 1, lines[i][j])
|
||||||
visited: set[tuple[tuple[int, int], int]] = set()
|
for i in range(1, len(lines) - 1)
|
||||||
distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {})
|
for j in range(1, len(lines[i]) - 1)
|
||||||
|
if lines[i][j] != "."
|
||||||
|
}
|
||||||
|
|
||||||
while queue:
|
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
|
||||||
_, distance, ((y, x), cycle) = heapq.heappop(queue)
|
CYCLE = math.lcm(n_rows, n_cols)
|
||||||
|
|
||||||
if ((y, x), cycle) in visited:
|
east_winds = [
|
||||||
continue
|
{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)
|
||||||
|
]
|
||||||
|
west_winds = [
|
||||||
|
{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)
|
||||||
|
]
|
||||||
|
north_winds = [
|
||||||
|
{i for i in range(n_rows) if (i, j, "^") in winds} for j in range(n_cols)
|
||||||
|
]
|
||||||
|
south_winds = [
|
||||||
|
{i for i in range(n_rows) if (i, j, "v") in winds} for j in range(n_cols)
|
||||||
|
]
|
||||||
|
|
||||||
distances[y, x][cycle] = distance
|
def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
|
||||||
|
def heuristic(y: int, x: int) -> int:
|
||||||
|
return abs(end[0] - y) + abs(end[1] - x)
|
||||||
|
|
||||||
visited.add(((y, x), cycle))
|
# (distance + heuristic, distance, (start_pos, cycle))
|
||||||
|
queue = [
|
||||||
|
(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))
|
||||||
|
]
|
||||||
|
visited: set[tuple[tuple[int, int], int]] = set()
|
||||||
|
distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {})
|
||||||
|
|
||||||
if (y, x) == (end[0], end[1]):
|
while queue:
|
||||||
break
|
_, distance, ((y, x), cycle) = heapq.heappop(queue)
|
||||||
|
|
||||||
for dy, dx in (0, 0), (-1, 0), (1, 0), (0, -1), (0, 1):
|
if ((y, x), cycle) in visited:
|
||||||
ty = y + dy
|
|
||||||
tx = x + dx
|
|
||||||
|
|
||||||
n_cycle = (cycle + 1) % CYCLE
|
|
||||||
|
|
||||||
if (ty, tx) == end:
|
|
||||||
heapq.heappush(queue, (distance + 1, distance + 1, ((ty, tx), n_cycle)))
|
|
||||||
break
|
|
||||||
|
|
||||||
if ((ty, tx), n_cycle) in visited:
|
|
||||||
continue
|
|
||||||
|
|
||||||
if (ty, tx) != start and (ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols):
|
|
||||||
continue
|
|
||||||
|
|
||||||
if (ty, tx) != start:
|
|
||||||
if (ty - n_cycle) % n_rows in south_winds[tx]:
|
|
||||||
continue
|
|
||||||
if (ty + n_cycle) % n_rows in north_winds[tx]:
|
|
||||||
continue
|
|
||||||
if (tx + n_cycle) % n_cols in west_winds[ty]:
|
|
||||||
continue
|
|
||||||
if (tx - n_cycle) % n_cols in east_winds[ty]:
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
heapq.heappush(
|
distances[y, x][cycle] = distance
|
||||||
queue,
|
|
||||||
((heuristic(ty, tx) + distance + 1, distance + 1, ((ty, tx), n_cycle))),
|
|
||||||
)
|
|
||||||
|
|
||||||
return distances, next(iter(distances[end].values()))
|
visited.add(((y, x), cycle))
|
||||||
|
|
||||||
|
if (y, x) == (end[0], end[1]):
|
||||||
|
break
|
||||||
|
|
||||||
start = (
|
for dy, dx in (0, 0), (-1, 0), (1, 0), (0, -1), (0, 1):
|
||||||
-1,
|
ty = y + dy
|
||||||
next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
|
tx = x + dx
|
||||||
)
|
|
||||||
end = (
|
|
||||||
n_rows,
|
|
||||||
next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1,
|
|
||||||
)
|
|
||||||
|
|
||||||
distances_1, forward_1 = run(start, 0, end)
|
n_cycle = (cycle + 1) % CYCLE
|
||||||
print(f"answer 1 is {forward_1}")
|
|
||||||
|
|
||||||
distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start)
|
if (ty, tx) == end:
|
||||||
distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
|
heapq.heappush(
|
||||||
print(f"answer 2 is {forward_1 + return_1 + forward_2}")
|
queue, (distance + 1, distance + 1, ((ty, tx), n_cycle))
|
||||||
|
)
|
||||||
|
break
|
||||||
|
|
||||||
|
if ((ty, tx), n_cycle) in visited:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (ty, tx) != start and (
|
||||||
|
ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols
|
||||||
|
):
|
||||||
|
continue
|
||||||
|
|
||||||
|
if (ty, tx) != start:
|
||||||
|
if (ty - n_cycle) % n_rows in south_winds[tx]:
|
||||||
|
continue
|
||||||
|
if (ty + n_cycle) % n_rows in north_winds[tx]:
|
||||||
|
continue
|
||||||
|
if (tx + n_cycle) % n_cols in west_winds[ty]:
|
||||||
|
continue
|
||||||
|
if (tx - n_cycle) % n_cols in east_winds[ty]:
|
||||||
|
continue
|
||||||
|
|
||||||
|
heapq.heappush(
|
||||||
|
queue,
|
||||||
|
(
|
||||||
|
(
|
||||||
|
heuristic(ty, tx) + distance + 1,
|
||||||
|
distance + 1,
|
||||||
|
((ty, tx), n_cycle),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
return distances, next(iter(distances[end].values()))
|
||||||
|
|
||||||
|
start = (
|
||||||
|
-1,
|
||||||
|
next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
|
||||||
|
)
|
||||||
|
end = (
|
||||||
|
n_rows,
|
||||||
|
next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1,
|
||||||
|
)
|
||||||
|
|
||||||
|
distances_1, forward_1 = run(start, 0, end)
|
||||||
|
yield forward_1
|
||||||
|
|
||||||
|
distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start)
|
||||||
|
_distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
|
||||||
|
yield forward_1 + return_1 + forward_2
|
||||||
|
@ -1,27 +1,28 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
|
|
||||||
|
|
||||||
|
|
||||||
def snafu2number(number: str) -> int:
|
class Solver(BaseSolver):
|
||||||
value = 0
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
for c in number:
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
value *= 5
|
|
||||||
value += coeffs[c]
|
|
||||||
return value
|
|
||||||
|
|
||||||
|
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
|
||||||
|
|
||||||
def number2snafu(number: int) -> str:
|
def snafu2number(number: str) -> int:
|
||||||
values = ["0", "1", "2", "=", "-"]
|
value = 0
|
||||||
res = ""
|
for c in number:
|
||||||
while number > 0:
|
value *= 5
|
||||||
mod = number % 5
|
value += coeffs[c]
|
||||||
res = res + values[mod]
|
return value
|
||||||
number = number // 5 + int(mod >= 3)
|
|
||||||
return "".join(reversed(res))
|
|
||||||
|
|
||||||
|
def number2snafu(number: int) -> str:
|
||||||
|
values = ["0", "1", "2", "=", "-"]
|
||||||
|
res = ""
|
||||||
|
while number > 0:
|
||||||
|
mod = number % 5
|
||||||
|
res = res + values[mod]
|
||||||
|
number = number // 5 + int(mod >= 3)
|
||||||
|
return "".join(reversed(res))
|
||||||
|
|
||||||
answer_1 = number2snafu(sum(map(snafu2number, lines)))
|
yield number2snafu(sum(map(snafu2number, lines)))
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
@ -1,23 +1,28 @@
|
|||||||
import string
|
import string
|
||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = [line.strip() for line in sys.stdin.readlines()]
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# extract content of each part
|
|
||||||
parts = [(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines]
|
|
||||||
|
|
||||||
# priorities
|
class Solver(BaseSolver):
|
||||||
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
|
|
||||||
# part 1
|
# extract content of each part
|
||||||
part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
|
parts = [
|
||||||
print(f"answer 1 is {part1}")
|
(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines
|
||||||
|
]
|
||||||
|
|
||||||
# part 2
|
# priorities
|
||||||
n_per_group = 3
|
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
|
||||||
part2 = sum(
|
|
||||||
priorities[c]
|
# part 1
|
||||||
for i in range(0, len(lines), n_per_group)
|
yield sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
|
||||||
for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group])
|
|
||||||
)
|
# part 2
|
||||||
print(f"answer 2 is {part2}")
|
n_per_group = 3
|
||||||
|
yield sum(
|
||||||
|
priorities[c]
|
||||||
|
for i in range(0, len(lines), n_per_group)
|
||||||
|
for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group])
|
||||||
|
)
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = [line.strip() for line in sys.stdin.readlines()]
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def make_range(value: str) -> set[int]:
|
def make_range(value: str) -> set[int]:
|
||||||
@ -8,10 +8,13 @@ def make_range(value: str) -> set[int]:
|
|||||||
return set(range(int(parts[0]), int(parts[1]) + 1))
|
return set(range(int(parts[0]), int(parts[1]) + 1))
|
||||||
|
|
||||||
|
|
||||||
sections = [tuple(make_range(part) for part in line.split(",")) for line in lines]
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
|
|
||||||
answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
|
sections = [
|
||||||
print(f"answer 1 is {answer_1}")
|
tuple(make_range(part) for part in line.split(",")) for line in lines
|
||||||
|
]
|
||||||
|
|
||||||
answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections)
|
yield sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
|
||||||
print(f"answer 1 is {answer_2}")
|
yield sum(bool(s1.intersection(s2)) for s1, s2 in sections)
|
||||||
|
@ -1,41 +1,43 @@
|
|||||||
import copy
|
import copy
|
||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
blocks_s, moves_s = (part.splitlines() for part in sys.stdin.read().split("\n\n"))
|
from ..base import BaseSolver
|
||||||
|
|
||||||
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
|
|
||||||
|
|
||||||
# this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the
|
class Solver(BaseSolver):
|
||||||
# form of '[X] ' (including the trailing space)
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
#
|
blocks_s, moves_s = (part.splitlines() for part in input.split("\n\n"))
|
||||||
for block in blocks_s[-2::-1]:
|
|
||||||
for stack, index in zip(blocks, range(0, len(block), 4)):
|
|
||||||
crate = block[index + 1 : index + 2].strip()
|
|
||||||
|
|
||||||
if crate:
|
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
|
||||||
blocks[stack].append(crate)
|
|
||||||
|
|
||||||
# part 1 - deep copy for part 2
|
# this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the
|
||||||
blocks_1 = copy.deepcopy(blocks)
|
# form of '[X] ' (including the trailing space)
|
||||||
|
#
|
||||||
|
for block in blocks_s[-2::-1]:
|
||||||
|
for stack, index in zip(blocks, range(0, len(block), 4)):
|
||||||
|
crate = block[index + 1 : index + 2].strip()
|
||||||
|
|
||||||
for move in moves_s:
|
if crate:
|
||||||
_, count_s, _, from_, _, to_ = move.strip().split()
|
blocks[stack].append(crate)
|
||||||
|
|
||||||
for _i in range(int(count_s)):
|
# part 1 - deep copy for part 2
|
||||||
blocks_1[to_].append(blocks_1[from_].pop())
|
blocks_1 = copy.deepcopy(blocks)
|
||||||
|
|
||||||
# part 2
|
for move in moves_s:
|
||||||
blocks_2 = copy.deepcopy(blocks)
|
_, count_s, _, from_, _, to_ = move.strip().split()
|
||||||
|
|
||||||
for move in moves_s:
|
for _i in range(int(count_s)):
|
||||||
_, count_s, _, from_, _, to_ = move.strip().split()
|
blocks_1[to_].append(blocks_1[from_].pop())
|
||||||
count = int(count_s)
|
|
||||||
|
|
||||||
blocks_2[to_].extend(blocks_2[from_][-count:])
|
# part 2
|
||||||
del blocks_2[from_][-count:]
|
blocks_2 = copy.deepcopy(blocks)
|
||||||
|
|
||||||
answer_1 = "".join(s[-1] for s in blocks_1.values())
|
for move in moves_s:
|
||||||
print(f"answer 1 is {answer_1}")
|
_, count_s, _, from_, _, to_ = move.strip().split()
|
||||||
|
count = int(count_s)
|
||||||
|
|
||||||
answer_2 = "".join(s[-1] for s in blocks_2.values())
|
blocks_2[to_].extend(blocks_2[from_][-count:])
|
||||||
print(f"answer 2 is {answer_2}")
|
del blocks_2[from_][-count:]
|
||||||
|
|
||||||
|
yield "".join(s[-1] for s in blocks_1.values())
|
||||||
|
yield "".join(s[-1] for s in blocks_2.values())
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def index_of_first_n_differents(data: str, n: int) -> int:
|
def index_of_first_n_differents(data: str, n: int) -> int:
|
||||||
@ -8,8 +10,7 @@ def index_of_first_n_differents(data: str, n: int) -> int:
|
|||||||
return -1
|
return -1
|
||||||
|
|
||||||
|
|
||||||
data = sys.stdin.read().strip()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
yield index_of_first_n_differents(input, 4)
|
||||||
print(f"answer 1 is {index_of_first_n_differents(data, 4)}")
|
yield index_of_first_n_differents(input, 14)
|
||||||
print(f"answer 2 is {index_of_first_n_differents(data, 14)}")
|
|
||||||
|
@ -1,80 +1,81 @@
|
|||||||
import sys
|
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
# we are going to use Path to create path and go up/down in the file tree since it
|
|
||||||
# implements everything we need
|
|
||||||
#
|
|
||||||
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
|
|
||||||
# on Windows but that is not an issue since only the sizes matter
|
|
||||||
#
|
|
||||||
|
|
||||||
# mapping from path to list of files or directories
|
|
||||||
trees: dict[Path, list[Path]] = {}
|
|
||||||
|
|
||||||
# mapping from paths to either size (for file) or -1 for directory
|
|
||||||
sizes: dict[Path, int] = {}
|
|
||||||
|
|
||||||
# first line must be a cd otherwise we have no idea where we are
|
|
||||||
assert lines[0].startswith("$ cd")
|
|
||||||
base_path = Path(lines[0].strip("$").split()[1]).resolve()
|
|
||||||
cur_path = base_path
|
|
||||||
|
|
||||||
trees[cur_path] = []
|
|
||||||
sizes[cur_path] = -1
|
|
||||||
|
|
||||||
for line in lines[1:]:
|
|
||||||
# command
|
|
||||||
if line.startswith("$"):
|
|
||||||
parts = line.strip("$").strip().split()
|
|
||||||
command = parts[0]
|
|
||||||
|
|
||||||
if command == "cd":
|
|
||||||
cur_path = cur_path.joinpath(parts[1]).resolve()
|
|
||||||
|
|
||||||
# just initialize the lis of files if not already done
|
|
||||||
if cur_path not in trees:
|
|
||||||
trees[cur_path] = []
|
|
||||||
else:
|
|
||||||
# nothing to do here
|
|
||||||
pass
|
|
||||||
|
|
||||||
# fill the current path
|
|
||||||
else:
|
|
||||||
parts = line.split()
|
|
||||||
name: str = parts[1]
|
|
||||||
if line.startswith("dir"):
|
|
||||||
size = -1
|
|
||||||
else:
|
|
||||||
size = int(parts[0])
|
|
||||||
|
|
||||||
path = cur_path.joinpath(name)
|
|
||||||
trees[cur_path].append(path)
|
|
||||||
sizes[path] = size
|
|
||||||
|
|
||||||
|
|
||||||
def compute_size(path: Path) -> int:
|
class Solver(BaseSolver):
|
||||||
size = sizes[path]
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
|
|
||||||
if size >= 0:
|
# we are going to use Path to create path and go up/down in the file tree since it
|
||||||
return size
|
# implements everything we need
|
||||||
|
#
|
||||||
|
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
|
||||||
|
# on Windows but that is not an issue since only the sizes matter
|
||||||
|
#
|
||||||
|
|
||||||
return sum(compute_size(sub) for sub in trees[path])
|
# mapping from path to list of files or directories
|
||||||
|
trees: dict[Path, list[Path]] = {}
|
||||||
|
|
||||||
|
# mapping from paths to either size (for file) or -1 for directory
|
||||||
|
sizes: dict[Path, int] = {}
|
||||||
|
|
||||||
acc_sizes = {path: compute_size(path) for path in trees}
|
# first line must be a cd otherwise we have no idea where we are
|
||||||
|
assert lines[0].startswith("$ cd")
|
||||||
|
base_path = Path(lines[0].strip("$").split()[1]).resolve()
|
||||||
|
cur_path = base_path
|
||||||
|
|
||||||
# part 1
|
trees[cur_path] = []
|
||||||
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
|
sizes[cur_path] = -1
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
for line in lines[1:]:
|
||||||
total_space = 70_000_000
|
# command
|
||||||
update_space = 30_000_000
|
if line.startswith("$"):
|
||||||
free_space = total_space - acc_sizes[base_path]
|
parts = line.strip("$").strip().split()
|
||||||
|
command = parts[0]
|
||||||
|
|
||||||
to_free_space = update_space - free_space
|
if command == "cd":
|
||||||
|
cur_path = cur_path.joinpath(parts[1]).resolve()
|
||||||
|
|
||||||
answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space)
|
# just initialize the lis of files if not already done
|
||||||
print(f"answer 2 is {answer_2}")
|
if cur_path not in trees:
|
||||||
|
trees[cur_path] = []
|
||||||
|
else:
|
||||||
|
# nothing to do here
|
||||||
|
pass
|
||||||
|
|
||||||
|
# fill the current path
|
||||||
|
else:
|
||||||
|
parts = line.split()
|
||||||
|
name: str = parts[1]
|
||||||
|
if line.startswith("dir"):
|
||||||
|
size = -1
|
||||||
|
else:
|
||||||
|
size = int(parts[0])
|
||||||
|
|
||||||
|
path = cur_path.joinpath(name)
|
||||||
|
trees[cur_path].append(path)
|
||||||
|
sizes[path] = size
|
||||||
|
|
||||||
|
def compute_size(path: Path) -> int:
|
||||||
|
size = sizes[path]
|
||||||
|
|
||||||
|
if size >= 0:
|
||||||
|
return size
|
||||||
|
|
||||||
|
return sum(compute_size(sub) for sub in trees[path])
|
||||||
|
|
||||||
|
acc_sizes = {path: compute_size(path) for path in trees}
|
||||||
|
|
||||||
|
# part 1
|
||||||
|
yield sum(size for size in acc_sizes.values() if size <= 100_000)
|
||||||
|
|
||||||
|
# part 2
|
||||||
|
total_space = 70_000_000
|
||||||
|
update_space = 30_000_000
|
||||||
|
free_space = total_space - acc_sizes[base_path]
|
||||||
|
|
||||||
|
to_free_space = update_space - free_space
|
||||||
|
|
||||||
|
yield min(size for size in acc_sizes.values() if size >= to_free_space)
|
||||||
|
@ -1,53 +1,54 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from numpy.typing import NDArray
|
from numpy.typing import NDArray
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
trees = np.array([[int(x) for x in row] for row in lines])
|
|
||||||
|
|
||||||
# answer 1
|
class Solver(BaseSolver):
|
||||||
highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
highest_trees[1:-1, 1:-1] = [
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
[
|
|
||||||
[
|
trees = np.array([[int(x) for x in row] for row in lines])
|
||||||
trees[:i, j].max(),
|
|
||||||
trees[i + 1 :, j].max(),
|
# answer 1
|
||||||
trees[i, :j].max(),
|
highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1
|
||||||
trees[i, j + 1 :].max(),
|
highest_trees[1:-1, 1:-1] = [
|
||||||
|
[
|
||||||
|
[
|
||||||
|
trees[:i, j].max(),
|
||||||
|
trees[i + 1 :, j].max(),
|
||||||
|
trees[i, :j].max(),
|
||||||
|
trees[i, j + 1 :].max(),
|
||||||
|
]
|
||||||
|
for j in range(1, trees.shape[1] - 1)
|
||||||
|
]
|
||||||
|
for i in range(1, trees.shape[0] - 1)
|
||||||
]
|
]
|
||||||
for j in range(1, trees.shape[1] - 1)
|
|
||||||
]
|
|
||||||
for i in range(1, trees.shape[0] - 1)
|
|
||||||
]
|
|
||||||
|
|
||||||
answer_1 = (highest_trees.min(axis=2) < trees).sum()
|
yield (highest_trees.min(axis=2) < trees).sum()
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
|
def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
|
||||||
|
w = np.where(row_of_trees >= value)[0]
|
||||||
|
|
||||||
def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
|
if not w.size:
|
||||||
w = np.where(row_of_trees >= value)[0]
|
return len(row_of_trees)
|
||||||
|
|
||||||
if not w.size:
|
return w[0] + 1
|
||||||
return len(row_of_trees)
|
|
||||||
|
|
||||||
return w[0] + 1
|
# answer 2
|
||||||
|
v_distances = np.zeros(trees.shape + (4,), dtype=int)
|
||||||
|
v_distances[1:-1, 1:-1, :] = [
|
||||||
# answer 2
|
[
|
||||||
v_distances = np.zeros(trees.shape + (4,), dtype=int)
|
[
|
||||||
v_distances[1:-1, 1:-1, :] = [
|
viewing_distance(trees[i - 1 :: -1, j], trees[i, j]),
|
||||||
[
|
viewing_distance(trees[i, j - 1 :: -1], trees[i, j]),
|
||||||
[
|
viewing_distance(trees[i, j + 1 :], trees[i, j]),
|
||||||
viewing_distance(trees[i - 1 :: -1, j], trees[i, j]),
|
viewing_distance(trees[i + 1 :, j], trees[i, j]),
|
||||||
viewing_distance(trees[i, j - 1 :: -1], trees[i, j]),
|
]
|
||||||
viewing_distance(trees[i, j + 1 :], trees[i, j]),
|
for j in range(1, trees.shape[1] - 1)
|
||||||
viewing_distance(trees[i + 1 :, j], trees[i, j]),
|
]
|
||||||
|
for i in range(1, trees.shape[0] - 1)
|
||||||
]
|
]
|
||||||
for j in range(1, trees.shape[1] - 1)
|
yield np.prod(v_distances, axis=2).max()
|
||||||
]
|
|
||||||
for i in range(1, trees.shape[0] - 1)
|
|
||||||
]
|
|
||||||
answer_2 = np.prod(v_distances, axis=2).max()
|
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,7 +1,10 @@
|
|||||||
import sys
|
import itertools as it
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def move(head: tuple[int, int], command: str) -> tuple[int, int]:
|
def move(head: tuple[int, int], command: str) -> tuple[int, int]:
|
||||||
h_col, h_row = head
|
h_col, h_row = head
|
||||||
@ -43,17 +46,14 @@ def run(commands: list[str], n_blocks: int) -> list[tuple[int, int]]:
|
|||||||
return visited
|
return visited
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = [line.strip() for line in input.splitlines()]
|
||||||
|
|
||||||
# flatten the commands
|
# flatten the commands
|
||||||
commands: list[str] = []
|
commands = list(
|
||||||
for line in lines:
|
it.chain(*(p[0] * int(p[1]) for line in lines if (p := line.split())))
|
||||||
d, c = line.split()
|
)
|
||||||
commands.extend(d * int(c))
|
|
||||||
|
|
||||||
|
yield len(set(run(commands, n_blocks=2)))
|
||||||
visited_1 = run(commands, n_blocks=2)
|
yield len(set(run(commands, n_blocks=10)))
|
||||||
print(f"answer 1 is {len(set(visited_1))}")
|
|
||||||
|
|
||||||
visited_2 = run(commands, n_blocks=10)
|
|
||||||
print(f"answer 2 is {len(set(visited_2))}")
|
|
||||||
|
@ -1,27 +1,9 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
lookups_1 = {str(d): d for d in range(1, 10)}
|
|
||||||
lookups_2 = lookups_1 | {
|
|
||||||
d: i + 1
|
|
||||||
for i, d in enumerate(
|
|
||||||
(
|
|
||||||
"one",
|
|
||||||
"two",
|
|
||||||
"three",
|
|
||||||
"four",
|
|
||||||
"five",
|
|
||||||
"six",
|
|
||||||
"seven",
|
|
||||||
"eight",
|
|
||||||
"nine",
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def find_values(lookups: dict[str, int]) -> list[int]:
|
def find_values(lines: list[str], lookups: dict[str, int]) -> list[int]:
|
||||||
values: list[int] = []
|
values: list[int] = []
|
||||||
|
|
||||||
for line in filter(bool, lines):
|
for line in filter(bool, lines):
|
||||||
@ -41,5 +23,27 @@ def find_values(lookups: dict[str, int]) -> list[int]:
|
|||||||
return values
|
return values
|
||||||
|
|
||||||
|
|
||||||
print(f"answer 1 is {sum(find_values(lookups_1))}")
|
class Solver(BaseSolver):
|
||||||
print(f"answer 2 is {sum(find_values(lookups_2))}")
|
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))
|
||||||
|
@ -1,100 +1,100 @@
|
|||||||
import os
|
from typing import Any, Iterator, Literal, cast
|
||||||
import sys
|
|
||||||
from typing import Literal, cast
|
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
|
|
||||||
Symbol = Literal["|", "-", "L", "J", "7", "F", ".", "S"]
|
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
|
class Solver(BaseSolver):
|
||||||
si, sj = next(
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
(i, j)
|
lines: list[list[Symbol]] = [
|
||||||
for i in range(len(lines))
|
[cast(Symbol, symbol) for symbol in line] for line in input.splitlines()
|
||||||
for j in range(len(lines[0]))
|
]
|
||||||
if lines[i][j] == "S"
|
|
||||||
)
|
|
||||||
|
|
||||||
# find one of the two outputs
|
# find starting point
|
||||||
ni, nj = si, sj
|
si, sj = next(
|
||||||
for ni, nj, chars in (
|
(i, j)
|
||||||
(si - 1, sj, "|7F"),
|
for i in range(len(lines))
|
||||||
(si + 1, sj, "|LJ"),
|
for j in range(len(lines[0]))
|
||||||
(si, sj - 1, "-LF"),
|
if lines[i][j] == "S"
|
||||||
(si, sj + 1, "-J7"),
|
)
|
||||||
):
|
|
||||||
if lines[ni][nj] in chars:
|
|
||||||
break
|
|
||||||
|
|
||||||
# part 1 - find the loop (re-used in part 2)
|
# find one of the two outputs
|
||||||
loop = [(si, sj), (ni, nj)]
|
ni, nj = si, sj
|
||||||
while True:
|
for ni, nj, chars in (
|
||||||
pi, pj = loop[-2]
|
(si - 1, sj, "|7F"),
|
||||||
i, j = loop[-1]
|
(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:
|
sym = lines[i][j]
|
||||||
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
|
|
||||||
|
|
||||||
if (i, j) == (si, sj):
|
if sym == "|" and pi > i or sym in "JL" and pi == i:
|
||||||
break
|
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):
|
if (i, j) == (si, sj):
|
||||||
print("\033[91mS\033[0m", end="")
|
break
|
||||||
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()
|
|
||||||
|
|
||||||
answer_2 = len(inside)
|
loop.append((i, j))
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
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)
|
||||||
|
@ -1,41 +1,42 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
|
|
||||||
def compute_total_distance(expansion: int) -> int:
|
class Solver(BaseSolver):
|
||||||
distances: list[int] = []
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
for g1 in range(len(galaxies_y)):
|
lines = input.splitlines()
|
||||||
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])
|
|
||||||
|
|
||||||
dx = sum(
|
data = np.array([[c == "#" for c in line] for line in lines])
|
||||||
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)
|
rows = {c for c in range(data.shape[0]) if not data[c, :].any()}
|
||||||
return sum(distances)
|
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
|
def compute_total_distance(expansion: int) -> int:
|
||||||
answer_1 = compute_total_distance(2)
|
distances: list[int] = []
|
||||||
print(f"answer 1 is {answer_1}")
|
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
|
dx = sum(
|
||||||
answer_2 = compute_total_distance(1000000)
|
1 + (expansion - 1) * (x in columns)
|
||||||
print(f"answer 2 is {answer_2}")
|
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)
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import os
|
|
||||||
import sys
|
|
||||||
from functools import lru_cache
|
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
|
@lru_cache
|
||||||
@ -77,31 +75,29 @@ def compute_possible_arrangements(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def compute_all_possible_arrangements(lines: Iterable[str], repeat: int) -> int:
|
class Solver(BaseSolver):
|
||||||
count = 0
|
def compute_all_possible_arrangements(
|
||||||
|
self, lines: Iterable[str], repeat: int
|
||||||
|
) -> int:
|
||||||
|
count = 0
|
||||||
|
|
||||||
if VERBOSE:
|
for i_line, line in enumerate(lines):
|
||||||
from tqdm import tqdm
|
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:
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
parts = line.split(" ")
|
lines = input.splitlines()
|
||||||
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,
|
|
||||||
)
|
|
||||||
|
|
||||||
return count
|
# part 1
|
||||||
|
yield self.compute_all_possible_arrangements(lines, 1)
|
||||||
|
|
||||||
|
# part 2
|
||||||
lines = sys.stdin.read().splitlines()
|
yield self.compute_all_possible_arrangements(lines, 5)
|
||||||
|
|
||||||
|
|
||||||
# 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}")
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import sys
|
from typing import Any, Callable, Iterator, Literal
|
||||||
from typing import Callable, Literal
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def split(block: list[str], axis: Literal[0, 1], count: int) -> int:
|
def split(block: list[str], axis: Literal[0, 1], count: int) -> int:
|
||||||
@ -25,19 +26,18 @@ def split(block: list[str], axis: Literal[0, 1], count: int) -> int:
|
|||||||
return 0
|
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
|
# part 2
|
||||||
answer_1 = sum(
|
yield sum(
|
||||||
split(block, axis=1, count=0) + 100 * split(block, axis=0, count=0)
|
split(block, axis=1, count=1) + 100 * split(block, axis=0, count=1)
|
||||||
for block in blocks
|
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}")
|
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
import sys
|
from typing import Any, Iterator, TypeAlias
|
||||||
from typing import TypeAlias
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
RockGrid: TypeAlias = list[list[str]]
|
RockGrid: TypeAlias = list[list[str]]
|
||||||
|
|
||||||
rocks0 = [list(line) for line in sys.stdin.read().splitlines()]
|
|
||||||
|
|
||||||
|
|
||||||
def slide_rocks_top(rocks: RockGrid) -> RockGrid:
|
def slide_rocks_top(rocks: RockGrid) -> RockGrid:
|
||||||
top = [0 if c == "." else 1 for c in rocks[0]]
|
top = [0 if c == "." else 1 for c in rocks[0]]
|
||||||
@ -34,35 +33,38 @@ def cycle(rocks: RockGrid) -> RockGrid:
|
|||||||
return rocks
|
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
|
rocks = slide_rocks_top([[c for c in r] for r in rocks0])
|
||||||
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}")
|
|
||||||
|
|
||||||
# part 2
|
# part 1
|
||||||
rocks = rocks0
|
yield sum(
|
||||||
|
(len(rocks) - i) * sum(1 for c in row if c == "O")
|
||||||
|
for i, row in enumerate(rocks)
|
||||||
|
)
|
||||||
|
|
||||||
N = 1000000000
|
# part 2
|
||||||
cycles: list[RockGrid] = []
|
rocks = rocks0
|
||||||
i_cycle: int = -1
|
|
||||||
for i_cycle in range(N):
|
|
||||||
rocks = cycle(rocks)
|
|
||||||
|
|
||||||
if any(rocks == c for c in cycles):
|
N = 1000000000
|
||||||
break
|
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]))
|
cycles.append([[c for c in r] for r in rocks])
|
||||||
cycle_length = i_cycle - cycle_start
|
|
||||||
|
|
||||||
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(
|
ci = cycle_start + (N - cycle_start) % cycle_length - 1
|
||||||
(len(rocks) - i) * sum(1 for c in row if c == "O")
|
|
||||||
for i, row in enumerate(cycles[ci])
|
yield sum(
|
||||||
)
|
(len(rocks) - i) * sum(1 for c in row if c == "O")
|
||||||
print(f"answer 2 is {answer_2}")
|
for i, row in enumerate(cycles[ci])
|
||||||
|
)
|
||||||
|
@ -1,31 +1,33 @@
|
|||||||
import sys
|
|
||||||
from functools import reduce
|
from functools import reduce
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
steps = sys.stdin.read().strip().split(",")
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
def _hash(s: str) -> int:
|
def _hash(s: str) -> int:
|
||||||
return reduce(lambda v, u: ((v + ord(u)) * 17) % 256, s, 0)
|
return reduce(lambda v, u: ((v + ord(u)) * 17) % 256, s, 0)
|
||||||
|
|
||||||
|
|
||||||
# part 1
|
class Solver(BaseSolver):
|
||||||
answer_1 = sum(map(_hash, steps))
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
print(f"answer 1 is {answer_1}")
|
steps = input.split(",")
|
||||||
|
|
||||||
# part 2
|
# part 1
|
||||||
boxes: list[dict[str, int]] = [{} for _ in range(256)]
|
yield sum(map(_hash, steps))
|
||||||
|
|
||||||
for step in steps:
|
# part 2
|
||||||
if (i := step.find("=")) >= 0:
|
boxes: list[dict[str, int]] = [{} for _ in range(256)]
|
||||||
label, length = step[:i], int(step[i + 1 :])
|
|
||||||
boxes[_hash(label)][label] = length
|
|
||||||
else:
|
|
||||||
label = step[:-1]
|
|
||||||
boxes[_hash(label)].pop(label, None)
|
|
||||||
|
|
||||||
answer_2 = sum(
|
for step in steps:
|
||||||
i_box * i_lens * length
|
if (i := step.find("=")) >= 0:
|
||||||
for i_box, box in enumerate(boxes, start=1)
|
label, length = step[:i], int(step[i + 1 :])
|
||||||
for i_lens, length in enumerate(box.values(), start=1)
|
boxes[_hash(label)][label] = length
|
||||||
)
|
else:
|
||||||
print(f"answer 2 is {answer_2}")
|
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)
|
||||||
|
)
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
import os
|
from typing import Any, Iterator, Literal, TypeAlias, cast
|
||||||
import sys
|
|
||||||
from typing import Literal, TypeAlias, cast
|
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
|
|
||||||
CellType: TypeAlias = Literal[".", "|", "-", "\\", "/"]
|
CellType: TypeAlias = Literal[".", "|", "-", "\\", "/"]
|
||||||
Direction: TypeAlias = Literal["R", "L", "U", "D"]
|
Direction: TypeAlias = Literal["R", "L", "U", "D"]
|
||||||
@ -78,33 +76,33 @@ def propagate(
|
|||||||
return beams
|
return beams
|
||||||
|
|
||||||
|
|
||||||
layout: list[list[CellType]] = [
|
class Solver(BaseSolver):
|
||||||
[cast(CellType, col) for col in row] for row in sys.stdin.read().splitlines()
|
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:
|
# part 1
|
||||||
print("\n".join(["".join("#" if col else "." for col in row) for row in beams]))
|
yield sum(sum(map(bool, row)) for row in beams)
|
||||||
|
|
||||||
# part 1
|
# part 2
|
||||||
answer_1 = sum(sum(map(bool, row)) for row in beams)
|
n_rows, n_cols = len(layout), len(layout[0])
|
||||||
print(f"answer 1 is {answer_1}")
|
cases: list[tuple[tuple[int, int], Direction]] = []
|
||||||
|
|
||||||
# part 2
|
for row in range(n_rows):
|
||||||
n_rows, n_cols = len(layout), len(layout[0])
|
cases.append(((row, 0), "R"))
|
||||||
cases: list[tuple[tuple[int, int], Direction]] = []
|
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):
|
yield max(
|
||||||
cases.append(((row, 0), "R"))
|
sum(sum(map(bool, row)) for row in propagate(layout, start, direction))
|
||||||
cases.append(((row, n_cols - 1), "L"))
|
for start, direction in cases
|
||||||
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}")
|
|
||||||
|
@ -1,13 +1,11 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import heapq
|
import heapq
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Literal, TypeAlias
|
from typing import Any, Iterator, Literal, TypeAlias
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
|
|
||||||
Direction: TypeAlias = Literal[">", "<", "^", "v"]
|
Direction: TypeAlias = Literal[">", "<", "^", "v"]
|
||||||
|
|
||||||
@ -32,202 +30,204 @@ MAPPINGS: dict[Direction, tuple[int, int, Direction]] = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def print_shortest_path(
|
class Solver(BaseSolver):
|
||||||
grid: list[list[int]],
|
def print_shortest_path(
|
||||||
target: tuple[int, int],
|
self,
|
||||||
per_cell: dict[tuple[int, int], list[tuple[Label, int]]],
|
grid: list[list[int]],
|
||||||
):
|
target: tuple[int, int],
|
||||||
assert len(per_cell[target]) == 1
|
per_cell: dict[tuple[int, int], list[tuple[Label, int]]],
|
||||||
label = per_cell[target][0][0]
|
):
|
||||||
|
assert len(per_cell[target]) == 1
|
||||||
|
label = per_cell[target][0][0]
|
||||||
|
|
||||||
path: list[Label] = []
|
path: list[Label] = []
|
||||||
while True:
|
while True:
|
||||||
path.insert(0, label)
|
path.insert(0, label)
|
||||||
if label.parent is None:
|
if label.parent is None:
|
||||||
break
|
break
|
||||||
label = label.parent
|
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 i in range(len(grid)):
|
||||||
for j in range(len(grid[0])):
|
for j in range(len(grid[0])):
|
||||||
if per_cell[i, j]:
|
if per_cell[i, j]:
|
||||||
p_grid[i][j] = f"\033[94m{grid[i][j]}\033[0m"
|
p_grid[i][j] = f"\033[94m{grid[i][j]}\033[0m"
|
||||||
|
|
||||||
prev_label = path[0]
|
prev_label = path[0]
|
||||||
for label in path[1:]:
|
for label in path[1:]:
|
||||||
for r in range(
|
for r in range(
|
||||||
min(prev_label.row, label.row), max(prev_label.row, label.row) + 1
|
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,
|
|
||||||
):
|
):
|
||||||
if (r, c) != (prev_label.row, prev_label.col):
|
for c in range(
|
||||||
p_grid[r][c] = f"\033[93m{grid[r][c]}\033[0m"
|
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]:
|
visited: dict[tuple[int, int], tuple[Label, int]] = {}
|
||||||
n_rows, n_cols = len(grid), len(grid[0])
|
|
||||||
|
|
||||||
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]] = [
|
while queue and len(visited) != n_rows * n_cols:
|
||||||
(0, Label(row=n_rows - 1, col=n_cols - 1, direction="^", count=0))
|
distance, label = heapq.heappop(queue)
|
||||||
]
|
|
||||||
|
|
||||||
while queue and len(visited) != n_rows * n_cols:
|
if (label.row, label.col) in visited:
|
||||||
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):
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
heapq.heappush(
|
visited[label.row, label.col] = (label, distance)
|
||||||
queue,
|
|
||||||
(
|
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
|
distance
|
||||||
+ sum(
|
+ sum(
|
||||||
grid[r][c]
|
grid[r][c]
|
||||||
for r in range(min(row, label.row), max(row, label.row) + 1)
|
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)
|
for c in range(min(col, label.col), max(col, label.col) + 1)
|
||||||
)
|
)
|
||||||
- grid[row][col],
|
- grid[label.row][label.col]
|
||||||
Label(
|
)
|
||||||
row=row,
|
|
||||||
col=col,
|
heapq.heappush(
|
||||||
direction=direction,
|
queue,
|
||||||
count=0,
|
(
|
||||||
parent=label,
|
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
|
if self.verbose:
|
||||||
elif label.count == max_straight:
|
self.print_shortest_path(grid, target, per_cell)
|
||||||
continue
|
|
||||||
|
|
||||||
# same direction, keep going and increment count
|
return per_cell[target][0][1]
|
||||||
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 = (
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
distance
|
data = [[int(c) for c in r] for r in input.splitlines()]
|
||||||
+ sum(
|
estimates = self.shortest_many_paths(data)
|
||||||
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]
|
|
||||||
)
|
|
||||||
|
|
||||||
heapq.heappush(
|
# part 1
|
||||||
queue,
|
yield self.shortest_path(data, 1, 3, lower_bounds=estimates)
|
||||||
(
|
|
||||||
distance_to + lower_bounds[row, col],
|
|
||||||
distance_to,
|
|
||||||
Label(
|
|
||||||
row=row,
|
|
||||||
col=col,
|
|
||||||
direction=direction,
|
|
||||||
count=count,
|
|
||||||
parent=label,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
)
|
|
||||||
|
|
||||||
if VERBOSE:
|
# part 2
|
||||||
print_shortest_path(grid, target, per_cell)
|
yield self.shortest_path(data, 4, 10, lower_bounds=estimates)
|
||||||
|
|
||||||
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}")
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import sys
|
from typing import Any, Iterator, Literal, TypeAlias, cast
|
||||||
from typing import Literal, TypeAlias, cast
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
Direction: TypeAlias = Literal["R", "L", "U", "D"]
|
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
|
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
|
# part 2
|
||||||
answer_1 = area(
|
yield area(
|
||||||
*polygon([(cast(Direction, (p := line.split())[0]), int(p[1])) for line in lines])
|
*polygon(
|
||||||
)
|
[
|
||||||
print(f"answer 1 is {answer_1}")
|
(DIRECTIONS[int((h := line.split()[-1])[-2])], int(h[2:-2], 16))
|
||||||
|
for line in lines
|
||||||
# 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}")
|
|
||||||
|
@ -1,13 +1,8 @@
|
|||||||
import logging
|
|
||||||
import operator
|
import operator
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from math import prod
|
from math import prod
|
||||||
from typing import Literal, TypeAlias, cast
|
from typing import Any, Iterator, Literal, TypeAlias, cast
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
|
|
||||||
|
|
||||||
Category: TypeAlias = Literal["x", "m", "a", "s"]
|
Category: TypeAlias = Literal["x", "m", "a", "s"]
|
||||||
Part: TypeAlias = dict[Category, int]
|
Part: TypeAlias = dict[Category, int]
|
||||||
@ -22,119 +17,118 @@ Check: TypeAlias = tuple[Category, Literal["<", ">"], int] | None
|
|||||||
Workflow: TypeAlias = list[tuple[Check, str]]
|
Workflow: TypeAlias = list[tuple[Check, str]]
|
||||||
|
|
||||||
|
|
||||||
def accept(workflows: dict[str, Workflow], part: Part) -> bool:
|
class Solver(BaseSolver):
|
||||||
workflow = "in"
|
def accept(self, workflows: dict[str, Workflow], part: Part) -> bool:
|
||||||
decision: bool | None = None
|
workflow = "in"
|
||||||
|
decision: bool | None = None
|
||||||
|
|
||||||
while decision is None:
|
while decision is None:
|
||||||
for check, target in workflows[workflow]:
|
for check, target in workflows[workflow]:
|
||||||
passed = check is None
|
passed = check is None
|
||||||
if check is not None:
|
if check is not None:
|
||||||
category, sense, value = check
|
category, sense, value = check
|
||||||
passed = OPERATORS[sense](part[category], value)
|
passed = OPERATORS[sense](part[category], value)
|
||||||
|
|
||||||
if passed:
|
if passed:
|
||||||
if target in workflows:
|
if target in workflows:
|
||||||
workflow = target
|
workflow = target
|
||||||
else:
|
else:
|
||||||
decision = target == "A"
|
decision = target == "A"
|
||||||
break
|
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 transfer_or_accept(
|
||||||
def _fmt(meta: PartWithBounds) -> str:
|
target: str, meta: PartWithBounds, queue: list[tuple[PartWithBounds, str]]
|
||||||
return "{" + ", ".join(f"{k}={v}" for k, v in meta.items()) + "}"
|
) -> int:
|
||||||
|
count = 0
|
||||||
def transfer_or_accept(
|
if target in workflows:
|
||||||
target: str, meta: PartWithBounds, queue: list[tuple[PartWithBounds, str]]
|
self.logger.info(f" transfer to {target}")
|
||||||
) -> int:
|
queue.append((meta, target))
|
||||||
count = 0
|
elif target == "A":
|
||||||
if target in workflows:
|
count = prod((high - low + 1) for low, high in meta.values())
|
||||||
logging.info(f" transfer to {target}")
|
self.logger.info(f" accepted ({count})")
|
||||||
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)
|
|
||||||
else:
|
else:
|
||||||
meta[category], meta2[category] = (low, value), (value + 1, high)
|
self.logger.info(" rejected")
|
||||||
logging.info(f" split {_fmt(meta2)} ({target}), {_fmt(meta)}")
|
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")
|
n_iterations = 0
|
||||||
return accepted
|
|
||||||
|
|
||||||
|
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] = {}
|
self.logger.info(
|
||||||
for workflow_s in workflows_s.split("\n"):
|
f" checking {_fmt(meta)} against {category} {sense} {value}"
|
||||||
name, block_s = workflow_s.split("{")
|
)
|
||||||
workflows[name] = []
|
|
||||||
|
|
||||||
for block in block_s[:-1].split(","):
|
if not op(bounds[0], value) and not op(bounds[1], value):
|
||||||
check: Check
|
self.logger.info(" reject, always false")
|
||||||
if (i := block.find(":")) >= 0:
|
continue
|
||||||
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
|
if op(meta[category][0], value) and op(meta[category][1], value):
|
||||||
parts: list[Part] = [
|
self.logger.info(" accept, always true")
|
||||||
{cast(Category, s[0]): int(s[2:]) for s in part_s[1:-1].split(",")}
|
accepted += transfer_or_accept(target, meta, queue)
|
||||||
for part_s in parts_s.split("\n")
|
break
|
||||||
]
|
|
||||||
answer_1 = sum(sum(part.values()) for part in parts if accept(workflows, part))
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
|
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
|
accepted += transfer_or_accept(target, meta2, queue)
|
||||||
answer_2 = propagate(
|
|
||||||
workflows, {cast(Category, c): (1, 4000) for c in ["x", "m", "a", "s"]}
|
self.logger.info(f"run took {n_iterations} iterations")
|
||||||
)
|
return accepted
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
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"]}
|
||||||
|
)
|
||||||
|
@ -1,43 +1,43 @@
|
|||||||
import math
|
import math
|
||||||
import sys
|
from typing import Any, Iterator, Literal, TypeAlias, cast
|
||||||
from typing import Literal, TypeAlias, cast
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
CubeType: TypeAlias = Literal["red", "blue", "green"]
|
CubeType: TypeAlias = Literal["red", "blue", "green"]
|
||||||
|
|
||||||
MAX_CUBES: dict[CubeType, int] = {"red": 12, "green": 13, "blue": 14}
|
MAX_CUBES: dict[CubeType, int] = {"red": 12, "green": 13, "blue": 14}
|
||||||
|
|
||||||
# parse games
|
|
||||||
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])] = [
|
class Solver(BaseSolver):
|
||||||
{
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
cast(CubeType, s[1]): int(s[0])
|
lines = input.splitlines()
|
||||||
for cube_draw in cube_set_s.strip().split(", ")
|
games: dict[int, list[dict[CubeType, int]]] = {}
|
||||||
if (s := cube_draw.split(" "))
|
for line in filter(bool, lines):
|
||||||
}
|
id_part, sets_part = line.split(":")
|
||||||
for cube_set_s in sets_part.strip().split(";")
|
|
||||||
]
|
|
||||||
|
|
||||||
# part 1
|
games[int(id_part.split(" ")[-1])] = [
|
||||||
answer_1 = sum(
|
{
|
||||||
id
|
cast(CubeType, s[1]): int(s[0])
|
||||||
for id, set_of_cubes in games.items()
|
for cube_draw in cube_set_s.strip().split(", ")
|
||||||
if all(
|
if (s := cube_draw.split(" "))
|
||||||
n_cubes <= MAX_CUBES[cube]
|
}
|
||||||
for cube_set in set_of_cubes
|
for cube_set_s in sets_part.strip().split(";")
|
||||||
for cube, n_cubes in cube_set.items()
|
]
|
||||||
)
|
|
||||||
)
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
yield sum(
|
||||||
answer_2 = sum(
|
id
|
||||||
math.prod(
|
for id, set_of_cubes in games.items()
|
||||||
max(cube_set.get(cube, 0) for cube_set in set_of_cubes) for cube in MAX_CUBES
|
if all(
|
||||||
)
|
n_cubes <= MAX_CUBES[cube]
|
||||||
for set_of_cubes in games.values()
|
for cube_set in set_of_cubes
|
||||||
)
|
for cube, n_cubes in cube_set.items()
|
||||||
print(f"answer 2 is {answer_2}")
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
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()
|
||||||
|
)
|
||||||
|
@ -1,161 +1,172 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
import sys
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
from math import lcm
|
from math import lcm
|
||||||
from typing import Literal, TypeAlias
|
from typing import Any, Iterator, Literal, TypeAlias
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
|
||||||
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
|
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
ModuleType: TypeAlias = Literal["broadcaster", "conjunction", "flip-flop"]
|
ModuleType: TypeAlias = Literal["broadcaster", "conjunction", "flip-flop"]
|
||||||
PulseType: TypeAlias = Literal["high", "low"]
|
PulseType: TypeAlias = Literal["high", "low"]
|
||||||
|
|
||||||
modules: dict[str, tuple[ModuleType, list[str]]] = {}
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
_modules: dict[str, tuple[ModuleType, list[str]]]
|
||||||
|
|
||||||
for line in lines:
|
def _process(
|
||||||
name, outputs_s = line.split(" -> ")
|
self,
|
||||||
outputs = outputs_s.split(", ")
|
start: tuple[str, str, PulseType],
|
||||||
if name == "broadcaster":
|
flip_flop_states: dict[str, Literal["on", "off"]],
|
||||||
modules["broadcaster"] = ("broadcaster", outputs)
|
conjunction_states: dict[str, dict[str, PulseType]],
|
||||||
else:
|
) -> tuple[dict[PulseType, int], dict[str, dict[PulseType, int]]]:
|
||||||
modules[name[1:]] = (
|
pulses: list[tuple[str, str, PulseType]] = [start]
|
||||||
"conjunction" if name.startswith("&") else "flip-flop",
|
counts: dict[PulseType, int] = {"low": 0, "high": 0}
|
||||||
outputs,
|
inputs: dict[str, dict[PulseType, int]] = defaultdict(
|
||||||
|
lambda: {"low": 0, "high": 0}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
self.logger.info("starting process... ")
|
||||||
|
|
||||||
def process(
|
while pulses:
|
||||||
start: tuple[str, str, PulseType],
|
input, name, pulse = pulses.pop(0)
|
||||||
flip_flop_states: dict[str, Literal["on", "off"]],
|
self.logger.info(f"{input} -{pulse}-> {name}")
|
||||||
conjunction_states: dict[str, dict[str, PulseType]],
|
counts[pulse] += 1
|
||||||
) -> 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})
|
|
||||||
|
|
||||||
logging.info("starting process... ")
|
inputs[name][pulse] += 1
|
||||||
|
|
||||||
while pulses:
|
if name not in self._modules:
|
||||||
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":
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if flip_flop_states[name] == "off":
|
type, outputs = self._modules[name]
|
||||||
flip_flop_states[name] = "on"
|
|
||||||
pulse = "high"
|
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:
|
else:
|
||||||
flip_flop_states[name] = "off"
|
conjunction_states[name][input] = pulse
|
||||||
pulse = "low"
|
|
||||||
|
|
||||||
else:
|
if all(state == "high" for state in conjunction_states[name].values()):
|
||||||
conjunction_states[name][input] = pulse
|
pulse = "low"
|
||||||
|
else:
|
||||||
|
pulse = "high"
|
||||||
|
|
||||||
if all(state == "high" for state in conjunction_states[name].values()):
|
pulses.extend((name, output, pulse) for output in outputs)
|
||||||
pulse = "low"
|
|
||||||
|
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:
|
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:
|
# reset states
|
||||||
fp.write("digraph G {\n")
|
for name in flip_flop_states:
|
||||||
fp.write("rx [shape=circle, color=red, style=filled];\n")
|
flip_flop_states[name] = "off"
|
||||||
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")
|
|
||||||
|
|
||||||
# part 1
|
for name in conjunction_states:
|
||||||
flip_flop_states: dict[str, Literal["on", "off"]] = {
|
for input in conjunction_states[name]:
|
||||||
name: "off" for name, (type, _) in modules.items() if type == "flip-flop"
|
conjunction_states[name][input] = "low"
|
||||||
}
|
|
||||||
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}")
|
|
||||||
|
|
||||||
# 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
|
to_rx_inputs = [
|
||||||
for name in flip_flop_states:
|
name for name, (_, outputs) in self._modules.items() if to_rx[0] in outputs
|
||||||
flip_flop_states[name] = "off"
|
]
|
||||||
|
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:
|
count = 1
|
||||||
for input in conjunction_states[name]:
|
cycles: dict[str, int] = {}
|
||||||
conjunction_states[name][input] = "low"
|
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
|
for node in to_rx_inputs:
|
||||||
to_rx = [name for name, (_, outputs) in modules.items() if "rx" in outputs]
|
if inputs[node]["low"] == 1:
|
||||||
assert len(to_rx) == 1, "cannot handle multiple module inputs for rx"
|
if node not in cycles:
|
||||||
assert (
|
cycles[node] = count
|
||||||
modules[to_rx[0]][0] == "conjunction"
|
elif node not in second:
|
||||||
), "can only handle conjunction as input to rx"
|
second[node] = count
|
||||||
|
|
||||||
to_rx_inputs = [name for name, (_, outputs) in modules.items() if to_rx[0] in outputs]
|
count += 1
|
||||||
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"
|
|
||||||
|
|
||||||
|
assert all(
|
||||||
|
second[k] == cycles[k] * 2 for k in to_rx_inputs
|
||||||
|
), "cannot only handle cycles starting at the beginning"
|
||||||
|
|
||||||
count = 1
|
yield lcm(*cycles.values())
|
||||||
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}")
|
|
||||||
|
@ -1,9 +1,6 @@
|
|||||||
import logging
|
from typing import Any, Iterator
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
|
|
||||||
|
|
||||||
|
|
||||||
def reachable(
|
def reachable(
|
||||||
@ -21,129 +18,133 @@ def reachable(
|
|||||||
return tiles
|
return tiles
|
||||||
|
|
||||||
|
|
||||||
map = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
start = next(
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
(i, j) for i in range(len(map)) for j in range(len(map[i])) if map[i][j] == "S"
|
map = input.splitlines()
|
||||||
)
|
start = next(
|
||||||
|
(i, j)
|
||||||
# part 1
|
for i in range(len(map))
|
||||||
answer_1 = len(reachable(map, {start}, 6 if len(map) < 20 else 64))
|
for j in range(len(map[i]))
|
||||||
print(f"answer 1 is {answer_1}")
|
if map[i][j] == "S"
|
||||||
|
|
||||||
# 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)
|
|
||||||
)
|
)
|
||||||
for cj in range(-2, 3)
|
|
||||||
]
|
|
||||||
for ci in range(-2, 3)
|
|
||||||
]
|
|
||||||
|
|
||||||
radius = (26501365 - rhombus) // cycle - 1
|
# part 1
|
||||||
A = counts[2][2] if radius % 2 == 0 else counts[2][1]
|
yield len(reachable(map, {start}, 6 if len(map) < 20 else 64))
|
||||||
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
|
# part 2
|
||||||
#
|
|
||||||
# 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
|
# the initial map is a square and contains an empty rhombus whose diameter is
|
||||||
answer_2 = a * n * n + b * n + c
|
# the size of the map, and has only empty cells around the middle row and column
|
||||||
print(f"answer 2 (v2) is {answer_2}")
|
#
|
||||||
|
# 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
|
||||||
|
@ -1,111 +1,109 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import logging
|
|
||||||
import os
|
|
||||||
import string
|
import string
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
|
from ..base import BaseSolver
|
||||||
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
|
|
||||||
|
|
||||||
|
|
||||||
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:
|
def build_supports(
|
||||||
if len(lines) < 26:
|
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]],
|
||||||
return string.ascii_uppercase[i]
|
) -> tuple[dict[int, set[int]], dict[int, set[int]]]:
|
||||||
return f"B{i:04d}"
|
# 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(
|
for z in range(sz - 1, 0, -1):
|
||||||
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]],
|
if any(levels[x, y, z] >= 0 for x, y in itertools.product(xs, ys)):
|
||||||
) -> tuple[dict[int, set[int]], dict[int, set[int]]]:
|
break
|
||||||
# 1. compute locations where a brick of sand will land after falling by processing
|
sz, ez = sz - 1, ez - 1
|
||||||
# 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)
|
bricks[i_brick] = ((sx, sy, sz), (ex, ey, ez))
|
||||||
|
zs = range(sz, ez + 1)
|
||||||
|
|
||||||
for z in range(sz - 1, 0, -1):
|
for x, y, z in itertools.product(xs, ys, zs):
|
||||||
if any(levels[x, y, z] >= 0 for x, y in itertools.product(xs, ys)):
|
levels[x, y, z] = i_brick
|
||||||
break
|
|
||||||
sz, ez = sz - 1, ez - 1
|
|
||||||
|
|
||||||
bricks[i_brick] = ((sx, sy, sz), (ex, ey, ez))
|
# 2. compute the bricks that supports any brick
|
||||||
zs = range(sz, ez + 1)
|
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):
|
supported_by[i_brick] = {
|
||||||
levels[x, y, z] = 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
|
for support in supported_by[i_brick]:
|
||||||
supported_by: dict[int, set[int]] = {}
|
supports[support].add(i_brick)
|
||||||
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)
|
|
||||||
|
|
||||||
supported_by[i_brick] = {
|
return supported_by, supports
|
||||||
v
|
|
||||||
for x, y in itertools.product(range(sx, ex + 1), range(sy, ey + 1))
|
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]] = []
|
||||||
if (v := levels[x, y, sz - 1]) != -1
|
for line in lines:
|
||||||
}
|
bricks.append(
|
||||||
logging.info(
|
(
|
||||||
f"{name} supported by {', '.join(map(_name, supported_by[i_brick]))}"
|
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]:
|
# part 2
|
||||||
supports[support].add(i_brick)
|
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]]] = []
|
to_disintegrate_v: set[int] = set()
|
||||||
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
|
for d_brick in to_disintegrate:
|
||||||
bricks = sorted(bricks, key=lambda b: b[0][-1])
|
for supported in supports[d_brick]:
|
||||||
supported_by, supports = build_supports(bricks)
|
supported_by_copy[supported] = supported_by_copy[supported] - {
|
||||||
|
d_brick
|
||||||
|
}
|
||||||
|
|
||||||
# part 1
|
if not supported_by_copy[supported]:
|
||||||
answer_1 = len(bricks) - sum(
|
to_disintegrate_v.add(supported)
|
||||||
any(len(supported_by[supported]) == 1 for supported in supports_to)
|
|
||||||
for supports_to in supports.values()
|
|
||||||
)
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
to_disintegrate = to_disintegrate_v
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
supported_by_copy = dict(supported_by)
|
yield sum(len(falling) for falling in falling_in_chain.values())
|
||||||
|
|
||||||
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}")
|
|
||||||
|
@ -1,11 +1,7 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
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"
|
from ..base import BaseSolver
|
||||||
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
|
|
||||||
|
|
||||||
DirectionType: TypeAlias = Literal[">", "<", "^", "v", ".", "#"]
|
DirectionType: TypeAlias = Literal[">", "<", "^", "v", ".", "#"]
|
||||||
|
|
||||||
@ -35,6 +31,7 @@ def neighbors(
|
|||||||
Compute neighbors of the given node, ignoring the given set of nodes and considering
|
Compute neighbors of the given node, ignoring the given set of nodes and considering
|
||||||
that you can go uphill on slopes.
|
that you can go uphill on slopes.
|
||||||
"""
|
"""
|
||||||
|
n_rows, n_cols = len(grid), len(grid[0])
|
||||||
i, j = node
|
i, j = node
|
||||||
|
|
||||||
for di, dj in Neighbors[grid[i][j]]:
|
for di, dj in Neighbors[grid[i][j]]:
|
||||||
@ -103,65 +100,66 @@ def compute_direct_links(
|
|||||||
return direct
|
return direct
|
||||||
|
|
||||||
|
|
||||||
def longest_path_length(
|
class Solver(BaseSolver):
|
||||||
links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]],
|
def longest_path_length(
|
||||||
start: tuple[int, int],
|
self,
|
||||||
target: tuple[int, int],
|
links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]],
|
||||||
) -> int:
|
start: tuple[int, int],
|
||||||
max_distance: int = -1
|
target: tuple[int, int],
|
||||||
queue: list[tuple[tuple[int, int], int, frozenset[tuple[int, int]]]] = [
|
) -> int:
|
||||||
(start, 0, frozenset({start}))
|
max_distance: int = -1
|
||||||
]
|
queue: list[tuple[tuple[int, int], int, frozenset[tuple[int, int]]]] = [
|
||||||
|
(start, 0, frozenset({start}))
|
||||||
|
]
|
||||||
|
|
||||||
nodes = 0
|
nodes = 0
|
||||||
while queue:
|
while queue:
|
||||||
node, distance, path = queue.pop()
|
node, distance, path = queue.pop()
|
||||||
|
|
||||||
nodes += 1
|
nodes += 1
|
||||||
|
|
||||||
if node == target:
|
if node == target:
|
||||||
max_distance = max(distance, max_distance)
|
max_distance = max(distance, max_distance)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
queue.extend(
|
queue.extend(
|
||||||
(reach, distance + length, path | {reach})
|
(reach, distance + length, path | {reach})
|
||||||
for reach, length in links.get(node, [])
|
for reach, length in links.get(node, [])
|
||||||
if reach not in path
|
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())
|
yield self.longest_path_length(links, start, target)
|
||||||
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}")
|
|
||||||
|
@ -1,63 +1,68 @@
|
|||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from sympy import solve, symbols
|
from sympy import solve, symbols
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
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()
|
|
||||||
|
|
||||||
|
|
||||||
answer_1 = count
|
class Solver(BaseSolver):
|
||||||
print(f"answer 1 is {answer_1}")
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
# part 2
|
positions = np.array(
|
||||||
# equation
|
[[int(c) for c in line.split("@")[0].strip().split(", ")] for line in lines]
|
||||||
# p1 + t1 * v1 == p0 + t1 * v0
|
)
|
||||||
# p2 + t2 * v2 == p0 + t2 * v0
|
velocities = np.array(
|
||||||
# p3 + t3 * v3 == p0 + t3 * v0
|
[[int(c) for c in line.split("@")[1].strip().split(", ")] for line in lines]
|
||||||
# ...
|
)
|
||||||
# pn + tn * vn == p0 + tn * v0
|
|
||||||
#
|
|
||||||
|
|
||||||
# we can solve with only 3 lines since each lines contains 3
|
# part 1
|
||||||
# equations (x / y / z), so 3 lines give 9 equations and 9
|
low, high = (
|
||||||
# variables: position (3), velocities (3) and times (3).
|
[7, 27] if len(positions) <= 10 else [200000000000000, 400000000000000]
|
||||||
n = 3
|
)
|
||||||
|
|
||||||
x, y, z, vx, vy, vz, *ts = symbols(
|
count = 0
|
||||||
"x y z vx vy vz " + " ".join(f"t{i}" for i in range(n + 1))
|
for i1, (p1, v1) in enumerate(zip(positions, velocities)):
|
||||||
)
|
p, r = p1[:2], v1[:2]
|
||||||
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]
|
q, s = positions[i1 + 1 :, :2], velocities[i1 + 1 :, :2]
|
||||||
|
|
||||||
answer_2 = r[x] + r[y] + r[z]
|
rs = np.cross(r, s)
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
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]
|
||||||
|
@ -1,25 +1,25 @@
|
|||||||
import sys
|
# pyright: reportUnknownMemberType=false
|
||||||
|
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
import networkx as nx
|
import networkx as nx
|
||||||
|
|
||||||
components = {
|
from ..base import BaseSolver
|
||||||
(p := line.split(": "))[0]: p[1].split() for line in sys.stdin.read().splitlines()
|
|
||||||
}
|
|
||||||
|
|
||||||
targets = {t for c in components for t in components[c] if t not in components}
|
|
||||||
|
|
||||||
graph = nx.Graph()
|
class Solver(BaseSolver):
|
||||||
graph.add_edges_from((u, v) for u, vs in components.items() for v in vs)
|
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: "nx.Graph[str]" = nx.Graph()
|
||||||
graph.remove_edges_from(cut)
|
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
|
c1, c2 = nx.connected_components(graph)
|
||||||
answer_1 = len(c1) * len(c2)
|
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
# part 1
|
||||||
answer_2 = ...
|
yield len(c1) * len(c2)
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,53 +1,53 @@
|
|||||||
import string
|
import string
|
||||||
import sys
|
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
NOT_A_SYMBOL = "." + string.digits
|
NOT_A_SYMBOL = "." + string.digits
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
|
||||||
|
|
||||||
values: list[int] = []
|
class Solver(BaseSolver):
|
||||||
gears: dict[tuple[int, int], list[int]] = defaultdict(list)
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
for i, line in enumerate(lines):
|
values: list[int] = []
|
||||||
j = 0
|
gears: dict[tuple[int, int], list[int]] = defaultdict(list)
|
||||||
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
|
|
||||||
|
|
||||||
# extract the range of the number and its value
|
for i, line in enumerate(lines):
|
||||||
k = j + 1
|
j = 0
|
||||||
while k < len(line) and line[k] in string.digits:
|
while j < len(line):
|
||||||
k += 1
|
# 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
|
value = int(line[j:k])
|
||||||
# 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] not in NOT_A_SYMBOL:
|
# lookup around the number if there is a symbol - we go through the number
|
||||||
found = True
|
# 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] == "*":
|
if lines[i2][j2] not in NOT_A_SYMBOL:
|
||||||
gears[i2, j2].append(value)
|
found = True
|
||||||
|
|
||||||
if found:
|
if lines[i2][j2] == "*":
|
||||||
values.append(value)
|
gears[i2, j2].append(value)
|
||||||
|
|
||||||
# continue starting from the end of the number
|
if found:
|
||||||
j = k
|
values.append(value)
|
||||||
|
|
||||||
# part 1
|
# continue starting from the end of the number
|
||||||
answer_1 = sum(values)
|
j = k
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
yield sum(values)
|
||||||
answer_2 = sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values()))
|
yield sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values()))
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import sys
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
@ -9,33 +11,34 @@ class Card:
|
|||||||
values: list[int]
|
values: list[int]
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
cards: list[Card] = []
|
cards: list[Card] = []
|
||||||
for line in lines:
|
for line in lines:
|
||||||
id_part, e_part = line.split(":")
|
id_part, e_part = line.split(":")
|
||||||
numbers_s, values_s = e_part.split("|")
|
numbers_s, values_s = e_part.split("|")
|
||||||
cards.append(
|
cards.append(
|
||||||
Card(
|
Card(
|
||||||
id=int(id_part.split()[1]),
|
id=int(id_part.split()[1]),
|
||||||
numbers=[int(v.strip()) for v in numbers_s.strip().split()],
|
numbers=[int(v.strip()) for v in numbers_s.strip().split()],
|
||||||
values=[int(v.strip()) for v in values_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
|
# part 1
|
||||||
answer_1 = sum(2 ** (winning - 1) for winning in winnings if winning > 0)
|
yield sum(2 ** (winning - 1) for winning in winnings if winning > 0)
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
# part 2
|
||||||
card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)}
|
card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)}
|
||||||
card2values = {i: 0 for i in range(len(cards))}
|
card2values = {i: 0 for i in range(len(cards))}
|
||||||
|
|
||||||
for i in range(len(cards)):
|
for i in range(len(cards)):
|
||||||
card2values[i] += 1
|
card2values[i] += 1
|
||||||
for j in card2cards[i]:
|
for j in card2cards[i]:
|
||||||
card2values[j] += card2values[i]
|
card2values[j] += card2values[i]
|
||||||
|
|
||||||
print(f"answer 2 is {sum(card2values.values())}")
|
yield sum(card2values.values())
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import sys
|
from typing import Any, Iterator, Sequence
|
||||||
from typing import Sequence
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
MAP_ORDER = [
|
MAP_ORDER = [
|
||||||
"seed",
|
"seed",
|
||||||
@ -12,55 +13,6 @@ MAP_ORDER = [
|
|||||||
"location",
|
"location",
|
||||||
]
|
]
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
|
||||||
|
|
||||||
# mappings from one category to another, each list contains
|
|
||||||
# ranges stored as (source, target, length), ordered by start and
|
|
||||||
# completed to have no "hole"
|
|
||||||
maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {}
|
|
||||||
|
|
||||||
# parsing
|
|
||||||
index = 2
|
|
||||||
while index < len(lines):
|
|
||||||
p1, _, p2 = lines[index].split()[0].split("-")
|
|
||||||
|
|
||||||
# extract the existing ranges from the file - we store as (source, target, length)
|
|
||||||
# whereas the file is in order (target, source, length)
|
|
||||||
index += 1
|
|
||||||
values: list[tuple[int, int, int]] = []
|
|
||||||
while index < len(lines) and lines[index]:
|
|
||||||
n1, n2, n3 = lines[index].split()
|
|
||||||
values.append((int(n2), int(n1), int(n3)))
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
# sort by source value
|
|
||||||
values.sort()
|
|
||||||
|
|
||||||
# add a 'fake' interval starting at 0 if missing
|
|
||||||
if values[0][0] != 0:
|
|
||||||
values.insert(0, (0, 0, values[0][0]))
|
|
||||||
|
|
||||||
# fill gaps between intervals
|
|
||||||
for i in range(len(values) - 1):
|
|
||||||
next_start = values[i + 1][0]
|
|
||||||
end = values[i][0] + values[i][2]
|
|
||||||
if next_start != end:
|
|
||||||
values.insert(
|
|
||||||
i + 1,
|
|
||||||
(end, end, next_start - end),
|
|
||||||
)
|
|
||||||
|
|
||||||
# add an interval covering values up to at least 2**32 at the end
|
|
||||||
last_start, _, last_length = values[-1]
|
|
||||||
values.append((last_start + last_length, last_start + last_length, 2**32))
|
|
||||||
|
|
||||||
assert all(v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:]))
|
|
||||||
assert values[0][0] == 0
|
|
||||||
assert values[-1][0] + values[-1][-1] >= 2**32
|
|
||||||
|
|
||||||
maps[p1, p2] = values
|
|
||||||
index += 1
|
|
||||||
|
|
||||||
|
|
||||||
def find_range(
|
def find_range(
|
||||||
values: tuple[int, int], map: list[tuple[int, int, int]]
|
values: tuple[int, int], map: list[tuple[int, int, int]]
|
||||||
@ -111,19 +63,71 @@ def find_range(
|
|||||||
return ranges
|
return ranges
|
||||||
|
|
||||||
|
|
||||||
def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]:
|
class Solver(BaseSolver):
|
||||||
for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]):
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])]
|
lines = input.splitlines()
|
||||||
return seeds
|
|
||||||
|
|
||||||
|
# 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
|
def find_location_ranges(
|
||||||
seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()]
|
seeds: Sequence[tuple[int, int]],
|
||||||
answer_1 = min(start for start, _ in find_location_ranges(seeds_p1))
|
) -> Sequence[tuple[int, int]]:
|
||||||
print(f"answer 1 is {answer_1}")
|
for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]):
|
||||||
|
seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])]
|
||||||
|
return seeds
|
||||||
|
|
||||||
# # part 2
|
# parsing
|
||||||
parts = lines[0].split(":")[1].strip().split()
|
index = 2
|
||||||
seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])]
|
while index < len(lines):
|
||||||
answer_2 = min(start for start, _ in find_location_ranges(seeds_p2))
|
p1, _, p2 = lines[index].split()[0].split("-")
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
# 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))
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import math
|
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]:
|
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
|
return t1, t2
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
lines = input.splitlines()
|
||||||
|
|
||||||
# part 1
|
# part 1
|
||||||
times = list(map(int, lines[0].split()[1:]))
|
times = list(map(int, lines[0].split()[1:]))
|
||||||
distances = list(map(int, lines[1].split()[1:]))
|
distances = list(map(int, lines[1].split()[1:]))
|
||||||
answer_1 = math.prod(
|
yield math.prod(
|
||||||
t2 - t1 + 1
|
t2 - t1 + 1
|
||||||
for t1, t2 in (
|
for t1, t2 in (
|
||||||
extreme_times_to_beat(time, distance)
|
extreme_times_to_beat(time, distance)
|
||||||
for time, distance in zip(times, distances)
|
for time, distance in zip(times, distances)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
print(f"answer 1 is {answer_1}")
|
|
||||||
|
|
||||||
# part 2
|
# part 2
|
||||||
time = int(lines[0].split(":")[1].strip().replace(" ", ""))
|
time = int(lines[0].split(":")[1].strip().replace(" ", ""))
|
||||||
distance = int(lines[1].split(":")[1].strip().replace(" ", ""))
|
distance = int(lines[1].split(":")[1].strip().replace(" ", ""))
|
||||||
t1, t2 = extreme_times_to_beat(time, distance)
|
t1, t2 = extreme_times_to_beat(time, distance)
|
||||||
answer_2 = t2 - t1 + 1
|
yield t2 - t1 + 1
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
import sys
|
|
||||||
from collections import Counter, defaultdict
|
from collections import Counter, defaultdict
|
||||||
|
from typing import Any, Iterator
|
||||||
|
|
||||||
|
from ..base import BaseSolver
|
||||||
|
|
||||||
|
|
||||||
class HandTypes:
|
class HandTypes:
|
||||||
@ -32,18 +34,17 @@ def extract_key(hand: str, values: dict[str, int], joker: str = "0") -> tuple[in
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
class Solver(BaseSolver):
|
||||||
cards = [(t[0], int(t[1])) for line in lines if (t := line.split())]
|
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
|
# part 2
|
||||||
values = {card: value for value, card in enumerate("23456789TJQKA")}
|
values = {card: value for value, card in enumerate("J23456789TQKA")}
|
||||||
cards.sort(key=lambda cv: extract_key(cv[0], values=values))
|
cards.sort(key=lambda cv: extract_key(cv[0], values=values, joker="J"))
|
||||||
answer_1 = sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
|
yield 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}")
|
|
||||||
|
@ -1,29 +1,30 @@
|
|||||||
import itertools
|
import itertools
|
||||||
import math
|
import math
|
||||||
import sys
|
from typing import Any, Iterator
|
||||||
|
|
||||||
lines = sys.stdin.read().splitlines()
|
from ..base import BaseSolver
|
||||||
|
|
||||||
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(" = "))
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def path(start: str):
|
class Solver(BaseSolver):
|
||||||
path = [start]
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
it_seq = iter(itertools.cycle(sequence))
|
lines = input.splitlines()
|
||||||
while not path[-1].endswith("Z"):
|
|
||||||
path.append(nodes[path[-1]][next(it_seq)])
|
|
||||||
return path
|
|
||||||
|
|
||||||
|
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
|
def path(start: str):
|
||||||
answer_1 = len(path(next(node for node in nodes if node.endswith("A")))) - 1
|
path = [start]
|
||||||
print(f"answer 1 is {answer_1}")
|
it_seq = iter(itertools.cycle(sequence))
|
||||||
|
while not path[-1].endswith("Z"):
|
||||||
|
path.append(nodes[path[-1]][next(it_seq)])
|
||||||
|
return path
|
||||||
|
|
||||||
# part 2
|
# part 1
|
||||||
answer_2 = math.lcm(*(len(path(node)) - 1 for node in nodes if node.endswith("A")))
|
yield len(path(next(node for node in nodes if node.endswith("A")))) - 1
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
# part 2
|
||||||
|
yield math.lcm(*(len(path(node)) - 1 for node in nodes if node.endswith("A")))
|
||||||
|
@ -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] = []
|
class Solver(BaseSolver):
|
||||||
left_values: list[int] = []
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
for values in data:
|
lines = input.splitlines()
|
||||||
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:])])
|
|
||||||
|
|
||||||
rhs: list[int] = [0]
|
data = [[int(c) for c in line.split()] for line in lines]
|
||||||
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])
|
|
||||||
|
|
||||||
right_values.append(rhs[-1])
|
right_values: list[int] = []
|
||||||
left_values.append(lhs[-1])
|
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
|
rhs: list[int] = [0]
|
||||||
answer_1 = sum(right_values)
|
lhs: list[int] = [0]
|
||||||
print(f"answer 1 is {answer_1}")
|
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
|
right_values.append(rhs[-1])
|
||||||
answer_2 = sum(left_values)
|
left_values.append(lhs[-1])
|
||||||
print(f"answer 2 is {answer_2}")
|
|
||||||
|
# part 1
|
||||||
|
yield sum(right_values)
|
||||||
|
|
||||||
|
# part 2
|
||||||
|
yield sum(left_values)
|
||||||
|
@ -1,14 +1,17 @@
|
|||||||
import sys
|
|
||||||
from collections import Counter
|
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))
|
class Solver(BaseSolver):
|
||||||
answer_2 = sum(value * counter_2.get(value, 0) for value in column_1)
|
def solve(self, input: str) -> Iterator[Any]:
|
||||||
|
values = list(map(int, input.split()))
|
||||||
|
|
||||||
print(f"answer 1 is {answer_1}")
|
column_1 = sorted(values[::2])
|
||||||
print(f"answer 2 is {answer_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)
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user