day19 made parallel and much quicker

This commit is contained in:
Ruediger Ludwig 2023-01-19 07:54:29 +01:00
parent 43d510ee22
commit cee40d1003

View file

@ -3,21 +3,35 @@ from dataclasses import dataclass
from enum import IntEnum from enum import IntEnum
from itertools import islice from itertools import islice
from math import prod from math import prod
from multiprocessing import Pool
from queue import PriorityQueue from queue import PriorityQueue
from typing import Iterator, Self from typing import Iterable, Iterator, Self
from advent.parser.parser import P from advent.parser.parser import P
day_num = 19 day_num = 19
def part1(lines: Iterator[str]) -> int: def part1(lines: Iterator[str]) -> int:
return sum(blueprint.number * blueprint.run(24) return sum(bp.number * geodes for bp, geodes in Processor.pool_it(24, lines))
for blueprint in (Blueprint.parse(line) for line in lines))
def part2(lines: Iterator[str]) -> int: def part2(lines: Iterator[str]) -> int:
return prod(Blueprint.parse(line).run(32) for line in islice(lines, 3)) return prod(num for _, num in Processor.pool_it(32, islice(lines, 3)))
@dataclass(slots=True, frozen=True)
class Processor:
rounds: int
@classmethod
def pool_it(cls, rounds: int, lines: Iterator[str]) -> Iterable[tuple[Blueprint, int]]:
with Pool() as p:
return p.map(Processor(rounds), lines)
def __call__(self, line: str) -> tuple[Blueprint, int]:
blueprint = Blueprint.parse(line)
return blueprint, blueprint.run(self.rounds)
number_parser = P.second(P.string("Blueprint "), P.unsigned()) number_parser = P.second(P.string("Blueprint "), P.unsigned())
@ -53,7 +67,7 @@ def sub(first: Elements, second: Elements) -> Elements:
return Elements(tuple(v1 - v2 for v1, v2 in zip(first, second))) return Elements(tuple(v1 - v2 for v1, v2 in zip(first, second)))
def inc_tuple(elements: Elements, pos: Element) -> Elements: def inc_element(elements: Elements, pos: Element) -> Elements:
return tuple(v + 1 if num == pos else v for num, v in enumerate(elements)) return tuple(v + 1 if num == pos else v for num, v in enumerate(elements))
@ -70,25 +84,34 @@ class Path:
return Path(0, (0, 0, 0, 0), (0, 0, 0, 1), blueprint, []) return Path(0, (0, 0, 0, 0), (0, 0, 0, 1), blueprint, [])
def _check(self, element: Element) -> Path | None: def _check(self, element: Element) -> Path | None:
if element != Element.Geode:
max_needed = max(req[element] for req in self.blueprint.requirements)
if self.robots[element] >= max_needed:
return None
if gt(self.material, self.blueprint.requirements[element]): if gt(self.material, self.blueprint.requirements[element]):
return Path(self.time + 1, return Path(self.time + 1,
add(sub(self.material, self.blueprint.requirements[element]), self.robots), add(sub(self.material, self.blueprint.requirements[element]), self.robots),
inc_tuple(self.robots, element), self.blueprint, self.path + [element]) inc_element(self.robots, element), self.blueprint, self.path + [element])
else: else:
return None return None
def find_next(self) -> Iterator[Path]: def find_next(self) -> Iterator[Path]:
if (path := self._check(Element.Geode)) is not None: if (path := self._check(Element.Geode)) is not None:
yield path yield path
if self.blueprint.max_requirement(Element.Obsidian) >= self.material[Element.Obsidian]: if self.blueprint.max_requirement(Element.Obsidian) >= self.material[Element.Obsidian]:
if (path := self._check(Element.Obsidian)) is not None: if (path := self._check(Element.Obsidian)) is not None:
yield path yield path
if self.blueprint.max_requirement(Element.Clay) >= self.material[Element.Clay]: if self.blueprint.max_requirement(Element.Clay) >= self.material[Element.Clay]:
if (path := self._check(Element.Clay)) is not None: if (path := self._check(Element.Clay)) is not None:
yield path yield path
if self.blueprint.max_requirement(Element.Ore) >= self.material[Element.Ore]: if self.blueprint.max_requirement(Element.Ore) >= self.material[Element.Ore]:
if (path := self._check(Element.Ore)) is not None: if (path := self._check(Element.Ore)) is not None:
yield path yield path
yield Path(self.time + 1, add(self.material, self.robots), self.robots, self.blueprint, yield Path(self.time + 1, add(self.material, self.robots), self.robots, self.blueprint,
self.path + [None]) self.path + [None])
@ -127,8 +150,8 @@ class Blueprint:
seen: dict[tuple[Elements, int], Elements] = {} seen: dict[tuple[Elements, int], Elements] = {}
while not queue.empty(): while not queue.empty():
current = queue.get() current = queue.get()
if ((current.robots, current.time) in seen last_seen = seen.get((current.robots, current.time))
and gt(seen[(current.robots, current.time)], current.material)): if last_seen is not None and gt(last_seen, current.material):
continue continue
seen[(current.robots, current.time)] = current.material seen[(current.robots, current.time)] = current.material