day19 made parallel and much quicker
This commit is contained in:
parent
43d510ee22
commit
cee40d1003
1 changed files with 31 additions and 8 deletions
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue