day19 slightly improved
This commit is contained in:
parent
cee40d1003
commit
f59089d5eb
2 changed files with 27 additions and 36 deletions
|
|
@ -1,12 +1,13 @@
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
from dataclasses import dataclass
|
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 multiprocessing import Pool
|
||||||
from queue import PriorityQueue
|
from queue import PriorityQueue
|
||||||
|
|
||||||
from typing import Iterable, 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
|
||||||
|
|
@ -71,51 +72,43 @@ 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))
|
||||||
|
|
||||||
|
|
||||||
|
MAGIC_MATERIAL_SURPLUS = 1.2
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
@dataclass(slots=True)
|
||||||
class Path:
|
class State:
|
||||||
time: int
|
time: int
|
||||||
material: Elements
|
material: Elements
|
||||||
robots: Elements
|
robots: Elements
|
||||||
blueprint: Blueprint
|
blueprint: Blueprint
|
||||||
path: list[Element | None]
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def start(cls, blueprint: Blueprint) -> Path:
|
def start(cls, blueprint: Blueprint) -> State:
|
||||||
return Path(0, (0, 0, 0, 0), (0, 0, 0, 1), blueprint, [])
|
return State(0, (0, 0, 0, 0), (0, 0, 0, 1), blueprint)
|
||||||
|
|
||||||
def _check(self, element: Element) -> Path | None:
|
def get_valid_production(self, element: Element) -> State | None:
|
||||||
if element != Element.Geode:
|
if element != Element.Geode:
|
||||||
max_needed = max(req[element] for req in self.blueprint.requirements)
|
max_needed = self.blueprint.max_needed[element]
|
||||||
if self.robots[element] >= max_needed:
|
if self.robots[element] >= max_needed:
|
||||||
return None
|
return None
|
||||||
|
if self.material[element] > round(max_needed * MAGIC_MATERIAL_SURPLUS):
|
||||||
|
return None
|
||||||
|
|
||||||
if gt(self.material, self.blueprint.requirements[element]):
|
if gt(self.material, self.blueprint.requirements[element]):
|
||||||
return Path(self.time + 1,
|
return State(self.time + 1,
|
||||||
add(sub(self.material, self.blueprint.requirements[element]), self.robots),
|
add(sub(self.material, self.blueprint.requirements[element]), self.robots),
|
||||||
inc_element(self.robots, element), self.blueprint, self.path + [element])
|
inc_element(self.robots, element), self.blueprint)
|
||||||
else:
|
else:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def find_next(self) -> Iterator[Path]:
|
def find_next(self) -> Iterator[State]:
|
||||||
if (path := self._check(Element.Geode)) is not None:
|
for element in Element:
|
||||||
|
if (path := self.get_valid_production(element)) is not None:
|
||||||
yield path
|
yield path
|
||||||
|
|
||||||
if self.blueprint.max_requirement(Element.Obsidian) >= self.material[Element.Obsidian]:
|
yield State(self.time + 1, add(self.material, self.robots), self.robots, self.blueprint)
|
||||||
if (path := self._check(Element.Obsidian)) is not None:
|
|
||||||
yield path
|
|
||||||
|
|
||||||
if self.blueprint.max_requirement(Element.Clay) >= self.material[Element.Clay]:
|
def __lt__(self, other: State) -> bool:
|
||||||
if (path := self._check(Element.Clay)) is not None:
|
|
||||||
yield path
|
|
||||||
|
|
||||||
if self.blueprint.max_requirement(Element.Ore) >= self.material[Element.Ore]:
|
|
||||||
if (path := self._check(Element.Ore)) is not None:
|
|
||||||
yield path
|
|
||||||
|
|
||||||
yield Path(self.time + 1, add(self.material, self.robots), self.robots, self.blueprint,
|
|
||||||
self.path + [None])
|
|
||||||
|
|
||||||
def __lt__(self, other: Path) -> bool:
|
|
||||||
if self.time != other.time:
|
if self.time != other.time:
|
||||||
return self.time < other.time
|
return self.time < other.time
|
||||||
return self.material > other.material
|
return self.material > other.material
|
||||||
|
|
@ -125,6 +118,7 @@ class Path:
|
||||||
class Blueprint:
|
class Blueprint:
|
||||||
number: int
|
number: int
|
||||||
requirements: tuple[Elements, Elements, Elements, Elements]
|
requirements: tuple[Elements, Elements, Elements, Elements]
|
||||||
|
max_needed: Elements
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, number: int, ore: int, clay: int,
|
def create(cls, number: int, ore: int, clay: int,
|
||||||
|
|
@ -135,18 +129,16 @@ class Blueprint:
|
||||||
(0, 0, 0, clay),
|
(0, 0, 0, clay),
|
||||||
(0, 0, 0, ore),
|
(0, 0, 0, ore),
|
||||||
)
|
)
|
||||||
return Blueprint(number, requirements)
|
max_needed = (0, geode[1], obsidian[1], max(geode[0], obsidian[0], clay, ore))
|
||||||
|
return Blueprint(number, requirements, max_needed)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, line: str) -> Self:
|
def parse(cls, line: str) -> Self:
|
||||||
return blueprint_parser.parse(line).get()
|
return blueprint_parser.parse(line).get()
|
||||||
|
|
||||||
def max_requirement(self, element: Element) -> int:
|
|
||||||
return round(max(requirement[element] for requirement in self.requirements) * 1.2)
|
|
||||||
|
|
||||||
def run(self, rounds: int) -> int:
|
def run(self, rounds: int) -> int:
|
||||||
queue: PriorityQueue[Path] = PriorityQueue()
|
queue: PriorityQueue[State] = PriorityQueue()
|
||||||
queue.put(Path.start(self))
|
queue.put(State.start(self))
|
||||||
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()
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ from .solution import Blueprint, day_num, part1, part2
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip
|
|
||||||
def test_part1():
|
def test_part1():
|
||||||
lines = input.read_lines(day_num, 'example01.txt')
|
lines = input.read_lines(day_num, 'example01.txt')
|
||||||
expected = 33
|
expected = 33
|
||||||
|
|
@ -23,11 +22,11 @@ def test_part2():
|
||||||
def test_parse():
|
def test_parse():
|
||||||
lines = input.read_lines(day_num, 'example01.txt')
|
lines = input.read_lines(day_num, 'example01.txt')
|
||||||
blueprint = Blueprint.parse(next(lines))
|
blueprint = Blueprint.parse(next(lines))
|
||||||
expected = Blueprint(1, ((0, 7, 0, 2), (0, 0, 14, 3), (0, 0, 0, 2), (0, 0, 0, 4)))
|
expected = Blueprint(1, ((0, 7, 0, 2), (0, 0, 14, 3),
|
||||||
|
(0, 0, 0, 2), (0, 0, 0, 4)), (0, 7, 14, 4))
|
||||||
assert blueprint == expected
|
assert blueprint == expected
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.skip
|
|
||||||
def test_blueprint1():
|
def test_blueprint1():
|
||||||
lines = input.read_lines(day_num, 'example01.txt')
|
lines = input.read_lines(day_num, 'example01.txt')
|
||||||
blueprint = Blueprint.parse(next(lines))
|
blueprint = Blueprint.parse(next(lines))
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue