day19 finished

This commit is contained in:
Ruediger Ludwig 2022-12-28 15:37:56 +01:00
parent b3b434882b
commit c2ebf9dc84
5 changed files with 213 additions and 0 deletions

View file

View 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.

View 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.

View 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")

View 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