day19 finished
This commit is contained in:
parent
b3b434882b
commit
c2ebf9dc84
5 changed files with 213 additions and 0 deletions
0
advent/days/day19/__init__.py
Normal file
0
advent/days/day19/__init__.py
Normal file
2
advent/days/day19/data/example01.txt
Normal file
2
advent/days/day19/data/example01.txt
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 2 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 2 ore and 7 obsidian.
|
||||||
|
Blueprint 2: Each ore robot costs 2 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 8 clay. Each geode robot costs 3 ore and 12 obsidian.
|
||||||
30
advent/days/day19/data/input.txt
Normal file
30
advent/days/day19/data/input.txt
Normal file
|
|
@ -0,0 +1,30 @@
|
||||||
|
Blueprint 1: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 17 clay. Each geode robot costs 4 ore and 20 obsidian.
|
||||||
|
Blueprint 2: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 17 clay. Each geode robot costs 3 ore and 8 obsidian.
|
||||||
|
Blueprint 3: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 7 clay. Each geode robot costs 4 ore and 13 obsidian.
|
||||||
|
Blueprint 4: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 10 clay. Each geode robot costs 3 ore and 14 obsidian.
|
||||||
|
Blueprint 5: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 17 clay. Each geode robot costs 3 ore and 16 obsidian.
|
||||||
|
Blueprint 6: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 16 clay. Each geode robot costs 2 ore and 15 obsidian.
|
||||||
|
Blueprint 7: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 15 clay. Each geode robot costs 2 ore and 15 obsidian.
|
||||||
|
Blueprint 8: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 19 clay. Each geode robot costs 2 ore and 18 obsidian.
|
||||||
|
Blueprint 9: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 7 clay. Each geode robot costs 2 ore and 19 obsidian.
|
||||||
|
Blueprint 10: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 6 clay. Each geode robot costs 3 ore and 16 obsidian.
|
||||||
|
Blueprint 11: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 8 clay. Each geode robot costs 3 ore and 19 obsidian.
|
||||||
|
Blueprint 12: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 2 ore and 12 obsidian.
|
||||||
|
Blueprint 13: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 4 ore and 17 obsidian.
|
||||||
|
Blueprint 14: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 2 ore and 14 obsidian.
|
||||||
|
Blueprint 15: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 10 clay. Each geode robot costs 2 ore and 11 obsidian.
|
||||||
|
Blueprint 16: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 13 clay. Each geode robot costs 3 ore and 11 obsidian.
|
||||||
|
Blueprint 17: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 19 clay. Each geode robot costs 3 ore and 10 obsidian.
|
||||||
|
Blueprint 18: Each ore robot costs 2 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 2 ore and 17 obsidian.
|
||||||
|
Blueprint 19: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 11 clay. Each geode robot costs 4 ore and 12 obsidian.
|
||||||
|
Blueprint 20: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 7 clay. Each geode robot costs 3 ore and 10 obsidian.
|
||||||
|
Blueprint 21: Each ore robot costs 3 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 13 clay. Each geode robot costs 3 ore and 7 obsidian.
|
||||||
|
Blueprint 22: Each ore robot costs 2 ore. Each clay robot costs 2 ore. Each obsidian robot costs 2 ore and 15 clay. Each geode robot costs 2 ore and 7 obsidian.
|
||||||
|
Blueprint 23: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 3 ore and 18 obsidian.
|
||||||
|
Blueprint 24: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 18 clay. Each geode robot costs 4 ore and 8 obsidian.
|
||||||
|
Blueprint 25: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 3 ore and 14 clay. Each geode robot costs 4 ore and 15 obsidian.
|
||||||
|
Blueprint 26: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 2 ore and 20 clay. Each geode robot costs 3 ore and 9 obsidian.
|
||||||
|
Blueprint 27: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 4 ore and 5 clay. Each geode robot costs 3 ore and 7 obsidian.
|
||||||
|
Blueprint 28: Each ore robot costs 3 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 11 clay. Each geode robot costs 2 ore and 8 obsidian.
|
||||||
|
Blueprint 29: Each ore robot costs 4 ore. Each clay robot costs 4 ore. Each obsidian robot costs 2 ore and 12 clay. Each geode robot costs 3 ore and 15 obsidian.
|
||||||
|
Blueprint 30: Each ore robot costs 4 ore. Each clay robot costs 3 ore. Each obsidian robot costs 3 ore and 10 clay. Each geode robot costs 3 ore and 10 obsidian.
|
||||||
140
advent/days/day19/solution.py
Normal file
140
advent/days/day19/solution.py
Normal file
|
|
@ -0,0 +1,140 @@
|
||||||
|
from __future__ import annotations
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from enum import IntEnum
|
||||||
|
from itertools import islice
|
||||||
|
from math import prod
|
||||||
|
from queue import PriorityQueue
|
||||||
|
|
||||||
|
from typing import Iterator, Self
|
||||||
|
from advent.parser.parser import P
|
||||||
|
|
||||||
|
day_num = 19
|
||||||
|
|
||||||
|
|
||||||
|
def part1(lines: Iterator[str]) -> int:
|
||||||
|
return sum(blueprint.number * blueprint.run(24)
|
||||||
|
for blueprint in (Blueprint.parse(line) for line in lines))
|
||||||
|
|
||||||
|
|
||||||
|
def part2(lines: Iterator[str]) -> int:
|
||||||
|
return prod(Blueprint.parse(line).run(32) for line in islice(lines, 3))
|
||||||
|
|
||||||
|
|
||||||
|
number_parser = P.second(P.string("Blueprint "), P.unsigned())
|
||||||
|
ore_parser = P.second(P.string(": Each ore robot costs "), P.unsigned())
|
||||||
|
clay_parser = P.second(P.string(" ore. Each clay robot costs "), P.unsigned())
|
||||||
|
tuple_parser = P.seq(P.unsigned(), P.second(P.string(" ore and "), P.unsigned()))
|
||||||
|
obsidian_parser = P.second(P.string(" ore. Each obsidian robot costs "), tuple_parser)
|
||||||
|
geode_parser = P.second(P.string(" clay. Each geode robot costs "), tuple_parser)
|
||||||
|
blueprint_parser = P.map5(number_parser, ore_parser, clay_parser, obsidian_parser, geode_parser,
|
||||||
|
lambda number, ore, clay, obsidian, geode:
|
||||||
|
Blueprint.create(number, ore, clay, obsidian, geode))
|
||||||
|
|
||||||
|
|
||||||
|
class Element(IntEnum):
|
||||||
|
Geode = 0
|
||||||
|
Obsidian = 1
|
||||||
|
Clay = 2
|
||||||
|
Ore = 3
|
||||||
|
|
||||||
|
|
||||||
|
Elements = tuple[int, int, int, int]
|
||||||
|
|
||||||
|
|
||||||
|
def gt(first: Elements, second: Elements) -> bool:
|
||||||
|
return all(f >= s for f, s in zip(first, second))
|
||||||
|
|
||||||
|
|
||||||
|
def add(first: Elements, second: Elements) -> Elements:
|
||||||
|
return tuple(v1 + v2 for v1, v2 in zip(first, second))
|
||||||
|
|
||||||
|
|
||||||
|
def sub(first: Elements, second: Elements) -> Elements:
|
||||||
|
return Elements(tuple(v1 - v2 for v1, v2 in zip(first, second)))
|
||||||
|
|
||||||
|
|
||||||
|
def inc_tuple(elements: Elements, pos: Element) -> Elements:
|
||||||
|
return tuple(v + 1 if num == pos else v for num, v in enumerate(elements))
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class Path:
|
||||||
|
time: int
|
||||||
|
material: Elements
|
||||||
|
robots: Elements
|
||||||
|
blueprint: Blueprint
|
||||||
|
path: list[Element | None]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def start(cls, blueprint: Blueprint) -> Path:
|
||||||
|
return Path(0, (0, 0, 0, 0), (0, 0, 0, 1), blueprint, [])
|
||||||
|
|
||||||
|
def _check(self, element: Element) -> Path | None:
|
||||||
|
if gt(self.material, self.blueprint.requirements[element]):
|
||||||
|
return Path(self.time + 1,
|
||||||
|
add(sub(self.material, self.blueprint.requirements[element]), self.robots),
|
||||||
|
inc_tuple(self.robots, element), self.blueprint, self.path + [element])
|
||||||
|
else:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def find_next(self) -> Iterator[Path]:
|
||||||
|
if (path := self._check(Element.Geode)) is not None:
|
||||||
|
yield path
|
||||||
|
if self.blueprint.max_requirement(Element.Obsidian) >= self.material[Element.Obsidian]:
|
||||||
|
if (path := self._check(Element.Obsidian)) is not None:
|
||||||
|
yield path
|
||||||
|
if self.blueprint.max_requirement(Element.Clay) >= self.material[Element.Clay]:
|
||||||
|
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:
|
||||||
|
return self.time < other.time
|
||||||
|
return self.material > other.material
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True)
|
||||||
|
class Blueprint:
|
||||||
|
number: int
|
||||||
|
requirements: tuple[Elements, Elements, Elements, Elements]
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def create(cls, number: int, ore: int, clay: int,
|
||||||
|
obsidian: tuple[int, int], geode: tuple[int, int]) -> Self:
|
||||||
|
requirements = (
|
||||||
|
(0, geode[1], 0, geode[0]),
|
||||||
|
(0, 0, obsidian[1], obsidian[0]),
|
||||||
|
(0, 0, 0, clay),
|
||||||
|
(0, 0, 0, ore),
|
||||||
|
)
|
||||||
|
return Blueprint(number, requirements)
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def parse(cls, line: str) -> Self:
|
||||||
|
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:
|
||||||
|
queue: PriorityQueue[Path] = PriorityQueue()
|
||||||
|
queue.put(Path.start(self))
|
||||||
|
seen: dict[tuple[Elements, int], Elements] = {}
|
||||||
|
while not queue.empty():
|
||||||
|
current = queue.get()
|
||||||
|
if ((current.robots, current.time) in seen
|
||||||
|
and gt(seen[(current.robots, current.time)], current.material)):
|
||||||
|
continue
|
||||||
|
seen[(current.robots, current.time)] = current.material
|
||||||
|
|
||||||
|
if current.time == rounds:
|
||||||
|
return current.material[Element.Geode]
|
||||||
|
for next in current.find_next():
|
||||||
|
queue.put(next)
|
||||||
|
|
||||||
|
raise Exception("No optimum found")
|
||||||
41
advent/days/day19/test_solution.py
Normal file
41
advent/days/day19/test_solution.py
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
from advent.common import input
|
||||||
|
|
||||||
|
from .solution import Blueprint, day_num, part1, part2
|
||||||
|
|
||||||
|
|
||||||
|
def test_part1():
|
||||||
|
lines = input.read_lines(day_num, 'example01.txt')
|
||||||
|
expected = 33
|
||||||
|
result = part1(lines)
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_part2():
|
||||||
|
lines = input.read_lines(day_num, 'example01.txt')
|
||||||
|
expected = None
|
||||||
|
result = part2(lines)
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_parse():
|
||||||
|
lines = input.read_lines(day_num, 'example01.txt')
|
||||||
|
blueprint = Blueprint.parse(next(lines))
|
||||||
|
expected = Blueprint(1, ((0, 0, 0, 4), (0, 0, 0, 2), (0, 0, 14, 3), (0, 7, 0, 2)))
|
||||||
|
assert blueprint == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint1():
|
||||||
|
lines = input.read_lines(day_num, 'example01.txt')
|
||||||
|
blueprint = Blueprint.parse(next(lines))
|
||||||
|
expected = 9
|
||||||
|
result = blueprint.run(24)
|
||||||
|
assert result == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_blueprint2():
|
||||||
|
lines = input.read_lines(day_num, 'example01.txt')
|
||||||
|
next(lines)
|
||||||
|
blueprint = Blueprint.parse(next(lines))
|
||||||
|
expected = 12
|
||||||
|
result = blueprint.run(24)
|
||||||
|
assert result == expected
|
||||||
Loading…
Add table
Add a link
Reference in a new issue