diff --git a/src/holt59/aoc/2022/day1.py b/src/holt59/aoc/2022/day1.py index 4dc115c..17776a8 100644 --- a/src/holt59/aoc/2022/day1.py +++ b/src/holt59/aoc/2022/day1.py @@ -1,7 +1,12 @@ -import sys +from typing import Any, Iterator -blocks = sys.stdin.read().split("\n\n") -values = sorted(sum(map(int, block.split())) for block in blocks) +from ..base import BaseSolver -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:]) diff --git a/src/holt59/aoc/2022/day10.py b/src/holt59/aoc/2022/day10.py index 73470c6..f06442e 100644 --- a/src/holt59/aoc/2022/day10.py +++ b/src/holt59/aoc/2022/day10.py @@ -1,38 +1,43 @@ -import sys +from typing import Any, Iterator -lines = sys.stdin.read().splitlines() - -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}") +from ..base import BaseSolver -for i in range(6): - for j in range(40): - v = values[1 + i * 40 + j] +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = [line.strip() for line in input.splitlines()] - if j >= v - 1 and j <= v + 1: - print("#", end="") - else: - print(".", end="") + cycle, x = 1, 1 + values = {cycle: x} - 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" + ) diff --git a/src/holt59/aoc/2022/day11.py b/src/holt59/aoc/2022/day11.py index 5c2efd9..a505d71 100644 --- a/src/holt59/aoc/2022/day11.py +++ b/src/holt59/aoc/2022/day11.py @@ -1,7 +1,8 @@ import copy -import sys 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: @@ -119,24 +120,28 @@ def monkey_business(inspects: dict[Monkey, int]) -> int: 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 -answer_1 = monkey_business( - run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3) -) -print(f"answer 1 is {answer_1}") + # case 1: we simply divide the worry by 3 after applying the monkey worry operation + yield monkey_business( + run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3) + ) -# 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 -# -# (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 -# we use the product of all test value -# -total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1) -answer_2 = monkey_business( - run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value) -) -print(f"answer 2 is {answer_2}") + # 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 + # + # (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 + # we use the product of all test value + # + total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1) + yield monkey_business( + run( + copy.deepcopy(monkeys), + 10_000, + me_worry_fn=lambda w: w % total_test_value, + ) + ) diff --git a/src/holt59/aoc/2022/day12.py b/src/holt59/aoc/2022/day12.py index 35a7116..f4811a0 100644 --- a/src/holt59/aoc/2022/day12.py +++ b/src/holt59/aoc/2022/day12.py @@ -1,6 +1,7 @@ import heapq -import sys -from typing import Callable, Iterator, TypeVar +from typing import Any, Callable, Iterator, TypeVar + +from ..base import BaseSolver Node = TypeVar("Node") @@ -68,30 +69,6 @@ def make_path(parents: dict[Node, Node], start: Node, end: Node) -> list[Node] | 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( grid: list[list[int]], node: tuple[int, int], up: bool ) -> Iterator[tuple[int, int]]: @@ -118,46 +95,74 @@ def neighbors( # === 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 -end: tuple[int, int] | None = None + graph = [["." for _c in range(n_cols)] for _r in range(n_rows)] + graph[end[0]][end[1]] = "E" -# for part 2 -start_s: list[tuple[int, int]] = [] + for i in range(0, len(path) - 1): + cr, cc = path[i] + nr, nc = path[i + 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)) + 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]) -assert start is not None -assert end is not None + for row in graph: + self.logger.info("".join(row)) -# fix values -grid[start[0]][start[1]] = 0 -grid[end[0]][end[1]] = ord("z") - ord("a") + def solve(self, input: str) -> Iterator[Any]: + lines = input.splitlines() + grid = [[ord(cell) - ord("a") for cell in line] for line in lines] -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 + start: tuple[int, int] | None = None + end: tuple[int, int] | None = 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( - start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1 -) -answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s) -print(f"answer 2 is {answer_2}") + assert start is not None + assert end is not None + + # fix values + 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) diff --git a/src/holt59/aoc/2022/day13.py b/src/holt59/aoc/2022/day13.py index c9d2361..e2c5d72 100644 --- a/src/holt59/aoc/2022/day13.py +++ b/src/holt59/aoc/2022/day13.py @@ -1,11 +1,8 @@ import json -import sys 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") - -pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks] +from ..base import BaseSolver Packet: TypeAlias = list[int | list["Packet"]] @@ -28,14 +25,18 @@ def compare(lhs: Packet, rhs: Packet) -> int: return len(rhs) - len(lhs) -answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0) -print(f"answer_1 is {answer_1}") +class Solver(BaseSolver): + 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] -packets.extend(dividers) -packets = list(reversed(sorted(packets, key=cmp_to_key(compare)))) + dividers = [[[2]], [[6]]] -d_index = [packets.index(d) + 1 for d in dividers] -print(f"answer 2 is {d_index[0] * d_index[1]}") + packets = [packet for packets in pairs for packet in packets] + 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] diff --git a/src/holt59/aoc/2022/day14.py b/src/holt59/aoc/2022/day14.py index 17d937c..4603157 100644 --- a/src/holt59/aoc/2022/day14.py +++ b/src/holt59/aoc/2022/day14.py @@ -1,6 +1,7 @@ -import sys from enum import Enum, auto -from typing import Callable, cast +from typing import Any, Callable, Iterator, cast + +from ..base import BaseSolver class Cell(Enum): @@ -12,26 +13,6 @@ class Cell(Enum): 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( blocks: dict[tuple[int, int], Cell], stop_fn: Callable[[int, int], bool], @@ -84,57 +65,75 @@ def flow( # === inputs === -lines = sys.stdin.read().splitlines() -paths: list[list[tuple[int, int]]] = [] -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 - ] - ) +class Solver(BaseSolver): + def print_blocks(self, 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), + ) -blocks: dict[tuple[int, int], Cell] = {} -for path in paths: - for start, end in zip(path[:-1], path[1:]): - x_start = min(start[0], end[0]) - x_end = max(start[0], end[0]) + 1 - y_start = min(start[1], end[1]) - y_end = max(start[1], end[1]) + 1 + for y in range(y_min, y_max + 1): + self.logger.info( + "".join( + str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1) + ) + ) - for x in range(x_start, x_end): - for y in range(y_start, y_end): - blocks[x, y] = Cell.ROCK + def solve(self, input: str) -> Iterator[Any]: + lines = [line.strip() for line in input.splitlines()] -print_blocks(blocks) -print() + paths: list[list[tuple[int, int]]] = [] + 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 = ( - 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 path in paths: + for start, end in zip(path[:-1], path[1:]): + x_start = min(start[0], end[0]) + 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( - 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() + self.print_blocks(blocks) -# === part 2 === + y_max = max(y for _, y in blocks) -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 -print_blocks(blocks_2) -print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}") + # === part 1 === + + blocks_1 = flow( + blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR + ) + self.print_blocks(blocks_1) + yield sum(v == Cell.SAND for v in blocks_1.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()) diff --git a/src/holt59/aoc/2022/day15.py b/src/holt59/aoc/2022/day15.py index fb13272..74fd076 100644 --- a/src/holt59/aoc/2022/day15.py +++ b/src/holt59/aoc/2022/day15.py @@ -1,4 +1,3 @@ -import sys from typing import Any, Iterator import numpy as np @@ -62,8 +61,9 @@ class Solver(BaseSolver): for (sx, sy), (bx, by) in sensor_to_beacon.items(): d = abs(sx - bx) + abs(sy - by) m.add_constraint( - m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}" - ) # type: ignore + m.abs(x - sx) + m.abs(y - sy) >= d + 1, # type: ignore + ctname=f"ct_{sx}_{sy}", + ) m.set_objective("min", x + y) diff --git a/src/holt59/aoc/2022/day16.py b/src/holt59/aoc/2022/day16.py index 055ed25..268588d 100644 --- a/src/holt59/aoc/2022/day16.py +++ b/src/holt59/aoc/2022/day16.py @@ -5,10 +5,12 @@ import itertools import re import sys from collections import defaultdict -from typing import FrozenSet, NamedTuple +from typing import Any, FrozenSet, Iterator, NamedTuple from tqdm import tqdm +from ..base import BaseSolver + class Pipe(NamedTuple): name: str diff --git a/src/holt59/aoc/2022/day17.py b/src/holt59/aoc/2022/day17.py index 6f02c26..2ec58f3 100644 --- a/src/holt59/aoc/2022/day17.py +++ b/src/holt59/aoc/2022/day17.py @@ -1,8 +1,10 @@ import sys -from typing import Sequence, TypeVar +from typing import Any, Iterator, Sequence, TypeVar import numpy as np +from ..base import BaseSolver + T = TypeVar("T") diff --git a/src/holt59/aoc/2022/day18.py b/src/holt59/aoc/2022/day18.py index c8d0f9a..52c3b60 100644 --- a/src/holt59/aoc/2022/day18.py +++ b/src/holt59/aoc/2022/day18.py @@ -1,7 +1,10 @@ import sys +from typing import Any, Iterator import numpy as np +from ..base import BaseSolver + xyz = np.asarray( [ tuple(int(x) for x in row.split(",")) # type: ignore diff --git a/src/holt59/aoc/2022/day19.py b/src/holt59/aoc/2022/day19.py index 7d888fa..9992f61 100644 --- a/src/holt59/aoc/2022/day19.py +++ b/src/holt59/aoc/2022/day19.py @@ -1,10 +1,12 @@ import sys -from typing import Any, Literal +from typing import Any, Iterator, Literal import numpy as np import parse # pyright: ignore[reportMissingTypeStubs] from numpy.typing import NDArray +from ..base import BaseSolver + Reagent = Literal["ore", "clay", "obsidian", "geode"] REAGENTS: tuple[Reagent, ...] = ( "ore", diff --git a/src/holt59/aoc/2022/day2.py b/src/holt59/aoc/2022/day2.py index 4d23c7c..c78b283 100644 --- a/src/holt59/aoc/2022/day2.py +++ b/src/holt59/aoc/2022/day2.py @@ -1,4 +1,6 @@ -import sys +from typing import Any, Iterator + +from ..base import BaseSolver 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 -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 -# modulo-3 arithmetic -# -# 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))) -# + # the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using + # modulo-3 arithmetic + # + # 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))) + # -# 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 -values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines] + # 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 + values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines] -# part 1 - 13526 -print(f"answer 1 is {sum(score_1(*v) for v in values)}") + # part 1 - 13526 + yield sum(score_1(*v) for v in values) -# part 2 - 14204 -print(f"answer 2 is {sum(score_2(*v) for v in values)}") + # part 2 - 14204 + yield sum(score_2(*v) for v in values) diff --git a/src/holt59/aoc/2022/day20.py b/src/holt59/aoc/2022/day20.py index 5448d0a..e4b3d6f 100644 --- a/src/holt59/aoc/2022/day20.py +++ b/src/holt59/aoc/2022/day20.py @@ -1,6 +1,9 @@ from __future__ import annotations import sys +from typing import Any, Iterator + +from ..base import BaseSolver class Number: diff --git a/src/holt59/aoc/2022/day21.py b/src/holt59/aoc/2022/day21.py index c5b3736..650423e 100644 --- a/src/holt59/aoc/2022/day21.py +++ b/src/holt59/aoc/2022/day21.py @@ -1,6 +1,8 @@ import operator import sys -from typing import Callable +from typing import Any, Callable, Iterator + +from ..base import BaseSolver def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int: diff --git a/src/holt59/aoc/2022/day22.py b/src/holt59/aoc/2022/day22.py index 571f314..694b376 100644 --- a/src/holt59/aoc/2022/day22.py +++ b/src/holt59/aoc/2022/day22.py @@ -1,9 +1,11 @@ import re import sys -from typing import Callable +from typing import Any, Callable, Iterator import numpy as np +from ..base import BaseSolver + VOID, EMPTY, WALL = 0, 1, 2 TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL} diff --git a/src/holt59/aoc/2022/day23.py b/src/holt59/aoc/2022/day23.py index 902c20b..612d39e 100644 --- a/src/holt59/aoc/2022/day23.py +++ b/src/holt59/aoc/2022/day23.py @@ -1,6 +1,9 @@ import itertools import sys from collections import defaultdict +from typing import Any, Iterator + +from ..base import BaseSolver Directions = list[ tuple[ diff --git a/src/holt59/aoc/2022/day24.py b/src/holt59/aoc/2022/day24.py index 6ea76b9..21cd515 100644 --- a/src/holt59/aoc/2022/day24.py +++ b/src/holt59/aoc/2022/day24.py @@ -2,6 +2,9 @@ import heapq import math import sys from collections import defaultdict +from typing import Any, Iterator + +from ..base import BaseSolver lines = sys.stdin.read().splitlines() diff --git a/src/holt59/aoc/2022/day25.py b/src/holt59/aoc/2022/day25.py index 4327247..e414825 100644 --- a/src/holt59/aoc/2022/day25.py +++ b/src/holt59/aoc/2022/day25.py @@ -1,4 +1,7 @@ import sys +from typing import Any, Iterator + +from ..base import BaseSolver lines = sys.stdin.read().splitlines() diff --git a/src/holt59/aoc/2022/day3.py b/src/holt59/aoc/2022/day3.py index b7740ed..26f0eef 100644 --- a/src/holt59/aoc/2022/day3.py +++ b/src/holt59/aoc/2022/day3.py @@ -1,23 +1,28 @@ 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 -priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)} +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = [line.strip() for line in input.splitlines()] -# part 1 -part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2)) -print(f"answer 1 is {part1}") + # extract content of each part + parts = [ + (set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines + ] -# part 2 -n_per_group = 3 -part2 = 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]) -) -print(f"answer 2 is {part2}") + # priorities + priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)} + + # part 1 + yield sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2)) + + # part 2 + 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]) + ) diff --git a/src/holt59/aoc/2022/day4.py b/src/holt59/aoc/2022/day4.py index bf6aae1..dfbb713 100644 --- a/src/holt59/aoc/2022/day4.py +++ b/src/holt59/aoc/2022/day4.py @@ -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]: @@ -8,10 +8,13 @@ def make_range(value: str) -> set[int]: 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) -print(f"answer 1 is {answer_1}") + sections = [ + 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) -print(f"answer 1 is {answer_2}") + yield sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections) + yield sum(bool(s1.intersection(s2)) for s1, s2 in sections) diff --git a/src/holt59/aoc/2022/day5.py b/src/holt59/aoc/2022/day5.py index 04ba94f..831470c 100644 --- a/src/holt59/aoc/2022/day5.py +++ b/src/holt59/aoc/2022/day5.py @@ -1,41 +1,43 @@ 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 -# 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() +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + blocks_s, moves_s = (part.splitlines() for part in input.split("\n\n")) - if crate: - blocks[stack].append(crate) + blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()} -# part 1 - deep copy for part 2 -blocks_1 = copy.deepcopy(blocks) + # this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the + # 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: - _, count_s, _, from_, _, to_ = move.strip().split() + if crate: + blocks[stack].append(crate) - for _i in range(int(count_s)): - blocks_1[to_].append(blocks_1[from_].pop()) + # part 1 - deep copy for part 2 + blocks_1 = copy.deepcopy(blocks) -# part 2 -blocks_2 = copy.deepcopy(blocks) + for move in moves_s: + _, count_s, _, from_, _, to_ = move.strip().split() -for move in moves_s: - _, count_s, _, from_, _, to_ = move.strip().split() - count = int(count_s) + for _i in range(int(count_s)): + blocks_1[to_].append(blocks_1[from_].pop()) - blocks_2[to_].extend(blocks_2[from_][-count:]) - del blocks_2[from_][-count:] + # part 2 + blocks_2 = copy.deepcopy(blocks) -answer_1 = "".join(s[-1] for s in blocks_1.values()) -print(f"answer 1 is {answer_1}") + for move in moves_s: + _, count_s, _, from_, _, to_ = move.strip().split() + count = int(count_s) -answer_2 = "".join(s[-1] for s in blocks_2.values()) -print(f"answer 2 is {answer_2}") + blocks_2[to_].extend(blocks_2[from_][-count:]) + 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()) diff --git a/src/holt59/aoc/2022/day6.py b/src/holt59/aoc/2022/day6.py index 086f33e..db7faf6 100644 --- a/src/holt59/aoc/2022/day6.py +++ b/src/holt59/aoc/2022/day6.py @@ -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: @@ -8,8 +10,7 @@ def index_of_first_n_differents(data: str, n: int) -> int: return -1 -data = sys.stdin.read().strip() - - -print(f"answer 1 is {index_of_first_n_differents(data, 4)}") -print(f"answer 2 is {index_of_first_n_differents(data, 14)}") +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + yield index_of_first_n_differents(input, 4) + yield index_of_first_n_differents(input, 14) diff --git a/src/holt59/aoc/2022/day7.py b/src/holt59/aoc/2022/day7.py index c95e1b9..a3cb585 100644 --- a/src/holt59/aoc/2022/day7.py +++ b/src/holt59/aoc/2022/day7.py @@ -1,80 +1,81 @@ -import sys from pathlib import Path +from typing import Any, Iterator -lines = sys.stdin.read().splitlines() - -# 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 +from ..base import BaseSolver -def compute_size(path: Path) -> int: - size = sizes[path] +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = [line.strip() for line in input.splitlines()] - if size >= 0: - return size + # 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 + # - 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 -answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000) -print(f"answer 1 is {answer_1}") + trees[cur_path] = [] + sizes[cur_path] = -1 -# part 2 -total_space = 70_000_000 -update_space = 30_000_000 -free_space = total_space - acc_sizes[base_path] + for line in lines[1:]: + # command + if line.startswith("$"): + 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) -print(f"answer 2 is {answer_2}") + # 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: + 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) diff --git a/src/holt59/aoc/2022/day8.py b/src/holt59/aoc/2022/day8.py index ee75ede..ba06574 100644 --- a/src/holt59/aoc/2022/day8.py +++ b/src/holt59/aoc/2022/day8.py @@ -1,53 +1,54 @@ -import sys +from typing import Any, Iterator import numpy as np 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 -highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1 -highest_trees[1:-1, 1:-1] = [ - [ - [ - trees[:i, j].max(), - trees[i + 1 :, j].max(), - trees[i, :j].max(), - trees[i, j + 1 :].max(), +class Solver(BaseSolver): + def solve(self, input: str) -> Iterator[Any]: + lines = [line.strip() for line in input.splitlines()] + + trees = np.array([[int(x) for x in row] for row in lines]) + + # answer 1 + highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1 + 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() -print(f"answer 1 is {answer_1}") + yield (highest_trees.min(axis=2) < trees).sum() + 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: - w = np.where(row_of_trees >= value)[0] + if not w.size: + return len(row_of_trees) - if not w.size: - return len(row_of_trees) + return w[0] + 1 - return w[0] + 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 :, j], trees[i, j]), + # 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 :, j], trees[i, j]), + ] + 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_2 = np.prod(v_distances, axis=2).max() -print(f"answer 2 is {answer_2}") + yield np.prod(v_distances, axis=2).max() diff --git a/src/holt59/aoc/2022/day9.py b/src/holt59/aoc/2022/day9.py index f81ec93..6bfa243 100644 --- a/src/holt59/aoc/2022/day9.py +++ b/src/holt59/aoc/2022/day9.py @@ -1,7 +1,10 @@ -import sys +import itertools as it +from typing import Any, Iterator import numpy as np +from ..base import BaseSolver + def move(head: tuple[int, int], command: str) -> tuple[int, int]: h_col, h_row = head @@ -43,17 +46,14 @@ def run(commands: list[str], n_blocks: int) -> list[tuple[int, int]]: 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 -commands: list[str] = [] -for line in lines: - d, c = line.split() - commands.extend(d * int(c)) + # flatten the commands + commands = list( + it.chain(*(p[0] * int(p[1]) for line in lines if (p := line.split()))) + ) - -visited_1 = run(commands, n_blocks=2) -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))}") + yield len(set(run(commands, n_blocks=2))) + yield len(set(run(commands, n_blocks=10))) diff --git a/src/holt59/aoc/2023/day25.py b/src/holt59/aoc/2023/day25.py index d75d1b7..0f80ab7 100644 --- a/src/holt59/aoc/2023/day25.py +++ b/src/holt59/aoc/2023/day25.py @@ -1,3 +1,5 @@ +# pyright: reportUnknownMemberType=false + from typing import Any, Iterator import networkx as nx