Start working on day 19.
This commit is contained in:
parent
3b4efce02f
commit
c1161f6c1f
416
2022/day19.py
Normal file
416
2022/day19.py
Normal file
@ -0,0 +1,416 @@
|
||||
# -*- encoding: utf-8 -*-
|
||||
|
||||
import heapq
|
||||
import math
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from typing import Literal, TypedDict
|
||||
|
||||
Reagent = Literal["ore", "clay", "obsidian", "geode"]
|
||||
REAGENTS: tuple[Reagent] = (
|
||||
"ore",
|
||||
"clay",
|
||||
"obsidian",
|
||||
"geode",
|
||||
)
|
||||
|
||||
IntOfReagent = dict[Reagent, int]
|
||||
|
||||
lines = sys.stdin.read().splitlines()
|
||||
|
||||
blueprints: list[dict[Reagent, IntOfReagent]] = [
|
||||
{
|
||||
"ore": {"ore": 4},
|
||||
"clay": {"ore": 2},
|
||||
"obsidian": {"ore": 3, "clay": 14},
|
||||
"geode": {"ore": 2, "obsidian": 7},
|
||||
},
|
||||
{
|
||||
"ore": {"ore": 2},
|
||||
"clay": {"ore": 3},
|
||||
"obsidian": {"ore": 3, "clay": 8},
|
||||
"geode": {"ore": 3, "obsidian": 12},
|
||||
},
|
||||
]
|
||||
|
||||
|
||||
class State:
|
||||
robots: IntOfReagent
|
||||
reagents: IntOfReagent
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
robots: IntOfReagent | None = None,
|
||||
reagents: IntOfReagent | None = None,
|
||||
):
|
||||
if robots is None:
|
||||
assert reagents is None
|
||||
self.reagents = {reagent: 0 for reagent in REAGENTS}
|
||||
self.robots = {reagent: 0 for reagent in REAGENTS}
|
||||
self.robots["ore"] = 1
|
||||
else:
|
||||
assert robots is not None and reagents is not None
|
||||
self.robots = robots
|
||||
self.reagents = reagents
|
||||
|
||||
def __eq__(self, other) -> bool:
|
||||
return (
|
||||
isinstance(other, State)
|
||||
and self.robots == other.robots
|
||||
and self.reagents == other.reagents
|
||||
)
|
||||
|
||||
def __lt__(self, other) -> bool:
|
||||
return isinstance(other, State) and tuple(
|
||||
(self.robots[r], self.reagents[r]) for r in REAGENTS
|
||||
) < tuple((other.robots[r], other.reagents[r]) for r in REAGENTS)
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(tuple((self.robots[r], self.reagents[r]) for r in REAGENTS))
|
||||
|
||||
def __str__(self) -> str:
|
||||
return "State({}, {})".format(
|
||||
"/".join(str(self.robots[k]) for k in REAGENTS),
|
||||
"/".join(str(self.reagents[k]) for k in REAGENTS),
|
||||
)
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return str(self)
|
||||
|
||||
|
||||
def dominates(lhs: State, rhs: State):
|
||||
return all(
|
||||
lhs.robots[r] >= rhs.robots[r] and lhs.reagents[r] >= rhs.reagents[r]
|
||||
for r in REAGENTS
|
||||
)
|
||||
|
||||
|
||||
MAX_TIME = 24
|
||||
blueprint = blueprints[0]
|
||||
|
||||
parents: dict[State, tuple[State | None, int]] = {State(): (None, 0)}
|
||||
queue = [(0, State())]
|
||||
visited: set[State] = set()
|
||||
at_time: dict[int, list[State]] = defaultdict(lambda: [])
|
||||
|
||||
while queue:
|
||||
time, state = heapq.heappop(queue)
|
||||
if state in visited:
|
||||
continue
|
||||
|
||||
visited.add(state)
|
||||
|
||||
# if any(dominates(state_3, state) for state_3 in at_time[time]):
|
||||
# continue
|
||||
|
||||
at_time[time].append(state)
|
||||
|
||||
if time > MAX_TIME:
|
||||
continue
|
||||
|
||||
if len(queue) % 500 == 0:
|
||||
print(len(queue), len(visited), time)
|
||||
|
||||
can_build_any: bool = False
|
||||
for reagent in REAGENTS:
|
||||
needed = blueprint[reagent]
|
||||
|
||||
if any(state.robots[r] == 0 for r in needed):
|
||||
continue
|
||||
|
||||
time_to_complete = max(
|
||||
max(
|
||||
math.ceil((needed[r] - state.reagents[r]) / state.robots[r])
|
||||
for r in needed
|
||||
),
|
||||
0,
|
||||
)
|
||||
|
||||
if time + time_to_complete + 1 > MAX_TIME:
|
||||
continue
|
||||
|
||||
wait = time_to_complete + 1
|
||||
|
||||
reagents = {
|
||||
r: state.reagents[r] + wait * state.robots[r] - needed.get(r, 0)
|
||||
for r in REAGENTS
|
||||
}
|
||||
|
||||
robots = state.robots.copy()
|
||||
robots[reagent] += 1
|
||||
|
||||
state_2 = State(reagents=reagents, robots=robots)
|
||||
|
||||
print(time + wait)
|
||||
if any(dominates(state_3, state_2) for state_3 in at_time[time + wait]):
|
||||
continue
|
||||
|
||||
if state_2 not in parents or parents[state_2][1] > time + wait:
|
||||
parents[state_2] = (state, time + wait)
|
||||
heapq.heappush(queue, (time + wait, state_2))
|
||||
can_build_any = True
|
||||
|
||||
if not can_build_any:
|
||||
state_2 = State(
|
||||
reagents={
|
||||
r: state.reagents[r] + state.robots[r] * (MAX_TIME - time)
|
||||
for r in REAGENTS
|
||||
},
|
||||
robots=state.robots,
|
||||
)
|
||||
|
||||
if state_2 not in parents or parents[state_2][1] > time + wait:
|
||||
parents[state_2] = (state, time + wait)
|
||||
heapq.heappush(queue, (time + wait, state_2))
|
||||
|
||||
print(len(visited))
|
||||
print(max(state.reagents["geode"] for state in visited))
|
||||
|
||||
exit()
|
||||
|
||||
while states:
|
||||
state = states.pop()
|
||||
processed.append(state)
|
||||
|
||||
if state.time > MAX_TIME:
|
||||
continue
|
||||
|
||||
if len(states) % 100 == 0:
|
||||
print(len(states), len(processed), min((s.time for s in states), default=1))
|
||||
|
||||
can_build_any: bool = False
|
||||
for reagent in REAGENTS:
|
||||
needed = blueprint[reagent]
|
||||
|
||||
if any(state.robots[r] == 0 for r in needed):
|
||||
continue
|
||||
|
||||
time_to_complete = max(
|
||||
max(
|
||||
math.ceil((needed[r] - state.reagents[r]) / state.robots[r])
|
||||
for r in needed
|
||||
),
|
||||
0,
|
||||
)
|
||||
|
||||
if state.time + time_to_complete + 1 > MAX_TIME:
|
||||
continue
|
||||
|
||||
wait = time_to_complete + 1
|
||||
|
||||
reagents = {
|
||||
r: state.reagents[r] + wait * state.robots[r] - needed.get(r, 0)
|
||||
for r in REAGENTS
|
||||
}
|
||||
|
||||
robots = state.robots.copy()
|
||||
robots[reagent] += 1
|
||||
|
||||
can_build_any = True
|
||||
state_2 = State(time=state.time + wait, reagents=reagents, robots=robots)
|
||||
# print(f"{state} -> {state_2}")
|
||||
states.add(state_2)
|
||||
|
||||
if not any(dominates(s2, state_2) for s2 in states):
|
||||
states.add(state)
|
||||
|
||||
# print(f"can build {reagent} in {time_to_complete}")
|
||||
|
||||
if not can_build_any:
|
||||
states.add(
|
||||
State(
|
||||
time=MAX_TIME + 1,
|
||||
reagents={
|
||||
r: state.reagents[r] + state.robots[r] * (MAX_TIME - state.time)
|
||||
for r in REAGENTS
|
||||
},
|
||||
robots=state.robots,
|
||||
)
|
||||
)
|
||||
|
||||
if len(states) % 1000 == 0:
|
||||
print("filtering")
|
||||
states = {
|
||||
s1
|
||||
for s1 in states
|
||||
if not any(dominates(s2, s1) for s2 in states if s2 is not s1)
|
||||
}
|
||||
|
||||
# if len(states) > 4:
|
||||
# break
|
||||
|
||||
# break
|
||||
|
||||
print(len(processed))
|
||||
print(max(state.reagents["geode"] for state in processed))
|
||||
|
||||
exit()
|
||||
|
||||
for t in range(1, 25):
|
||||
states = set()
|
||||
for state in state_after_t[t - 1]:
|
||||
robots_that_can_be_built = [
|
||||
robot
|
||||
for robot in REAGENTS
|
||||
if all(
|
||||
state.reagents[reagent] >= blueprint[robot].get(reagent, 0)
|
||||
for reagent in REAGENTS
|
||||
)
|
||||
]
|
||||
|
||||
new_states = set()
|
||||
|
||||
# new reagents
|
||||
reagents = {
|
||||
reagent: state.reagents[reagent] + state.robots[reagent]
|
||||
for reagent in REAGENTS
|
||||
}
|
||||
|
||||
# if we can build anything, there is no point in waiting
|
||||
if len(robots_that_can_be_built) != len(REAGENTS):
|
||||
new_states.add(State(robots=state.robots, reagents=reagents))
|
||||
|
||||
for robot in robots_that_can_be_built:
|
||||
robots = state.robots.copy()
|
||||
robots[robot] += 1
|
||||
reagents = {
|
||||
reagent: state.reagents[reagent]
|
||||
+ state.robots[reagent]
|
||||
- blueprint[robot].get(reagent, 0)
|
||||
for reagent in REAGENTS
|
||||
}
|
||||
new_states.add(State(robots=robots, reagents=reagents))
|
||||
|
||||
new_states = [
|
||||
s1
|
||||
for s1 in new_states
|
||||
if not any(s1 is not s2 and dominates(s2, s1) for s2 in new_states)
|
||||
]
|
||||
|
||||
states = {
|
||||
s1 for s1 in states if not any(dominates(s2, s1) for s2 in new_states)
|
||||
}
|
||||
states.update(new_states)
|
||||
|
||||
state_after_t[t] = states
|
||||
|
||||
exit()
|
||||
|
||||
|
||||
for t in range(1, 25):
|
||||
print(t, len(state_after_t[t - 1]))
|
||||
state_after_t[t] = set()
|
||||
|
||||
bests_for_robots: dict[tuple[int, ...], set[State]] = {}
|
||||
bests_for_reagents: dict[tuple[int, ...], set[State]] = {}
|
||||
|
||||
for state in state_after_t[t - 1]:
|
||||
robots_that_can_be_built = [
|
||||
robot
|
||||
for robot in REAGENTS
|
||||
if all(
|
||||
state.reagents[reagent] >= blueprint[robot].get(reagent, 0)
|
||||
for reagent in REAGENTS
|
||||
)
|
||||
]
|
||||
|
||||
# print(t, robots_that_can_be_built)
|
||||
new_states: set[State] = set()
|
||||
|
||||
# new reagents
|
||||
reagents = {
|
||||
reagent: state.reagents[reagent] + state.robots[reagent]
|
||||
for reagent in REAGENTS
|
||||
}
|
||||
|
||||
# if we can build anything, there is no point in waiting
|
||||
new_states.add(State(robots=state.robots, reagents=reagents, last=None))
|
||||
|
||||
for robot in robots_that_can_be_built:
|
||||
if robot == state.last:
|
||||
continue
|
||||
robots = state.robots.copy()
|
||||
robots[robot] += 1
|
||||
reagents = {
|
||||
reagent: state.reagents[reagent]
|
||||
+ state.robots[reagent]
|
||||
- blueprint[robot].get(reagent, 0)
|
||||
for reagent in REAGENTS
|
||||
}
|
||||
new_states.add(State(robots=robots, reagents=reagents, last=robot))
|
||||
|
||||
for s1 in new_states:
|
||||
r1 = tuple(s1.robots[r] for r in REAGENTS)
|
||||
if r1 not in bests_for_robots:
|
||||
bests_for_robots[r1] = {s1}
|
||||
else:
|
||||
is_dominated = False
|
||||
for s2 in bests_for_robots[r1]:
|
||||
if all(s2.reagents[r] >= s1.reagents[r] for r in REAGENTS):
|
||||
is_dominated = True
|
||||
break
|
||||
if not is_dominated:
|
||||
bests_for_robots[r1].add(s1)
|
||||
|
||||
r2 = tuple(s1.reagents[r] for r in REAGENTS)
|
||||
if r2 not in bests_for_reagents:
|
||||
bests_for_reagents[r2] = {s1}
|
||||
else:
|
||||
is_dominated = False
|
||||
for s2 in bests_for_reagents[r2]:
|
||||
if all(s2.robots[r] >= s1.robots[r] for r in REAGENTS):
|
||||
is_dominated = True
|
||||
break
|
||||
if not is_dominated:
|
||||
bests_for_reagents[r2].add(s1)
|
||||
|
||||
state_after_t[t] = set()
|
||||
for bests in bests_for_robots.values():
|
||||
dominated = [False for _ in range(len(bests))]
|
||||
for i_s1, s1 in enumerate(bests):
|
||||
if dominated[i_s1]:
|
||||
continue
|
||||
for i_s2, s2 in enumerate(bests):
|
||||
if s1 is s2 or dominated[i_s2]:
|
||||
continue
|
||||
if all(s1.reagents[r] >= s2.reagents[r] for r in REAGENTS):
|
||||
dominated[i_s2] = True
|
||||
state_after_t[t].update(
|
||||
s1 for i_s1, s1 in enumerate(bests) if not dominated[i_s1]
|
||||
)
|
||||
for bests in bests_for_reagents.values():
|
||||
dominated = [False for _ in range(len(bests))]
|
||||
for i_s1, s1 in enumerate(bests):
|
||||
if dominated[i_s1]:
|
||||
continue
|
||||
for i_s2, s2 in enumerate(bests):
|
||||
if s1 is s2 or dominated[i_s2]:
|
||||
continue
|
||||
if all(s1.robots[r] >= s2.robots[r] for r in REAGENTS):
|
||||
dominated[i_s2] = True
|
||||
state_after_t[t].update(
|
||||
s1 for i_s1, s1 in enumerate(bests) if not dominated[i_s1]
|
||||
)
|
||||
|
||||
# dominated = [False for _ in range(len(state_after_t[t]))]
|
||||
# print(t, "->", len(state_after_t[t]))
|
||||
# for i_s1, s1 in enumerate(state_after_t[t]):
|
||||
# if dominated[i_s1]:
|
||||
# continue
|
||||
# for i_s2, s2 in enumerate(state_after_t[t]):
|
||||
# if s1 is s2 or dominated[i_s2]:
|
||||
# continue
|
||||
# if all(s1.robots[r] >= s2.robots[r] for r in REAGENTS) and all(
|
||||
# s1.reagents[r] >= s2.reagents[r] for r in REAGENTS
|
||||
# ):
|
||||
# dominated[i_s2] = True
|
||||
|
||||
# state_after_t[t] = {
|
||||
# s1 for i_s1, s1 in enumerate(state_after_t[t]) if not dominated[i_s1]
|
||||
# }
|
||||
|
||||
# print(len(state_after_t[t]))
|
||||
# print(sum(dominated))
|
||||
# break
|
||||
|
||||
print(max(state.reagents["geode"] for state in state_after_t[24]))
|
2
2022/tests/day19.txt
Normal file
2
2022/tests/day19.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.
|
Loading…
Reference in New Issue
Block a user