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:
		| @@ -1,10 +1,12 @@ | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| line = sys.stdin.read().strip() | ||||
|  | ||||
| floor = 0 | ||||
| floors = [(floor := floor + (1 if c == "(" else -1)) for c in line] | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| print(f"answer 1 is {floors[-1]}") | ||||
| print(f"answer 2 is {floors.index(-1)}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         floor = 0 | ||||
|         floors = [(floor := floor + (1 if c == "(" else -1)) for c in input] | ||||
|  | ||||
|         yield floors[-1] | ||||
|         yield floors.index(-1) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import itertools | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| line = sys.stdin.read().strip() | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| # see http://www.se16.info/js/lands2.htm for the explanation of 'atoms' (or elements) | ||||
| # | ||||
| @@ -9,7 +9,7 @@ line = sys.stdin.read().strip() | ||||
| # CodeGolf answer https://codegolf.stackexchange.com/a/8479/42148 | ||||
|  | ||||
| # fmt: off | ||||
| atoms = [ | ||||
| ATOMS: list[tuple[str, tuple[int, ...]]] = [ | ||||
|     ("22", (0, )),  # 0 | ||||
|     ("13112221133211322112211213322112", (71, 90, 0, 19, 2, )),  # 1 | ||||
|     ("312211322212221121123222112", (1, )),  # 2 | ||||
| @@ -105,7 +105,7 @@ atoms = [ | ||||
| ] | ||||
| # fmt: on | ||||
|  | ||||
| starters = [ | ||||
| STARTERS = [ | ||||
|     "1", | ||||
|     "11", | ||||
|     "21", | ||||
| @@ -122,27 +122,26 @@ def look_and_say_length(s: str, n: int) -> int: | ||||
|     if n == 0: | ||||
|         return len(s) | ||||
|  | ||||
|     if s in starters: | ||||
|     if s in STARTERS: | ||||
|         return look_and_say_length( | ||||
|             "".join(f"{len(list(g))}{k}" for k, g in itertools.groupby(s)), n - 1 | ||||
|         ) | ||||
|  | ||||
|     counts = {i: 0 for i in range(len(atoms))} | ||||
|     idx = next(i for i, (a, _) in enumerate(atoms) if s == a) | ||||
|     counts = {i: 0 for i in range(len(ATOMS))} | ||||
|     idx = next(i for i, (a, _) in enumerate(ATOMS) if s == a) | ||||
|     counts[idx] = 1 | ||||
|  | ||||
|     for _ in range(n): | ||||
|         c2 = {i: 0 for i in range(len(atoms))} | ||||
|         c2 = {i: 0 for i in range(len(ATOMS))} | ||||
|         for i in counts: | ||||
|             for j in atoms[i][1]: | ||||
|             for j in ATOMS[i][1]: | ||||
|                 c2[j] += counts[i] | ||||
|         counts = c2 | ||||
|  | ||||
|     return sum(counts[i] * len(a[0]) for i, a in enumerate(atoms)) | ||||
|     return sum(counts[i] * len(a[0]) for i, a in enumerate(ATOMS)) | ||||
|  | ||||
|  | ||||
| answer_1 = look_and_say_length(line, 40) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| answer_2 = look_and_say_length(line, 50) | ||||
| print(f"answer 2 is {answer_2}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any] | None: | ||||
|         yield look_and_say_length(input, 40) | ||||
|         yield look_and_say_length(input, 50) | ||||
|   | ||||
| @@ -1,5 +1,7 @@ | ||||
| import itertools | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| def is_valid(p: str) -> bool: | ||||
| @@ -40,10 +42,8 @@ def find_next_password(p: str) -> str: | ||||
|     return p | ||||
|  | ||||
|  | ||||
| line = sys.stdin.read().strip() | ||||
|  | ||||
| answer_1 = find_next_password(line) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| answer_2 = find_next_password(increment(answer_1)) | ||||
| print(f"answer 2 is {answer_2}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         answer_1 = find_next_password(input) | ||||
|         yield answer_1 | ||||
|         yield find_next_password(increment(answer_1)) | ||||
|   | ||||
| @@ -1,6 +1,7 @@ | ||||
| import json | ||||
| import sys | ||||
| from typing import TypeAlias | ||||
| from typing import Any, Iterator, TypeAlias | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| JsonObject: TypeAlias = dict[str, "JsonObject"] | list["JsonObject"] | int | str | ||||
|  | ||||
| @@ -18,10 +19,9 @@ def json_sum(value: JsonObject, ignore: str | None = None) -> int: | ||||
|         return 0 | ||||
|  | ||||
|  | ||||
| data: JsonObject = json.load(sys.stdin) | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         data: JsonObject = json.loads(input) | ||||
|  | ||||
| answer_1 = json_sum(data) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| answer_2 = json_sum(data, "red") | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield json_sum(data) | ||||
|         yield json_sum(data, "red") | ||||
|   | ||||
| @@ -1,10 +1,11 @@ | ||||
| import itertools | ||||
| import sys | ||||
| from collections import defaultdict | ||||
| from typing import Literal, cast | ||||
| from typing import Any, Iterator, Literal, cast | ||||
|  | ||||
| import parse  # type: ignore | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| def max_change_in_happiness(happiness: dict[str, dict[str, int]]) -> int: | ||||
|     guests = list(happiness) | ||||
| @@ -17,25 +18,23 @@ def max_change_in_happiness(happiness: dict[str, dict[str, int]]) -> int: | ||||
|     ) | ||||
|  | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| happiness: dict[str, dict[str, int]] = defaultdict(dict) | ||||
| for line in lines: | ||||
|     u1, gain_or_loose, hap, u2 = cast( | ||||
|         tuple[str, Literal["gain", "lose"], int, str], | ||||
|         parse.parse(  # type: ignore | ||||
|             "{} would {} {:d} happiness units by sitting next to {}.", line | ||||
|         ), | ||||
|     ) | ||||
|     happiness[u1][u2] = hap if gain_or_loose == "gain" else -hap | ||||
|         happiness: dict[str, dict[str, int]] = defaultdict(dict) | ||||
|         for line in lines: | ||||
|             u1, gain_or_loose, hap, u2 = cast( | ||||
|                 tuple[str, Literal["gain", "lose"], int, str], | ||||
|                 parse.parse(  # type: ignore | ||||
|                     "{} would {} {:d} happiness units by sitting next to {}.", line | ||||
|                 ), | ||||
|             ) | ||||
|             happiness[u1][u2] = hap if gain_or_loose == "gain" else -hap | ||||
|  | ||||
|         yield max_change_in_happiness(happiness) | ||||
|         for guest in list(happiness): | ||||
|             happiness["me"][guest] = 0 | ||||
|             happiness[guest]["me"] = 0 | ||||
|  | ||||
| answer_1 = max_change_in_happiness(happiness) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| for guest in list(happiness): | ||||
|     happiness["me"][guest] = 0 | ||||
|     happiness[guest]["me"] = 0 | ||||
|  | ||||
| answer_2 = max_change_in_happiness(happiness) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield max_change_in_happiness(happiness) | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import sys | ||||
| from dataclasses import dataclass | ||||
| from typing import Literal, cast | ||||
| from typing import Any, Iterator, Literal, cast | ||||
|  | ||||
| import parse  # type: ignore | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| @dataclass(frozen=True) | ||||
| class Reindeer: | ||||
| @@ -13,50 +14,50 @@ class Reindeer: | ||||
|     rest_time: int | ||||
|  | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| reindeers: list[Reindeer] = [] | ||||
| for line in lines: | ||||
|     reindeer, speed, speed_time, rest_time = cast( | ||||
|         tuple[str, int, int, int], | ||||
|         parse.parse(  # type: ignore | ||||
|             "{} can fly {:d} km/s for {:d} seconds, " | ||||
|             "but then must rest for {:d} seconds.", | ||||
|             line, | ||||
|         ), | ||||
|     ) | ||||
|     reindeers.append( | ||||
|         Reindeer(name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time) | ||||
|     ) | ||||
|         reindeers: list[Reindeer] = [] | ||||
|         for line in lines: | ||||
|             reindeer, speed, speed_time, rest_time = cast( | ||||
|                 tuple[str, int, int, int], | ||||
|                 parse.parse(  # type: ignore | ||||
|                     "{} can fly {:d} km/s for {:d} seconds, " | ||||
|                     "but then must rest for {:d} seconds.", | ||||
|                     line, | ||||
|                 ), | ||||
|             ) | ||||
|             reindeers.append( | ||||
|                 Reindeer( | ||||
|                     name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time | ||||
|                 ) | ||||
|             ) | ||||
|  | ||||
| target = 1000 if len(reindeers) <= 2 else 2503 | ||||
|         target = 1000 if len(reindeers) <= 2 else 2503 | ||||
|  | ||||
| states: dict[Reindeer, tuple[Literal["resting", "flying"], int]] = { | ||||
|     reindeer: ("resting", 0) for reindeer in reindeers | ||||
| } | ||||
| distances: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers} | ||||
| points: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers} | ||||
|         states: dict[Reindeer, tuple[Literal["resting", "flying"], int]] = { | ||||
|             reindeer: ("resting", 0) for reindeer in reindeers | ||||
|         } | ||||
|         distances: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers} | ||||
|         points: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers} | ||||
|  | ||||
| for time in range(target): | ||||
|     for reindeer in reindeers: | ||||
|         if states[reindeer][0] == "flying": | ||||
|             distances[reindeer] += reindeer.speed | ||||
|         for time in self.progress.wrap(range(target)): | ||||
|             for reindeer in reindeers: | ||||
|                 if states[reindeer][0] == "flying": | ||||
|                     distances[reindeer] += reindeer.speed | ||||
|  | ||||
|     top_distance = max(distances.values()) | ||||
|     for reindeer in reindeers: | ||||
|         if distances[reindeer] == top_distance: | ||||
|             points[reindeer] += 1 | ||||
|             top_distance = max(distances.values()) | ||||
|             for reindeer in reindeers: | ||||
|                 if distances[reindeer] == top_distance: | ||||
|                     points[reindeer] += 1 | ||||
|  | ||||
|     for reindeer in reindeers: | ||||
|         if states[reindeer][1] == time: | ||||
|             if states[reindeer][0] == "resting": | ||||
|                 states[reindeer] = ("flying", time + reindeer.fly_time) | ||||
|             else: | ||||
|                 states[reindeer] = ("resting", time + reindeer.rest_time) | ||||
|             for reindeer in reindeers: | ||||
|                 if states[reindeer][1] == time: | ||||
|                     if states[reindeer][0] == "resting": | ||||
|                         states[reindeer] = ("flying", time + reindeer.fly_time) | ||||
|                     else: | ||||
|                         states[reindeer] = ("resting", time + reindeer.rest_time) | ||||
|  | ||||
|  | ||||
| answer_1 = max(distances.values()) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| answer_2 = max(points.values()) - 1 | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield max(distances.values()) | ||||
|         yield max(points.values()) - 1 | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| import math | ||||
| import sys | ||||
| from typing import Sequence, cast | ||||
| from typing import Any, Iterator, Sequence, cast | ||||
|  | ||||
| import parse  # type: ignore | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| def score(ingredients: list[list[int]], teaspoons: Sequence[int]) -> int: | ||||
|     return math.prod( | ||||
| @@ -18,40 +19,38 @@ def score(ingredients: list[list[int]], teaspoons: Sequence[int]) -> int: | ||||
|     ) | ||||
|  | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| ingredients: list[list[int]] = [] | ||||
| for line in lines: | ||||
|     _, *scores = cast( | ||||
|         tuple[str, int, int, int, int, int], | ||||
|         parse.parse(  # type: ignore | ||||
|             "{}: capacity {:d}, durability {:d}, flavor {:d}, " | ||||
|             "texture {:d}, calories {:d}", | ||||
|             line, | ||||
|         ), | ||||
|     ) | ||||
|     ingredients.append(scores) | ||||
|  | ||||
| total_teaspoons = 100 | ||||
| calories: list[int] = [] | ||||
| scores: list[int] = [] | ||||
|  | ||||
| for a in range(total_teaspoons + 1): | ||||
|     for b in range(total_teaspoons + 1 - a): | ||||
|         for c in range(total_teaspoons + 1 - a - b): | ||||
|             teaspoons = (a, b, c, total_teaspoons - a - b - c) | ||||
|  | ||||
|             scores.append(score(ingredients, teaspoons)) | ||||
|             calories.append( | ||||
|                 sum( | ||||
|                     ingredient[-1] * teaspoon | ||||
|                     for ingredient, teaspoon in zip(ingredients, teaspoons) | ||||
|                 ) | ||||
|         ingredients: list[list[int]] = [] | ||||
|         for line in lines: | ||||
|             _, *scores = cast( | ||||
|                 tuple[str, int, int, int, int, int], | ||||
|                 parse.parse(  # type: ignore | ||||
|                     "{}: capacity {:d}, durability {:d}, flavor {:d}, " | ||||
|                     "texture {:d}, calories {:d}", | ||||
|                     line, | ||||
|                 ), | ||||
|             ) | ||||
|             ingredients.append(scores) | ||||
|  | ||||
|         total_teaspoons = 100 | ||||
|         calories: list[int] = [] | ||||
|         scores: list[int] = [] | ||||
|  | ||||
| answer_1 = max(scores) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|         for a in range(total_teaspoons + 1): | ||||
|             for b in range(total_teaspoons + 1 - a): | ||||
|                 for c in range(total_teaspoons + 1 - a - b): | ||||
|                     teaspoons = (a, b, c, total_teaspoons - a - b - c) | ||||
|  | ||||
| answer_2 = max(score for score, calory in zip(scores, calories) if calory == 500) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|                     scores.append(score(ingredients, teaspoons)) | ||||
|                     calories.append( | ||||
|                         sum( | ||||
|                             ingredient[-1] * teaspoon | ||||
|                             for ingredient, teaspoon in zip(ingredients, teaspoons) | ||||
|                         ) | ||||
|                     ) | ||||
|  | ||||
|         yield max(scores) | ||||
|         yield max(score for score, calory in zip(scores, calories) if calory == 500) | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| import operator as op | ||||
| import re | ||||
| import sys | ||||
| from collections import defaultdict | ||||
| from typing import Callable | ||||
| from typing import Any, Callable, Iterator | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| MFCSAM: dict[str, int] = { | ||||
|     "children": 3, | ||||
| @@ -17,18 +18,10 @@ MFCSAM: dict[str, int] = { | ||||
|     "perfumes": 1, | ||||
| } | ||||
|  | ||||
| lines = sys.stdin.readlines() | ||||
|  | ||||
| aunts: list[dict[str, int]] = [ | ||||
|     { | ||||
|         match[1]: int(match[2]) | ||||
|         for match in re.findall(R"((?P<compound>[^:, ]+): (?P<quantity>\d+))", line) | ||||
|     } | ||||
|     for line in lines | ||||
| ] | ||||
|  | ||||
|  | ||||
| def match(operators: dict[str, Callable[[int, int], bool]]) -> int: | ||||
| def match( | ||||
|     aunts: list[dict[str, int]], operators: dict[str, Callable[[int, int], bool]] | ||||
| ) -> int: | ||||
|     return next( | ||||
|         i | ||||
|         for i, aunt in enumerate(aunts, start=1) | ||||
| @@ -36,16 +29,29 @@ def match(operators: dict[str, Callable[[int, int], bool]]) -> int: | ||||
|     ) | ||||
|  | ||||
|  | ||||
| answer_1 = match(defaultdict(lambda: op.eq)) | ||||
| print(f"answer 1 is {answer_1}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| answer_2 = match( | ||||
|     defaultdict( | ||||
|         lambda: op.eq, | ||||
|         trees=op.gt, | ||||
|         cats=op.gt, | ||||
|         pomeranians=op.lt, | ||||
|         goldfish=op.lt, | ||||
|     ) | ||||
| ) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         aunts: list[dict[str, int]] = [ | ||||
|             { | ||||
|                 match[1]: int(match[2]) | ||||
|                 for match in re.findall( | ||||
|                     R"((?P<compound>[^:, ]+): (?P<quantity>\d+))", line | ||||
|                 ) | ||||
|             } | ||||
|             for line in lines | ||||
|         ] | ||||
|  | ||||
|         yield match(aunts, defaultdict(lambda: op.eq)) | ||||
|  | ||||
|         yield match( | ||||
|             aunts, | ||||
|             defaultdict( | ||||
|                 lambda: op.eq, | ||||
|                 trees=op.gt, | ||||
|                 cats=op.gt, | ||||
|                 pomeranians=op.lt, | ||||
|                 goldfish=op.lt, | ||||
|             ), | ||||
|         ) | ||||
|   | ||||
| @@ -1,5 +1,6 @@ | ||||
| import sys | ||||
| from typing import Iterator | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int, ...]]: | ||||
| @@ -16,15 +17,18 @@ def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int, | ||||
|             yield (containers[i],) + combination | ||||
|  | ||||
|  | ||||
| containers = [int(c) for c in sys.stdin.read().split()] | ||||
| total = 25 if len(containers) <= 5 else 150 | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         containers = [int(c) for c in input.split()] | ||||
|         total = 25 if len(containers) <= 5 else 150 | ||||
|  | ||||
| combinations = [combination for combination in iter_combinations(total, containers)] | ||||
|         combinations = [ | ||||
|             combination for combination in iter_combinations(total, containers) | ||||
|         ] | ||||
|  | ||||
| answer_1 = len(combinations) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|         yield len(combinations) | ||||
|  | ||||
| min_containers = min(len(combination) for combination in combinations) | ||||
|  | ||||
| answer_2 = sum(1 for combination in combinations if len(combination) == min_containers) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         min_containers = min(len(combination) for combination in combinations) | ||||
|         yield sum( | ||||
|             1 for combination in combinations if len(combination) == min_containers | ||||
|         ) | ||||
|   | ||||
| @@ -1,66 +1,66 @@ | ||||
| import itertools | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| import numpy as np | ||||
| from numpy.typing import NDArray | ||||
|  | ||||
| grid0 = np.array([[c == "#" for c in line] for line in sys.stdin.read().splitlines()]) | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| # add an always off circle around | ||||
| grid0 = np.concatenate( | ||||
|     [ | ||||
|         np.zeros((grid0.shape[0] + 2, 1), dtype=bool), | ||||
|         np.concatenate( | ||||
|  | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         grid0 = np.array([[c == "#" for c in line] for line in input.splitlines()]) | ||||
|  | ||||
|         # add an always off circle around | ||||
|         grid0 = np.concatenate( | ||||
|             [ | ||||
|                 np.zeros((1, grid0.shape[1]), dtype=bool), | ||||
|                 grid0, | ||||
|                 np.zeros((1, grid0.shape[1]), dtype=bool), | ||||
|             ] | ||||
|         ), | ||||
|         np.zeros((grid0.shape[0] + 2, 1), dtype=bool), | ||||
|     ], | ||||
|     axis=1, | ||||
| ) | ||||
|                 np.zeros((grid0.shape[0] + 2, 1), dtype=bool), | ||||
|                 np.concatenate( | ||||
|                     [ | ||||
|                         np.zeros((1, grid0.shape[1]), dtype=bool), | ||||
|                         grid0, | ||||
|                         np.zeros((1, grid0.shape[1]), dtype=bool), | ||||
|                     ] | ||||
|                 ), | ||||
|                 np.zeros((grid0.shape[0] + 2, 1), dtype=bool), | ||||
|             ], | ||||
|             axis=1, | ||||
|         ) | ||||
|  | ||||
| moves = list(itertools.product([-1, 0, 1], repeat=2)) | ||||
| moves.remove((0, 0)) | ||||
|         moves = list(itertools.product([-1, 0, 1], repeat=2)) | ||||
|         moves.remove((0, 0)) | ||||
|  | ||||
| jjs, iis = np.meshgrid( | ||||
|     np.arange(1, grid0.shape[0] - 1, dtype=int), | ||||
|     np.arange(1, grid0.shape[1] - 1, dtype=int), | ||||
| ) | ||||
| iis, jjs = iis.flatten(), jjs.flatten() | ||||
|         jjs, iis = np.meshgrid( | ||||
|             np.arange(1, grid0.shape[0] - 1, dtype=int), | ||||
|             np.arange(1, grid0.shape[1] - 1, dtype=int), | ||||
|         ) | ||||
|         iis, jjs = iis.flatten(), jjs.flatten() | ||||
|  | ||||
| ins = iis[:, None] + np.array(moves)[:, 0] | ||||
| jns = jjs[:, None] + np.array(moves)[:, 1] | ||||
|         ins = iis[:, None] + np.array(moves)[:, 0] | ||||
|         jns = jjs[:, None] + np.array(moves)[:, 1] | ||||
|  | ||||
|         def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]: | ||||
|             neighbors_on = grid[ins, jns].sum(axis=1) | ||||
|             cells_on = grid[iis, jjs] | ||||
|  | ||||
| def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]: | ||||
|     neighbors_on = grid[ins, jns].sum(axis=1) | ||||
|     cells_on = grid[iis, jjs] | ||||
|             grid = np.zeros_like(grid) | ||||
|             grid[iis, jjs] = (neighbors_on == 3) | (cells_on & (neighbors_on == 2)) | ||||
|  | ||||
|     grid = np.zeros_like(grid) | ||||
|     grid[iis, jjs] = (neighbors_on == 3) | (cells_on & (neighbors_on == 2)) | ||||
|             return grid | ||||
|  | ||||
|     return grid | ||||
|         grid = grid0 | ||||
|         n_steps = 4 if len(grid) < 10 else 100 | ||||
|         for _ in range(n_steps): | ||||
|             grid = game_of_life(grid) | ||||
|  | ||||
|         yield grid.sum() | ||||
|  | ||||
| grid = grid0 | ||||
| n_steps = 4 if len(grid) < 10 else 100 | ||||
| for _ in range(n_steps): | ||||
|     grid = game_of_life(grid) | ||||
|         n_steps = 5 if len(grid) < 10 else 100 | ||||
|         grid = grid0 | ||||
|         for _ in range(n_steps): | ||||
|             grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True | ||||
|             grid = game_of_life(grid) | ||||
|  | ||||
| answer_1 = grid.sum() | ||||
| print(f"answer 1 is {answer_1}") | ||||
|         grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True | ||||
|  | ||||
|  | ||||
| n_steps = 5 if len(grid) < 10 else 100 | ||||
| grid = grid0 | ||||
| for _ in range(n_steps): | ||||
|     grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True | ||||
|     grid = game_of_life(grid) | ||||
|  | ||||
| grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True | ||||
|  | ||||
| answer_2 = sum(cell for line in grid for cell in line) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield sum(cell for line in grid for cell in line) | ||||
|   | ||||
| @@ -1,56 +1,58 @@ | ||||
| import sys | ||||
| from collections import defaultdict | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| replacements_s, molecule = sys.stdin.read().split("\n\n") | ||||
|  | ||||
| REPLACEMENTS: dict[str, list[str]] = defaultdict(list) | ||||
| for replacement_s in replacements_s.splitlines(): | ||||
|     p = replacement_s.split(" => ") | ||||
|     REPLACEMENTS[p[0]].append(p[1]) | ||||
| molecule = molecule.strip() | ||||
|  | ||||
| generated = [ | ||||
|     molecule[:i] + replacement + molecule[i + len(symbol) :] | ||||
|     for symbol, replacements in REPLACEMENTS.items() | ||||
|     for replacement in replacements | ||||
|     for i in range(len(molecule)) | ||||
|     if molecule[i:].startswith(symbol) | ||||
| ] | ||||
|  | ||||
| answer_1 = len(set(generated)) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| inversion: dict[str, str] = { | ||||
|     replacement: symbol | ||||
|     for symbol, replacements in REPLACEMENTS.items() | ||||
|     for replacement in replacements | ||||
| } | ||||
|  | ||||
| # there is actually only one way to create the molecule, and we can greedily replace | ||||
| # tokens with their replacements, e.g., if H => OH then we can replace OH by H directly | ||||
| # without thinking | ||||
|  | ||||
| count = 0 | ||||
| while molecule != "e": | ||||
|     i = 0 | ||||
|     m2 = "" | ||||
|     while i < len(molecule): | ||||
|         found = False | ||||
|         for replacement in inversion: | ||||
|             if molecule[i:].startswith(replacement): | ||||
|                 m2 += inversion[replacement] | ||||
|                 i += len(replacement) | ||||
|                 count += 1 | ||||
|                 found = True | ||||
|                 break | ||||
|  | ||||
|         if not found: | ||||
|             m2 += molecule[i] | ||||
|             i += 1 | ||||
|  | ||||
|     # print(m2) | ||||
|     molecule = m2 | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| answer_2 = count | ||||
| print(f"answer 2 is {count}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         replacements_s, molecule = input.split("\n\n") | ||||
|  | ||||
|         REPLACEMENTS: dict[str, list[str]] = defaultdict(list) | ||||
|         for replacement_s in replacements_s.splitlines(): | ||||
|             p = replacement_s.split(" => ") | ||||
|             REPLACEMENTS[p[0]].append(p[1]) | ||||
|         molecule = molecule.strip() | ||||
|  | ||||
|         generated = [ | ||||
|             molecule[:i] + replacement + molecule[i + len(symbol) :] | ||||
|             for symbol, replacements in REPLACEMENTS.items() | ||||
|             for replacement in replacements | ||||
|             for i in range(len(molecule)) | ||||
|             if molecule[i:].startswith(symbol) | ||||
|         ] | ||||
|  | ||||
|         yield len(set(generated)) | ||||
|  | ||||
|         inversion: dict[str, str] = { | ||||
|             replacement: symbol | ||||
|             for symbol, replacements in REPLACEMENTS.items() | ||||
|             for replacement in replacements | ||||
|         } | ||||
|  | ||||
|         # there is actually only one way to create the molecule, and we can greedily replace | ||||
|         # tokens with their replacements, e.g., if H => OH then we can replace OH by H directly | ||||
|         # without thinking | ||||
|  | ||||
|         count = 0 | ||||
|         while molecule != "e": | ||||
|             i = 0 | ||||
|             m2 = "" | ||||
|             while i < len(molecule): | ||||
|                 found = False | ||||
|                 for replacement in inversion: | ||||
|                     if molecule[i:].startswith(replacement): | ||||
|                         m2 += inversion[replacement] | ||||
|                         i += len(replacement) | ||||
|                         count += 1 | ||||
|                         found = True | ||||
|                         break | ||||
|  | ||||
|                 if not found: | ||||
|                     m2 += molecule[i] | ||||
|                     i += 1 | ||||
|  | ||||
|             # print(m2) | ||||
|             molecule = m2 | ||||
|  | ||||
|         yield count | ||||
|   | ||||
| @@ -1,20 +1,24 @@ | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| import numpy as np | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| length, width, height = np.array( | ||||
|     [[int(c) for c in line.split("x")] for line in lines] | ||||
| ).T | ||||
|  | ||||
| lw, wh, hl = (length * width, width * height, height * length) | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         length, width, height = np.array( | ||||
|             [[int(c) for c in line.split("x")] for line in input.splitlines()] | ||||
|         ).T | ||||
|  | ||||
| answer_1 = np.sum(2 * (lw + wh + hl) + np.min(np.stack([lw, wh, hl]), axis=0)) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|         lw, wh, hl = (length * width, width * height, height * length) | ||||
|  | ||||
| answer_2 = np.sum( | ||||
|     length * width * height | ||||
|     + 2 * np.min(np.stack([length + width, length + height, height + width]), axis=0) | ||||
| ) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield np.sum(2 * (lw + wh + hl) + np.min(np.stack([lw, wh, hl]), axis=0)) | ||||
|  | ||||
|         yield np.sum( | ||||
|             length * width * height | ||||
|             + 2 | ||||
|             * np.min( | ||||
|                 np.stack([length + width, length + height, height + width]), axis=0 | ||||
|             ) | ||||
|         ) | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import itertools | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| target = int(sys.stdin.read()) | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| def presents(n: int, elf: int, max: int = target) -> int: | ||||
| def presents(n: int, elf: int, max: int) -> int: | ||||
|     count = 0 | ||||
|     k = 1 | ||||
|     while k * k < n: | ||||
| @@ -21,8 +21,9 @@ def presents(n: int, elf: int, max: int = target) -> int: | ||||
|     return count | ||||
|  | ||||
|  | ||||
| answer_1 = next(n for n in itertools.count(1) if presents(n, 10) >= target) | ||||
| print(f"answer 1 is {answer_1}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         target = int(input) | ||||
|  | ||||
| answer_2 = next(n for n in itertools.count(1) if presents(n, 11, 50) >= target) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield next(n for n in itertools.count(1) if presents(n, 10, target) >= target) | ||||
|         yield next(n for n in itertools.count(1) if presents(n, 11, 50) >= target) | ||||
|   | ||||
| @@ -1,7 +1,8 @@ | ||||
| import itertools | ||||
| import sys | ||||
| from math import ceil | ||||
| from typing import TypeAlias | ||||
| from typing import Any, Iterator, TypeAlias | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| Modifier: TypeAlias = tuple[str, int, int, int] | ||||
|  | ||||
| @@ -33,34 +34,31 @@ RINGS: list[Modifier] = [ | ||||
| ] | ||||
|  | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| player_hp = 100 | ||||
|         player_hp = 100 | ||||
|  | ||||
| boss_attack = int(lines[1].split(":")[1].strip()) | ||||
| boss_armor = int(lines[2].split(":")[1].strip()) | ||||
| boss_hp = int(lines[0].split(":")[1].strip()) | ||||
|         boss_attack = int(lines[1].split(":")[1].strip()) | ||||
|         boss_armor = int(lines[2].split(":")[1].strip()) | ||||
|         boss_hp = int(lines[0].split(":")[1].strip()) | ||||
|  | ||||
|         min_cost, max_cost = 1_000_000, 0 | ||||
|         for equipments in itertools.product(WEAPONS, ARMORS, RINGS, RINGS): | ||||
|             if equipments[-1][0] != "" and equipments[-2] == equipments[-1]: | ||||
|                 continue | ||||
|  | ||||
| min_cost, max_cost = 1_000_000, 0 | ||||
| for equipments in itertools.product(WEAPONS, ARMORS, RINGS, RINGS): | ||||
|     if equipments[-1][0] != "" and equipments[-2] == equipments[-1]: | ||||
|         continue | ||||
|             cost, player_attack, player_armor = ( | ||||
|                 sum(equipment[1:][k] for equipment in equipments) for k in range(3) | ||||
|             ) | ||||
|  | ||||
|     cost, player_attack, player_armor = ( | ||||
|         sum(equipment[1:][k] for equipment in equipments) for k in range(3) | ||||
|     ) | ||||
|             if ceil(boss_hp / max(1, player_attack - boss_armor)) <= ceil( | ||||
|                 player_hp / max(1, boss_attack - player_armor) | ||||
|             ): | ||||
|                 min_cost = min(cost, min_cost) | ||||
|             else: | ||||
|                 max_cost = max(cost, max_cost) | ||||
|  | ||||
|     if ceil(boss_hp / max(1, player_attack - boss_armor)) <= ceil( | ||||
|         player_hp / max(1, boss_attack - player_armor) | ||||
|     ): | ||||
|         min_cost = min(cost, min_cost) | ||||
|     else: | ||||
|         max_cost = max(cost, max_cost) | ||||
|  | ||||
|  | ||||
| answer_1 = min_cost | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| answer_2 = max_cost | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield min_cost | ||||
|         yield max_cost | ||||
|   | ||||
| @@ -1,8 +1,9 @@ | ||||
| from __future__ import annotations | ||||
|  | ||||
| import heapq | ||||
| import sys | ||||
| from typing import Literal, TypeAlias, cast | ||||
| from typing import Any, Iterator, Literal, TypeAlias, cast | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| PlayerType: TypeAlias = Literal["player", "boss"] | ||||
| SpellType: TypeAlias = Literal["magic missile", "drain", "shield", "poison", "recharge"] | ||||
| @@ -62,17 +63,6 @@ def play( | ||||
|             continue | ||||
|  | ||||
|         visited.add((player, player_hp, player_mana, player_armor, boss_hp, buffs)) | ||||
|  | ||||
|         if hard_mode and player == "player": | ||||
|             player_hp = max(0, player_hp - 1) | ||||
|  | ||||
|         if player_hp == 0: | ||||
|             continue | ||||
|  | ||||
|         if boss_hp == 0: | ||||
|             winning_node = spells | ||||
|             continue | ||||
|  | ||||
|         new_buffs: list[tuple[BuffType, int]] = [] | ||||
|         for buff, length in buffs: | ||||
|             length = length - 1 | ||||
| @@ -88,6 +78,16 @@ def play( | ||||
|             if length > 0: | ||||
|                 new_buffs.append((buff, length)) | ||||
|  | ||||
|         if hard_mode and player == "player": | ||||
|             player_hp = player_hp - 1 | ||||
|  | ||||
|         if player_hp <= 0: | ||||
|             continue | ||||
|  | ||||
|         if boss_hp <= 0: | ||||
|             winning_node = spells | ||||
|             continue | ||||
|  | ||||
|         buffs = tuple(new_buffs) | ||||
|  | ||||
|         if player == "boss": | ||||
| @@ -155,23 +155,28 @@ def play( | ||||
|     return winning_node | ||||
|  | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| player_hp = 50 | ||||
| player_mana = 500 | ||||
| player_armor = 0 | ||||
|         player_hp = 50 | ||||
|         player_mana = 500 | ||||
|         player_armor = 0 | ||||
|  | ||||
| boss_hp = int(lines[0].split(":")[1].strip()) | ||||
| boss_attack = int(lines[1].split(":")[1].strip()) | ||||
|         boss_hp = int(lines[0].split(":")[1].strip()) | ||||
|         boss_attack = int(lines[1].split(":")[1].strip()) | ||||
|  | ||||
| answer_1 = sum( | ||||
|     c | ||||
|     for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, False) | ||||
| ) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|         yield sum( | ||||
|             c | ||||
|             for _, c in play( | ||||
|                 player_hp, player_mana, player_armor, boss_hp, boss_attack, False | ||||
|             ) | ||||
|         ) | ||||
|  | ||||
| # 1242 (not working) | ||||
| answer_2 = sum( | ||||
|     c for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, True) | ||||
| ) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         # 1242 (not working) | ||||
|         yield sum( | ||||
|             c | ||||
|             for _, c in play( | ||||
|                 player_hp, player_mana, player_armor, boss_hp, boss_attack, True | ||||
|             ) | ||||
|         ) | ||||
|   | ||||
| @@ -1,7 +1,7 @@ | ||||
| import sys | ||||
| from collections import defaultdict | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| line = sys.stdin.read().strip() | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| def process(directions: str) -> dict[tuple[int, int], int]: | ||||
| @@ -27,8 +27,7 @@ def process(directions: str) -> dict[tuple[int, int], int]: | ||||
|     return counts | ||||
|  | ||||
|  | ||||
| answer_1 = len(process(line)) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| answer_2 = len(process(line[::2]) | process(line[1::2])) | ||||
| print(f"answer 2 is {answer_2}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         yield len(process(input)) | ||||
|         yield len(process(input[::2]) | process(input[1::2])) | ||||
|   | ||||
| @@ -1,16 +1,20 @@ | ||||
| import hashlib | ||||
| import itertools | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| line = sys.stdin.read().strip() | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| it = iter(itertools.count(1)) | ||||
| answer_1 = next( | ||||
|     i for i in it if hashlib.md5(f"{line}{i}".encode()).hexdigest().startswith("00000") | ||||
| ) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| answer_2 = next( | ||||
|     i for i in it if hashlib.md5(f"{line}{i}".encode()).hexdigest().startswith("000000") | ||||
| ) | ||||
| print(f"answer 2 is {answer_2}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         it = iter(itertools.count(1)) | ||||
|         yield next( | ||||
|             i | ||||
|             for i in it | ||||
|             if hashlib.md5(f"{input}{i}".encode()).hexdigest().startswith("00000") | ||||
|         ) | ||||
|         yield next( | ||||
|             i | ||||
|             for i in it | ||||
|             if hashlib.md5(f"{input}{i}".encode()).hexdigest().startswith("000000") | ||||
|         ) | ||||
|   | ||||
| @@ -1,4 +1,6 @@ | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| VOWELS = "aeiou" | ||||
| FORBIDDEN = {"ab", "cd", "pq", "xy"} | ||||
| @@ -27,10 +29,8 @@ def is_nice_2(s: str) -> bool: | ||||
|     return True | ||||
|  | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
|  | ||||
| answer_1 = sum(map(is_nice_1, lines)) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|  | ||||
| answer_2 = sum(map(is_nice_2, lines)) | ||||
| print(f"answer 2 is {answer_2}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|         yield sum(map(is_nice_1, lines)) | ||||
|         yield sum(map(is_nice_2, lines)) | ||||
|   | ||||
| @@ -1,33 +1,32 @@ | ||||
| import sys | ||||
| from typing import Literal, cast | ||||
| from typing import Any, Iterator, Literal, cast | ||||
|  | ||||
| import numpy as np | ||||
| import parse  # type: ignore | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| lights_1 = np.zeros((1000, 1000), dtype=bool) | ||||
| lights_2 = np.zeros((1000, 1000), dtype=int) | ||||
| for line in lines: | ||||
|     action, sx, sy, ex, ey = cast( | ||||
|         tuple[Literal["turn on", "turn off", "toggle"], int, int, int, int], | ||||
|         parse.parse("{} {:d},{:d} through {:d},{:d}", line),  # type: ignore | ||||
|     ) | ||||
|     ex, ey = ex + 1, ey + 1 | ||||
|  | ||||
|     match action: | ||||
|         case "turn on": | ||||
|             lights_1[sx:ex, sy:ey] = True | ||||
|             lights_2[sx:ex, sy:ey] += 1 | ||||
|         case "turn off": | ||||
|             lights_1[sx:ex, sy:ey] = False | ||||
|             lights_2[sx:ex, sy:ey] = np.maximum(lights_2[sx:ex, sy:ey] - 1, 0) | ||||
|         case "toggle": | ||||
|             lights_1[sx:ex, sy:ey] = ~lights_1[sx:ex, sy:ey] | ||||
|             lights_2[sx:ex, sy:ey] += 2 | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lights_1 = np.zeros((1000, 1000), dtype=bool) | ||||
|         lights_2 = np.zeros((1000, 1000), dtype=int) | ||||
|         for line in input.splitlines(): | ||||
|             action, sx, sy, ex, ey = cast( | ||||
|                 tuple[Literal["turn on", "turn off", "toggle"], int, int, int, int], | ||||
|                 parse.parse("{} {:d},{:d} through {:d},{:d}", line),  # type: ignore | ||||
|             ) | ||||
|             ex, ey = ex + 1, ey + 1 | ||||
|  | ||||
| answer_1 = lights_1.sum() | ||||
| print(f"answer 1 is {answer_1}") | ||||
|             match action: | ||||
|                 case "turn on": | ||||
|                     lights_1[sx:ex, sy:ey] = True | ||||
|                     lights_2[sx:ex, sy:ey] += 1 | ||||
|                 case "turn off": | ||||
|                     lights_1[sx:ex, sy:ey] = False | ||||
|                     lights_2[sx:ex, sy:ey] = np.maximum(lights_2[sx:ex, sy:ey] - 1, 0) | ||||
|                 case "toggle": | ||||
|                     lights_1[sx:ex, sy:ey] = ~lights_1[sx:ex, sy:ey] | ||||
|                     lights_2[sx:ex, sy:ey] += 2 | ||||
|  | ||||
| answer_2 = lights_2.sum() | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield lights_1.sum() | ||||
|         yield lights_2.sum() | ||||
|   | ||||
| @@ -1,11 +1,7 @@ | ||||
| import logging | ||||
| import operator | ||||
| import os | ||||
| import sys | ||||
| from typing import Callable | ||||
| from typing import Any, Callable, Iterator | ||||
|  | ||||
| VERBOSE = os.getenv("AOC_VERBOSE") == "True" | ||||
| logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING) | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| OPERATORS = { | ||||
|     "AND": operator.and_, | ||||
| @@ -36,48 +32,6 @@ def value_of(key: str) -> tuple[str, Callable[[dict[str, int]], int]]: | ||||
|         return key, lambda values: values[key] | ||||
|  | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
|  | ||||
| signals: Signals = {} | ||||
| values: dict[str, int] = {"": 0} | ||||
|  | ||||
| for line in lines: | ||||
|     command, signal = line.split(" -> ") | ||||
|  | ||||
|     if command.startswith("NOT"): | ||||
|         name = command.split(" ")[1] | ||||
|         signals[signal] = ( | ||||
|             (name, ""), | ||||
|             (lambda values, _n=name: values[_n], lambda _v: 0), | ||||
|             lambda a, _b: ~a, | ||||
|         ) | ||||
|  | ||||
|     elif not any(command.find(name) >= 0 for name in OPERATORS): | ||||
|         try: | ||||
|             values[signal] = int(command) | ||||
|         except ValueError: | ||||
|             signals[signal] = ( | ||||
|                 (command, ""), | ||||
|                 (lambda values, _c=command: values[_c], lambda _v: 0), | ||||
|                 lambda a, _b: a, | ||||
|             ) | ||||
|  | ||||
|     else: | ||||
|         op: Callable[[int, int], int] = zero_op | ||||
|         lhs_s, rhs_s = "", "" | ||||
|  | ||||
|         for name in OPERATORS: | ||||
|             if command.find(name) >= 0: | ||||
|                 op = OPERATORS[name] | ||||
|                 lhs_s, rhs_s = command.split(f" {name} ") | ||||
|                 break | ||||
|  | ||||
|         lhs_s, lhs_fn = value_of(lhs_s) | ||||
|         rhs_s, rhs_fn = value_of(rhs_s) | ||||
|  | ||||
|         signals[signal] = ((lhs_s, rhs_s), (lhs_fn, rhs_fn), op) | ||||
|  | ||||
|  | ||||
| def process( | ||||
|     signals: Signals, | ||||
|     values: dict[str, int], | ||||
| @@ -91,11 +45,52 @@ def process( | ||||
|     return values | ||||
|  | ||||
|  | ||||
| values_1 = process(signals.copy(), values.copy()) | ||||
| logging.info("\n" + "\n".join(f"{k}: {values_1[k]}" for k in sorted(values_1))) | ||||
| answer_1 = values_1["a"] | ||||
| print(f"answer 1 is {answer_1}") | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any] | None: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| values_2 = process(signals.copy(), values | {"b": values_1["a"]}) | ||||
| answer_2 = values_2["a"] | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         signals: Signals = {} | ||||
|         values: dict[str, int] = {"": 0} | ||||
|  | ||||
|         for line in lines: | ||||
|             command, signal = line.split(" -> ") | ||||
|  | ||||
|             if command.startswith("NOT"): | ||||
|                 name = command.split(" ")[1] | ||||
|                 signals[signal] = ( | ||||
|                     (name, ""), | ||||
|                     (lambda values, _n=name: values[_n], lambda _v: 0), | ||||
|                     lambda a, _b: ~a, | ||||
|                 ) | ||||
|  | ||||
|             elif not any(command.find(name) >= 0 for name in OPERATORS): | ||||
|                 try: | ||||
|                     values[signal] = int(command) | ||||
|                 except ValueError: | ||||
|                     signals[signal] = ( | ||||
|                         (command, ""), | ||||
|                         (lambda values, _c=command: values[_c], lambda _v: 0), | ||||
|                         lambda a, _b: a, | ||||
|                     ) | ||||
|  | ||||
|             else: | ||||
|                 op: Callable[[int, int], int] = zero_op | ||||
|                 lhs_s, rhs_s = "", "" | ||||
|  | ||||
|                 for name in OPERATORS: | ||||
|                     if command.find(name) >= 0: | ||||
|                         op = OPERATORS[name] | ||||
|                         lhs_s, rhs_s = command.split(f" {name} ") | ||||
|                         break | ||||
|  | ||||
|                 lhs_s, lhs_fn = value_of(lhs_s) | ||||
|                 rhs_s, rhs_fn = value_of(rhs_s) | ||||
|  | ||||
|                 signals[signal] = ((lhs_s, rhs_s), (lhs_fn, rhs_fn), op) | ||||
|  | ||||
|         values_1 = process(signals.copy(), values.copy()) | ||||
|         for k in sorted(values_1): | ||||
|             self.logger.info(f"{k}: {values_1[k]}") | ||||
|         yield values_1["a"] | ||||
|  | ||||
|         yield process(signals.copy(), values | {"b": values_1["a"]})["a"] | ||||
|   | ||||
| @@ -1,35 +1,32 @@ | ||||
| import logging | ||||
| import os | ||||
| import sys | ||||
| from typing import Any, Iterator | ||||
|  | ||||
| VERBOSE = os.getenv("AOC_VERBOSE") == "True" | ||||
| logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING) | ||||
| from ..base import BaseSolver | ||||
|  | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| answer_1 = sum( | ||||
|     # left and right quotes (not in memory) | ||||
|     2 | ||||
|     # each \\ adds one character in the literals (compared to memory) | ||||
|     + line.count(R"\\") | ||||
|     # each \" adds one character in the literals (compared to memory) | ||||
|     + line[1:-1].count(R"\"") | ||||
|     # each \xFF adds 3 characters in the literals (compared to memory), but we must not | ||||
|     # count A\\x (A != \), but we must count A\\\x (A != \) - in practice we should also | ||||
|     # avoid \\\\x, etc., but this does not occur in the examples and the actual input | ||||
|     + 3 * (line.count(R"\x") - line.count(R"\\x") + line.count(R"\\\x")) | ||||
|     for line in lines | ||||
| ) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|         yield sum( | ||||
|             # left and right quotes (not in memory) | ||||
|             2 | ||||
|             # each \\ adds one character in the literals (compared to memory) | ||||
|             + line.count(R"\\") | ||||
|             # each \" adds one character in the literals (compared to memory) | ||||
|             + line[1:-1].count(R"\"") | ||||
|             # each \xFF adds 3 characters in the literals (compared to memory), but we must not | ||||
|             # count A\\x (A != \), but we must count A\\\x (A != \) - in practice we should also | ||||
|             # avoid \\\\x, etc., but this does not occur in the examples and the actual input | ||||
|             + 3 * (line.count(R"\x") - line.count(R"\\x") + line.count(R"\\\x")) | ||||
|             for line in lines | ||||
|         ) | ||||
|  | ||||
| answer_2 = sum( | ||||
|     # needs to wrap in quotes (2 characters) | ||||
|     2 | ||||
|     # needs to escape every \ with an extra \ | ||||
|     + line.count("\\") | ||||
|     # needs to escape every " with an extra \ (including the first and last ones) | ||||
|     + line.count('"') | ||||
|     for line in lines | ||||
| ) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         yield sum( | ||||
|             # needs to wrap in quotes (2 characters) | ||||
|             2 | ||||
|             # needs to escape every \ with an extra \ | ||||
|             + line.count("\\") | ||||
|             # needs to escape every " with an extra \ (including the first and last ones) | ||||
|             + line.count('"') | ||||
|             for line in lines | ||||
|         ) | ||||
|   | ||||
| @@ -1,27 +1,28 @@ | ||||
| import itertools | ||||
| import sys | ||||
| from collections import defaultdict | ||||
| from typing import cast | ||||
| from typing import Any, Iterator, cast | ||||
|  | ||||
| import parse  # type: ignore | ||||
|  | ||||
| lines = sys.stdin.read().splitlines() | ||||
| from ..base import BaseSolver | ||||
|  | ||||
| distances: dict[str, dict[str, int]] = defaultdict(dict) | ||||
| for line in lines: | ||||
|     origin, destination, length = cast( | ||||
|         tuple[str, str, int], | ||||
|         parse.parse("{} to {} = {:d}", line),  # type: ignore | ||||
|     ) | ||||
|     distances[origin][destination] = distances[destination][origin] = length | ||||
|  | ||||
| distance_of_routes = { | ||||
|     route: sum(distances[o][d] for o, d in zip(route[:-1], route[1:])) | ||||
|     for route in map(tuple, itertools.permutations(distances)) | ||||
| } | ||||
| class Solver(BaseSolver): | ||||
|     def solve(self, input: str) -> Iterator[Any]: | ||||
|         lines = input.splitlines() | ||||
|  | ||||
| answer_1 = min(distance_of_routes.values()) | ||||
| print(f"answer 1 is {answer_1}") | ||||
|         distances: dict[str, dict[str, int]] = defaultdict(dict) | ||||
|         for line in lines: | ||||
|             origin, destination, length = cast( | ||||
|                 tuple[str, str, int], | ||||
|                 parse.parse("{} to {} = {:d}", line),  # type: ignore | ||||
|             ) | ||||
|             distances[origin][destination] = distances[destination][origin] = length | ||||
|  | ||||
| answer_2 = max(distance_of_routes.values()) | ||||
| print(f"answer 2 is {answer_2}") | ||||
|         distance_of_routes = { | ||||
|             route: sum(distances[o][d] for o, d in zip(route[:-1], route[1:])) | ||||
|             for route in map(tuple, itertools.permutations(distances)) | ||||
|         } | ||||
|  | ||||
|         yield min(distance_of_routes.values()) | ||||
|         yield max(distance_of_routes.values()) | ||||
|   | ||||
		Reference in New Issue
	
	Block a user