From a9bcf9ef8f12bc80c0517815ecb55d6ef1bbc6c3 Mon Sep 17 00:00:00 2001 From: Mikael CAPELLE Date: Wed, 4 Dec 2024 16:03:37 +0100 Subject: [PATCH] Start refactoring code better flexibility. --- src/holt59/aoc/2023/day1.py | 50 +++++++------ src/holt59/aoc/2023/day2.py | 68 +++++++++--------- src/holt59/aoc/2023/day3.py | 74 ++++++++++---------- src/holt59/aoc/2023/day4.py | 53 +++++++------- src/holt59/aoc/2023/day5.py | 132 ++++++++++++++++++----------------- src/holt59/aoc/2024/day1.py | 21 +++--- src/holt59/aoc/2024/day10.py | 11 ++- src/holt59/aoc/2024/day11.py | 11 ++- src/holt59/aoc/2024/day12.py | 11 ++- src/holt59/aoc/2024/day13.py | 11 ++- src/holt59/aoc/2024/day14.py | 11 ++- src/holt59/aoc/2024/day15.py | 11 ++- src/holt59/aoc/2024/day16.py | 11 ++- src/holt59/aoc/2024/day17.py | 11 ++- src/holt59/aoc/2024/day18.py | 11 ++- src/holt59/aoc/2024/day19.py | 11 ++- src/holt59/aoc/2024/day2.py | 33 ++++----- src/holt59/aoc/2024/day20.py | 11 ++- src/holt59/aoc/2024/day21.py | 11 ++- src/holt59/aoc/2024/day22.py | 11 ++- src/holt59/aoc/2024/day23.py | 11 ++- src/holt59/aoc/2024/day24.py | 11 ++- src/holt59/aoc/2024/day25.py | 11 ++- src/holt59/aoc/2024/day3.py | 54 +++++++------- src/holt59/aoc/2024/day4.py | 58 ++++++++------- src/holt59/aoc/2024/day7.py | 11 ++- src/holt59/aoc/2024/day8.py | 11 ++- src/holt59/aoc/2024/day9.py | 11 ++- src/holt59/aoc/__main__.py | 22 ++++-- src/holt59/aoc/base.py | 13 ++++ 30 files changed, 384 insertions(+), 403 deletions(-) create mode 100644 src/holt59/aoc/base.py diff --git a/src/holt59/aoc/2023/day1.py b/src/holt59/aoc/2023/day1.py index 3acd9ad..9fe88f7 100644 --- a/src/holt59/aoc/2023/day1.py +++ b/src/holt59/aoc/2023/day1.py @@ -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)) diff --git a/src/holt59/aoc/2023/day2.py b/src/holt59/aoc/2023/day2.py index a768318..9d1a05c 100644 --- a/src/holt59/aoc/2023/day2.py +++ b/src/holt59/aoc/2023/day2.py @@ -1,43 +1,43 @@ import math -import sys -from typing import Literal, TypeAlias, cast +from typing import Any, Iterator, Literal, TypeAlias, cast + +from ..base import BaseSolver CubeType: TypeAlias = Literal["red", "blue", "green"] MAX_CUBES: dict[CubeType, int] = {"red": 12, "green": 13, "blue": 14} -# parse games -lines = sys.stdin.read().splitlines() -games: dict[int, list[dict[CubeType, int]]] = {} -for line in filter(bool, lines): - id_part, sets_part = line.split(":") - games[int(id_part.split(" ")[-1])] = [ - { - cast(CubeType, s[1]): int(s[0]) - for cube_draw in cube_set_s.strip().split(", ") - if (s := cube_draw.split(" ")) - } - for cube_set_s in sets_part.strip().split(";") - ] +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = input.splitlines() + games: dict[int, list[dict[CubeType, int]]] = {} + for line in filter(bool, lines): + id_part, sets_part = line.split(":") -# part 1 -answer_1 = sum( - id - for id, set_of_cubes in games.items() - if all( - n_cubes <= MAX_CUBES[cube] - for cube_set in set_of_cubes - for cube, n_cubes in cube_set.items() - ) -) -print(f"answer 1 is {answer_1}") + games[int(id_part.split(" ")[-1])] = [ + { + cast(CubeType, s[1]): int(s[0]) + for cube_draw in cube_set_s.strip().split(", ") + if (s := cube_draw.split(" ")) + } + for cube_set_s in sets_part.strip().split(";") + ] -# part 2 -answer_2 = sum( - math.prod( - max(cube_set.get(cube, 0) for cube_set in set_of_cubes) for cube in MAX_CUBES - ) - for set_of_cubes in games.values() -) -print(f"answer 2 is {answer_2}") + yield sum( + id + for id, set_of_cubes in games.items() + if all( + n_cubes <= MAX_CUBES[cube] + for cube_set in set_of_cubes + for cube, n_cubes in cube_set.items() + ) + ) + + yield sum( + math.prod( + max(cube_set.get(cube, 0) for cube_set in set_of_cubes) + for cube in MAX_CUBES + ) + for set_of_cubes in games.values() + ) diff --git a/src/holt59/aoc/2023/day3.py b/src/holt59/aoc/2023/day3.py index db1fff4..ae72053 100644 --- a/src/holt59/aoc/2023/day3.py +++ b/src/holt59/aoc/2023/day3.py @@ -1,53 +1,53 @@ import string -import sys from collections import defaultdict +from typing import Any, Iterator + +from ..base import BaseSolver NOT_A_SYMBOL = "." + string.digits -lines = sys.stdin.read().splitlines() -values: list[int] = [] -gears: dict[tuple[int, int], list[int]] = defaultdict(list) +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = input.splitlines() -for i, line in enumerate(lines): - j = 0 - while j < len(line): - # skip everything until a digit is found (start of a number) - if line[j] not in string.digits: - j += 1 - continue + values: list[int] = [] + gears: dict[tuple[int, int], list[int]] = defaultdict(list) - # extract the range of the number and its value - k = j + 1 - while k < len(line) and line[k] in string.digits: - k += 1 + for i, line in enumerate(lines): + j = 0 + while j < len(line): + # skip everything until a digit is found (start of a number) + if line[j] not in string.digits: + j += 1 + continue - value = int(line[j:k]) + # extract the range of the number and its value + k = j + 1 + while k < len(line) and line[k] in string.digits: + k += 1 - # lookup around the number if there is a symbol - we go through the number - # itself but that should not matter since it only contains digits - found = False - for i2 in range(max(0, i - 1), min(i + 1, len(lines) - 1) + 1): - for j2 in range(max(0, j - 1), min(k, len(line) - 1) + 1): - assert i2 >= 0 and i2 < len(lines) - assert j2 >= 0 and j2 < len(line) + value = int(line[j:k]) - if lines[i2][j2] not in NOT_A_SYMBOL: - found = True + # lookup around the number if there is a symbol - we go through the number + # itself but that should not matter since it only contains digits + found = False + for i2 in range(max(0, i - 1), min(i + 1, len(lines) - 1) + 1): + for j2 in range(max(0, j - 1), min(k, len(line) - 1) + 1): + assert i2 >= 0 and i2 < len(lines) + assert j2 >= 0 and j2 < len(line) - if lines[i2][j2] == "*": - gears[i2, j2].append(value) + if lines[i2][j2] not in NOT_A_SYMBOL: + found = True - if found: - values.append(value) + if lines[i2][j2] == "*": + gears[i2, j2].append(value) - # continue starting from the end of the number - j = k + if found: + values.append(value) -# part 1 -answer_1 = sum(values) -print(f"answer 1 is {answer_1}") + # continue starting from the end of the number + j = k -# part 2 -answer_2 = sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values())) -print(f"answer 2 is {answer_2}") + yield sum(values) + yield sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values())) diff --git a/src/holt59/aoc/2023/day4.py b/src/holt59/aoc/2023/day4.py index 3cc9d6c..a29caac 100644 --- a/src/holt59/aoc/2023/day4.py +++ b/src/holt59/aoc/2023/day4.py @@ -1,5 +1,7 @@ -import sys from dataclasses import dataclass +from typing import Any, Iterator + +from ..base import BaseSolver @dataclass(frozen=True) @@ -9,33 +11,34 @@ class Card: values: list[int] -lines = sys.stdin.read().splitlines() +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = input.splitlines() -cards: list[Card] = [] -for line in lines: - id_part, e_part = line.split(":") - numbers_s, values_s = e_part.split("|") - cards.append( - Card( - id=int(id_part.split()[1]), - numbers=[int(v.strip()) for v in numbers_s.strip().split()], - values=[int(v.strip()) for v in values_s.strip().split()], - ) - ) + cards: list[Card] = [] + for line in lines: + id_part, e_part = line.split(":") + numbers_s, values_s = e_part.split("|") + cards.append( + Card( + id=int(id_part.split()[1]), + numbers=[int(v.strip()) for v in numbers_s.strip().split()], + values=[int(v.strip()) for v in values_s.strip().split()], + ) + ) -winnings = [sum(1 for n in card.values if n in card.numbers) for card in cards] + winnings = [sum(1 for n in card.values if n in card.numbers) for card in cards] -# part 1 -answer_1 = sum(2 ** (winning - 1) for winning in winnings if winning > 0) -print(f"answer 1 is {answer_1}") + # part 1 + yield sum(2 ** (winning - 1) for winning in winnings if winning > 0) -# part 2 -card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)} -card2values = {i: 0 for i in range(len(cards))} + # part 2 + card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)} + card2values = {i: 0 for i in range(len(cards))} -for i in range(len(cards)): - card2values[i] += 1 - for j in card2cards[i]: - card2values[j] += card2values[i] + for i in range(len(cards)): + card2values[i] += 1 + for j in card2cards[i]: + card2values[j] += card2values[i] -print(f"answer 2 is {sum(card2values.values())}") + yield sum(card2values.values()) diff --git a/src/holt59/aoc/2023/day5.py b/src/holt59/aoc/2023/day5.py index 33d84c5..8cac8f9 100644 --- a/src/holt59/aoc/2023/day5.py +++ b/src/holt59/aoc/2023/day5.py @@ -1,5 +1,6 @@ -import sys -from typing import Sequence +from typing import Any, Iterator, Sequence + +from ..base import BaseSolver MAP_ORDER = [ "seed", @@ -12,55 +13,6 @@ MAP_ORDER = [ "location", ] -lines = sys.stdin.read().splitlines() - -# mappings from one category to another, each list contains -# ranges stored as (source, target, length), ordered by start and -# completed to have no "hole" -maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {} - -# parsing -index = 2 -while index < len(lines): - p1, _, p2 = lines[index].split()[0].split("-") - - # extract the existing ranges from the file - we store as (source, target, length) - # whereas the file is in order (target, source, length) - index += 1 - values: list[tuple[int, int, int]] = [] - while index < len(lines) and lines[index]: - n1, n2, n3 = lines[index].split() - values.append((int(n2), int(n1), int(n3))) - index += 1 - - # sort by source value - values.sort() - - # add a 'fake' interval starting at 0 if missing - if values[0][0] != 0: - values.insert(0, (0, 0, values[0][0])) - - # fill gaps between intervals - for i in range(len(values) - 1): - next_start = values[i + 1][0] - end = values[i][0] + values[i][2] - if next_start != end: - values.insert( - i + 1, - (end, end, next_start - end), - ) - - # add an interval covering values up to at least 2**32 at the end - last_start, _, last_length = values[-1] - values.append((last_start + last_length, last_start + last_length, 2**32)) - - assert all(v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:])) - assert values[0][0] == 0 - assert values[-1][0] + values[-1][-1] >= 2**32 - - maps[p1, p2] = values - index += 1 - def find_range( values: tuple[int, int], map: list[tuple[int, int, int]] @@ -111,19 +63,71 @@ def find_range( return ranges -def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]: - for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]): - seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])] - return seeds +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = input.splitlines() + # mappings from one category to another, each list contains + # ranges stored as (source, target, length), ordered by start and + # completed to have no "hole" + maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {} -# part 1 - use find_range() with range of length 1 -seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()] -answer_1 = min(start for start, _ in find_location_ranges(seeds_p1)) -print(f"answer 1 is {answer_1}") + def find_location_ranges( + seeds: Sequence[tuple[int, int]], + ) -> Sequence[tuple[int, int]]: + for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]): + seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])] + return seeds -# # part 2 -parts = lines[0].split(":")[1].strip().split() -seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])] -answer_2 = min(start for start, _ in find_location_ranges(seeds_p2)) -print(f"answer 2 is {answer_2}") + # parsing + index = 2 + while index < len(lines): + p1, _, p2 = lines[index].split()[0].split("-") + + # extract the existing ranges from the file - we store as (source, target, length) + # whereas the file is in order (target, source, length) + index += 1 + values: list[tuple[int, int, int]] = [] + while index < len(lines) and lines[index]: + n1, n2, n3 = lines[index].split() + values.append((int(n2), int(n1), int(n3))) + index += 1 + + # sort by source value + values.sort() + + # add a 'fake' interval starting at 0 if missing + if values[0][0] != 0: + values.insert(0, (0, 0, values[0][0])) + + # fill gaps between intervals + for i in range(len(values) - 1): + next_start = values[i + 1][0] + end = values[i][0] + values[i][2] + if next_start != end: + values.insert( + i + 1, + (end, end, next_start - end), + ) + + # add an interval covering values up to at least 2**32 at the end + last_start, _, last_length = values[-1] + values.append((last_start + last_length, last_start + last_length, 2**32)) + + assert all( + v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:]) + ) + assert values[0][0] == 0 + assert values[-1][0] + values[-1][-1] >= 2**32 + + maps[p1, p2] = values + index += 1 + + # part 1 - use find_range() with range of length 1 + seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()] + yield min(start for start, _ in find_location_ranges(seeds_p1)) + + # # part 2 + parts = lines[0].split(":")[1].strip().split() + seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])] + yield min(start for start, _ in find_location_ranges(seeds_p2)) diff --git a/src/holt59/aoc/2024/day1.py b/src/holt59/aoc/2024/day1.py index acbf0c2..181e2a7 100644 --- a/src/holt59/aoc/2024/day1.py +++ b/src/holt59/aoc/2024/day1.py @@ -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) diff --git a/src/holt59/aoc/2024/day10.py b/src/holt59/aoc/2024/day10.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day10.py +++ b/src/holt59/aoc/2024/day10.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day11.py b/src/holt59/aoc/2024/day11.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day11.py +++ b/src/holt59/aoc/2024/day11.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day12.py b/src/holt59/aoc/2024/day12.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day12.py +++ b/src/holt59/aoc/2024/day12.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day13.py b/src/holt59/aoc/2024/day13.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day13.py +++ b/src/holt59/aoc/2024/day13.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day14.py b/src/holt59/aoc/2024/day14.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day14.py +++ b/src/holt59/aoc/2024/day14.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day15.py b/src/holt59/aoc/2024/day15.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day15.py +++ b/src/holt59/aoc/2024/day15.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day16.py b/src/holt59/aoc/2024/day16.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day16.py +++ b/src/holt59/aoc/2024/day16.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day17.py b/src/holt59/aoc/2024/day17.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day17.py +++ b/src/holt59/aoc/2024/day17.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day18.py b/src/holt59/aoc/2024/day18.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day18.py +++ b/src/holt59/aoc/2024/day18.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day19.py b/src/holt59/aoc/2024/day19.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day19.py +++ b/src/holt59/aoc/2024/day19.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day2.py b/src/holt59/aoc/2024/day2.py index 1425d49..ff59110 100644 --- a/src/holt59/aoc/2024/day2.py +++ b/src/holt59/aoc/2024/day2.py @@ -1,22 +1,23 @@ -import sys +from typing import Any, Iterator + +from ..base import BaseSolver -def is_safe(level: list[int]) -> bool: - diff = [a - b for a, b in zip(level[:-1], level[1:], strict=True)] +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + def is_safe(level: list[int]) -> bool: + diff = [a - b for a, b in zip(level[:-1], level[1:], strict=True)] - return sum(d > 0 for d in diff) in (0, len(diff)) and all( - 1 <= abs(d) <= 3 for d in diff - ) + return sum(d > 0 for d in diff) in (0, len(diff)) and all( + 1 <= abs(d) <= 3 for d in diff + ) + def is_any_safe(level: list[int]) -> bool: + return any( + is_safe(level[:i] + level[i + 1 :]) for i in range(0, len(level)) + ) -def is_any_safe(level: list[int]) -> bool: - return any(is_safe(level[:i] + level[i + 1 :]) for i in range(0, len(level))) + levels = [[int(c) for c in r.split()] for r in input.splitlines()] - -levels = [[int(c) for c in r.split()] for r in sys.stdin.read().strip().splitlines()] - -answer_1 = sum(is_safe(level) for level in levels) -answer_2 = sum(is_safe(level) or is_any_safe(level) for level in levels) - -print(f"answer 1 is {answer_1}") -print(f"answer 2 is {answer_2}") + yield sum(is_safe(level) for level in levels) + yield sum(is_safe(level) or is_any_safe(level) for level in levels) diff --git a/src/holt59/aoc/2024/day20.py b/src/holt59/aoc/2024/day20.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day20.py +++ b/src/holt59/aoc/2024/day20.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day21.py b/src/holt59/aoc/2024/day21.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day21.py +++ b/src/holt59/aoc/2024/day21.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day22.py b/src/holt59/aoc/2024/day22.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day22.py +++ b/src/holt59/aoc/2024/day22.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day23.py b/src/holt59/aoc/2024/day23.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day23.py +++ b/src/holt59/aoc/2024/day23.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day24.py b/src/holt59/aoc/2024/day24.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day24.py +++ b/src/holt59/aoc/2024/day24.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day25.py b/src/holt59/aoc/2024/day25.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day25.py +++ b/src/holt59/aoc/2024/day25.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day3.py b/src/holt59/aoc/2024/day3.py index 09afee0..ce2fc61 100644 --- a/src/holt59/aoc/2024/day3.py +++ b/src/holt59/aoc/2024/day3.py @@ -1,34 +1,30 @@ import re -import sys -from typing import Iterator +from typing import Any, Iterator + +from ..base import BaseSolver -def extract_multiply(line: str) -> Iterator[int]: - for m in re.finditer(r"mul\(([0-9]{1,3}),\s*([0-9]{1,3})\)", line): - yield int(m.group(1)) * int(m.group(2)) +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + def extract_multiply(line: str) -> Iterator[int]: + for m in re.finditer(r"mul\(([0-9]{1,3}),\s*([0-9]{1,3})\)", line): + yield int(m.group(1)) * int(m.group(2)) + def valid_memory_blocks(line: str) -> Iterator[str]: + accumulate = True + while line: + if accumulate: + if (dont_i := line.find("don't()")) != -1: + yield line[:dont_i] + line, accumulate = line[dont_i:], False + else: + yield line + line = "" + else: + if (do_i := line.find("do()")) != -1: + line, accumulate = line[do_i:], True + else: + line = "" -def valid_memory_blocks(line: str) -> Iterator[str]: - accumulate = True - while line: - if accumulate: - if (dont_i := line.find("don't()")) != -1: - yield line[:dont_i] - line, accumulate = line[dont_i:], False - else: - yield line - line = "" - else: - if (do_i := line.find("do()")) != -1: - line, accumulate = line[do_i:], True - else: - line = "" - - -line = sys.stdin.read().strip() - -answer_1 = sum(extract_multiply(line)) -answer_2 = sum(sum(extract_multiply(block)) for block in valid_memory_blocks(line)) - -print(f"answer 1 is {answer_1}") -print(f"answer 2 is {answer_2}") + yield sum(extract_multiply(input)) + yield sum(sum(extract_multiply(block)) for block in valid_memory_blocks(input)) diff --git a/src/holt59/aoc/2024/day4.py b/src/holt59/aoc/2024/day4.py index 656ed2e..56cd80b 100644 --- a/src/holt59/aoc/2024/day4.py +++ b/src/holt59/aoc/2024/day4.py @@ -1,31 +1,37 @@ 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( - line.count("XMAS") + line.count("SAMX") - for i in range(n) - for ri, rk, ro, ci, ck, cm in ( - (1, 0, 0, 0, 1, n), - (0, 1, 0, 1, 0, n), - (0, 1, 0, 1, 1, n - i), - (0, -1, -1, 1, 1, n - i), - (1, 1, 0, 0, 1, n - i if i != 0 else 0), - (-1, -1, -1, 0, 1, n - i if i != 0 else 0), - ) - if ( - line := "".join(lines[ri * i + rk * k + ro][ci * i + ck * k] for k in range(cm)) - ) -) -answer_2 = sum( - lines[i][j] == "A" - and "".join(lines[i + di][j + dj] for di, dj in it.product((-1, 1), (-1, 1))) - in {"MSMS", "SSMM", "MMSS", "SMSM"} - for i, j in it.product(range(1, n - 1), range(1, n - 1)) -) +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = input.splitlines() + n = len(lines) -print(f"answer 1 is {answer_1}") -print(f"answer 2 is {answer_2}") + yield sum( + line.count("XMAS") + line.count("SAMX") + for i in range(n) + for ri, rk, ro, ci, ck, cm in ( + (1, 0, 0, 0, 1, n), + (0, 1, 0, 1, 0, n), + (0, 1, 0, 1, 1, n - i), + (0, -1, -1, 1, 1, n - i), + (1, 1, 0, 0, 1, n - i if i != 0 else 0), + (-1, -1, -1, 0, 1, n - i if i != 0 else 0), + ) + if ( + line := "".join( + lines[ri * i + rk * k + ro][ci * i + ck * k] for k in range(cm) + ) + ) + ) + + yield sum( + lines[i][j] == "A" + and "".join( + lines[i + di][j + dj] for di, dj in it.product((-1, 1), (-1, 1)) + ) + in {"MSMS", "SSMM", "MMSS", "SMSM"} + for i, j in it.product(range(1, n - 1), range(1, n - 1)) + ) diff --git a/src/holt59/aoc/2024/day7.py b/src/holt59/aoc/2024/day7.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day7.py +++ b/src/holt59/aoc/2024/day7.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day8.py b/src/holt59/aoc/2024/day8.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day8.py +++ b/src/holt59/aoc/2024/day8.py @@ -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]: ... diff --git a/src/holt59/aoc/2024/day9.py b/src/holt59/aoc/2024/day9.py index 4022929..07e201e 100644 --- a/src/holt59/aoc/2024/day9.py +++ b/src/holt59/aoc/2024/day9.py @@ -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]: ... diff --git a/src/holt59/aoc/__main__.py b/src/holt59/aoc/__main__.py index 88ddc40..59b0b11 100644 --- a/src/holt59/aoc/__main__.py +++ b/src/holt59/aoc/__main__.py @@ -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}") diff --git a/src/holt59/aoc/base.py b/src/holt59/aoc/base.py new file mode 100644 index 0000000..9ff1a16 --- /dev/null +++ b/src/holt59/aoc/base.py @@ -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]: ...