Start refactoring code better flexibility.

This commit is contained in:
Mikael CAPELLE 2024-12-04 16:03:37 +01:00
parent 1caf93b38b
commit a9bcf9ef8f
30 changed files with 384 additions and 403 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,9 +1,11 @@
import argparse
import importlib
import os
import logging
import sys
from pathlib import Path
from .base import BaseSolver
def main():
parser = argparse.ArgumentParser("Holt59 Advent-Of-Code Runner")
@ -40,8 +42,7 @@ def main():
day: int = args.day
# TODO: change this
if verbose:
os.environ["AOC_VERBOSE"] = "True"
logging.basicConfig(level=logging.INFO if verbose else logging.WARNING)
if input_path is None:
input_path = Path(__file__).parent.joinpath(
@ -49,11 +50,18 @@ def main():
)
assert input_path.exists(), f"{input_path} missing"
solver_class: type[BaseSolver] = importlib.import_module(
f".{year}.day{day}", __package__
).Solver
solver = solver_class(logging.getLogger("AOC"), year, day)
data: str
if stdin:
importlib.import_module(f".{year}.day{day}", __package__)
data = sys.stdin.read()
else:
with open(input_path) as fp:
sys.stdin = fp
importlib.import_module(f".{year}.day{day}", __package__)
data = fp.read()
sys.stdin = sys.__stdin__
for i_answer, answer in enumerate(solver.solve(data.strip())):
print(f"answer {i_answer + 1} is {answer}")

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

@ -0,0 +1,13 @@
from abc import abstractmethod
from logging import Logger
from typing import Any, Final, Iterator
class BaseSolver:
def __init__(self, logger: Logger, year: int, day: int):
self.logger: Final = logger
self.year: Final = year
self.day: Final = day
@abstractmethod
def solve(self, input: str) -> Iterator[Any]: ...