day19 - p2 takes about 1,5h

This commit is contained in:
Stefan Harmuth 2022-12-21 10:20:55 +01:00
parent e10d256638
commit 265f481472

View File

@ -1,7 +1,10 @@
from enum import Enum from enum import Enum
from heapq import heappush, heappop
from tools.aoc import AOCDay from tools.aoc import AOCDay
from typing import Any from typing import Any
from tools.int_seq import triangular
class Material(Enum): class Material(Enum):
ORE = 1 ORE = 1
@ -17,12 +20,6 @@ class Robot:
self.min_viable = min_viable self.min_viable = min_viable
class Blueprint:
def __init__(self, bp_id: int, robots: dict):
self.bp_id = bp_id
self.robots = robots
class Inventory: class Inventory:
def __init__(self): def __init__(self):
self.robots = { self.robots = {
@ -43,19 +40,10 @@ class Inventory:
for mat, amount in self.robots.items(): for mat, amount in self.robots.items():
self.materials[mat] += amount self.materials[mat] += amount
def queue_build_robot(self, robot: Robot) -> None: def build_robot(self, robot: Robot) -> None:
for cost_mat, cost_amount in robot.costs.items(): for cost_mat, cost_amount in robot.costs.items():
self.materials[cost_mat] -= cost_amount self.materials[cost_mat] -= cost_amount
self.__build_queue.append(robot)
def build_queued_robots(self) -> None:
for robot in self.__build_queue:
self.robots[robot.produces] += 1 self.robots[robot.produces] += 1
self.__build_queue = []
def build_robot(self, robot: Robot) -> None:
self.queue_build_robot(robot)
self.build_queued_robots()
def copy(self) -> 'Inventory': def copy(self) -> 'Inventory':
new_inv = Inventory() new_inv = Inventory()
@ -63,10 +51,27 @@ class Inventory:
new_inv.materials = self.materials.copy() new_inv.materials = self.materials.copy()
return new_inv return new_inv
def __lt__(self, other: 'Inventory') -> bool:
return self.materials[Material.GEODE] < other.materials[Material.GEODE]
class Blueprint:
def __init__(self, bp_id: int, robots: dict):
self.bp_id = bp_id
self.robots = robots
self.max_cost = {
Material.ORE: max(robot.costs[Material.ORE] for _, robot in self.robots.items()),
Material.CLAY: self.robots[Material.OBSIDIAN].costs[Material.CLAY],
Material.OBSIDIAN: self.robots[Material.GEODE].costs[Material.OBSIDIAN],
Material.GEODE: 1e9
}
def pass_minute(bp: Blueprint, inv: Inventory, ignore: list) -> (Inventory, list): def pass_minute(bp: Blueprint, inv: Inventory, ignore: list) -> (Inventory, list):
could_build = [] could_build = []
for mat in Material: for mat in Material:
if inv.robots[mat] >= bp.max_cost[mat]:
continue
for robot_mat, robot_cost in bp.robots[mat].costs.items(): for robot_mat, robot_cost in bp.robots[mat].costs.items():
if inv.materials[robot_mat] < robot_cost: if inv.materials[robot_mat] < robot_cost:
break break
@ -79,31 +84,32 @@ def pass_minute(bp: Blueprint, inv: Inventory, ignore: list) -> (Inventory, list
return inv, could_build return inv, could_build
def get_quality(bp: Blueprint, inv: Inventory, ignore: list, time_left: int = 24) -> int: def get_bfs_quality(bp: Blueprint, max_time: int = 24) -> int:
for m in range(time_left): q = []
inv, could_build = pass_minute(bp, inv, ignore) max_geode = 0
heappush(q, (max_time, Inventory(), [], 0))
while q:
time_left, inv, ignore, no_build = heappop(q)
inv, could_build = pass_minute(bp, inv, ignore)
time_left -= 1
if time_left == 0:
if inv.materials[Material.GEODE] > max_geode:
max_geode = inv.materials[Material.GEODE]
continue
if could_build and time_left - m > 1:
max_quality = 0
build_a_bot = False
for robot in could_build: for robot in could_build:
if robot.min_viable > time_left - m: if robot.min_viable > time_left:
continue continue
sub_inv = inv.copy() sub_inv = inv.copy()
sub_inv.build_robot(robot) sub_inv.build_robot(robot)
sub_quality = get_quality(bp, sub_inv, [], time_left - m - 1) heappush(q, (time_left, sub_inv, [], 0))
if sub_quality > max_quality:
max_quality = sub_quality
build_a_bot = True
if build_a_bot: if no_build < bp.max_cost[Material.ORE]:
sub_quality = get_quality(bp, inv, could_build, time_left - m - 1) heappush(q, (time_left, inv, could_build, no_build + 1))
if sub_quality > max_quality:
max_quality = sub_quality
return max_quality return max_geode
return inv.materials[Material.GEODE]
class Day(AOCDay): class Day(AOCDay):
@ -113,8 +119,8 @@ class Day(AOCDay):
(1616, "input19"), (1616, "input19"),
], ],
[ [
(56*62, "input19_test"), #(56*62, "input19_test"),
(None, "input19"), (8990, "input19"),
] ]
] ]
@ -143,18 +149,18 @@ class Day(AOCDay):
blueprints = self.get_blueprints() blueprints = self.get_blueprints()
score = 0 score = 0
for b in blueprints: for b in blueprints:
quality = get_quality(b, Inventory(), []) quality = get_bfs_quality(b)
print("BP", b.bp_id, "Q", quality)
score += quality * b.bp_id score += quality * b.bp_id
print("BP", b.bp_id, "Q", quality, "S", score)
return score return score
def part2(self) -> Any: def part2(self) -> Any:
blueprints = self.get_blueprints() blueprints = self.get_blueprints()
score = 1 score = 1
for b in blueprints[:3]: for b in blueprints[:3]:
quality = get_quality(b, Inventory(), [], 32) quality = get_bfs_quality(b, 32)
print("BP", b.bp_id, "Q", quality)
score *= quality score *= quality
print("BP", b.bp_id, "Q", quality, "S", score)
return score return score