speed up day 16
This commit is contained in:
parent
cf05072def
commit
923e967056
1 changed files with 86 additions and 58 deletions
|
|
@ -4,7 +4,7 @@ from dataclasses import dataclass, field
|
||||||
from itertools import product
|
from itertools import product
|
||||||
from queue import PriorityQueue
|
from queue import PriorityQueue
|
||||||
|
|
||||||
from typing import Iterator, Literal, Self
|
from typing import Iterator, Literal, NamedTuple, Self
|
||||||
from advent.parser.parser import P
|
from advent.parser.parser import P
|
||||||
|
|
||||||
day_num = 16
|
day_num = 16
|
||||||
|
|
@ -29,16 +29,14 @@ valve_parser = P.map3(
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True)
|
class RawValve(NamedTuple):
|
||||||
class RawValve:
|
|
||||||
name: str
|
name: str
|
||||||
flow_rate: int
|
flow_rate: int
|
||||||
following: list[str]
|
following: list[str]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse(cls, line: str) -> Self:
|
def parse(cls, line: str) -> Self:
|
||||||
result = valve_parser.parse(line).get()
|
return valve_parser.parse(line).get()
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, unsafe_hash=True)
|
@dataclass(slots=True, unsafe_hash=True)
|
||||||
|
|
@ -46,38 +44,48 @@ class Valve:
|
||||||
name: str
|
name: str
|
||||||
flow_rate: int
|
flow_rate: int
|
||||||
following: list[Valve] = field(hash=False, compare=False)
|
following: list[Valve] = field(hash=False, compare=False)
|
||||||
paths: dict[str, int] | None = field(default=None, hash=False, init=False, compare=False)
|
paths: dict[str, int] = field(default_factory=dict, hash=False, init=False, compare=False)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return f"{self.name}:{self.flow_rate}->{','.join(v.name for v in self.following)}"
|
return f"{self.name}:{self.flow_rate}->{','.join(v.name for v in self.following)}"
|
||||||
|
|
||||||
def travel_time(self, to: str) -> int:
|
def travel_time(self, to: str) -> int:
|
||||||
if self.paths is None:
|
if not self.paths:
|
||||||
self.create_paths()
|
self.create_paths()
|
||||||
return self.paths[to] # type: ignore
|
return self.paths[to]
|
||||||
|
|
||||||
def create_paths(self):
|
def create_paths(self):
|
||||||
paths: dict[str, int] = {}
|
paths: dict[str, tuple[int, bool]] = {}
|
||||||
to_check: list[tuple[Valve, int]] = [(self, 0)]
|
to_check: list[tuple[Valve, int]] = [(self, 0)]
|
||||||
while to_check:
|
while to_check:
|
||||||
current, steps = to_check[0]
|
current, steps = to_check[0]
|
||||||
to_check = to_check[1:]
|
to_check = to_check[1:]
|
||||||
|
|
||||||
paths[current.name] = steps
|
paths[current.name] = steps, (current.flow_rate > 0)
|
||||||
for next in current.following:
|
for next in current.following:
|
||||||
if paths.get(next.name, steps + 2) > steps + 1:
|
known_path, _ = paths.get(next.name, (steps + 2, False))
|
||||||
|
if known_path > steps + 1:
|
||||||
to_check.append((next, steps + 1))
|
to_check.append((next, steps + 1))
|
||||||
self.paths = paths
|
|
||||||
|
self.paths = {name: steps
|
||||||
|
for name, (steps, has_valve) in paths.items()
|
||||||
|
if has_valve is True}
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
class Actor(NamedTuple):
|
||||||
class Actor:
|
|
||||||
position: Valve
|
position: Valve
|
||||||
next_time: int
|
next_time: int
|
||||||
finished: bool
|
finished: bool
|
||||||
|
|
||||||
|
|
||||||
@dataclass(slots=True, frozen=True)
|
class SystemInfo(NamedTuple):
|
||||||
|
max_pressure: int
|
||||||
|
min_pressure: int
|
||||||
|
closed_vales: frozenset[Valve]
|
||||||
|
opening: frozenset[Valve]
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(slots=True, frozen=True, kw_only=True)
|
||||||
class SystemProgress(ABC):
|
class SystemProgress(ABC):
|
||||||
max_time: int
|
max_time: int
|
||||||
prev_time: int
|
prev_time: int
|
||||||
|
|
@ -85,7 +93,6 @@ class SystemProgress(ABC):
|
||||||
pressure: int
|
pressure: int
|
||||||
flow_rate: int
|
flow_rate: int
|
||||||
closed_valves: frozenset[Valve]
|
closed_valves: frozenset[Valve]
|
||||||
path: frozenset[tuple[str, int, bool]]
|
|
||||||
|
|
||||||
def one_actor(self, actor: Actor) -> Iterator[Actor]:
|
def one_actor(self, actor: Actor) -> Iterator[Actor]:
|
||||||
if actor.finished or actor.next_time != self.time:
|
if actor.finished or actor.next_time != self.time:
|
||||||
|
|
@ -103,16 +110,27 @@ class SystemProgress(ABC):
|
||||||
yield Actor(actor.position, self.max_time, True)
|
yield Actor(actor.position, self.max_time, True)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, max_time: int, pressure: int, flow_rate: int,
|
def create(cls, max_time: int,
|
||||||
closed_valves: frozenset[Valve], start: Valve,
|
closed_valves: frozenset[Valve], start: Valve,
|
||||||
num_actors: Literal[1] | Literal[2]) -> SystemProgress:
|
num_actors: Literal[1] | Literal[2]) -> SystemProgress:
|
||||||
match num_actors:
|
match num_actors:
|
||||||
case 1:
|
case 1:
|
||||||
return OneActorProgress(max_time, 0, 0, pressure, flow_rate, closed_valves,
|
return OneActorProgress(max_time=max_time,
|
||||||
frozenset(), Actor(start, 0, False))
|
prev_time=0,
|
||||||
|
time=0,
|
||||||
|
pressure=0,
|
||||||
|
flow_rate=0,
|
||||||
|
closed_valves=closed_valves,
|
||||||
|
actor=Actor(start, 0, False))
|
||||||
case 2:
|
case 2:
|
||||||
return TwoActorProgress(max_time, 0, 0, pressure, flow_rate, closed_valves,
|
return TwoActorProgress(max_time=max_time,
|
||||||
frozenset(), Actor(start, 0, False), Actor(start, 0, False))
|
prev_time=0,
|
||||||
|
time=0,
|
||||||
|
pressure=0,
|
||||||
|
flow_rate=0,
|
||||||
|
closed_valves=closed_valves,
|
||||||
|
actor1=Actor(start, 0, False),
|
||||||
|
actor2=Actor(start, 0, False))
|
||||||
case _:
|
case _:
|
||||||
assert False, "Unreachable"
|
assert False, "Unreachable"
|
||||||
|
|
||||||
|
|
@ -122,11 +140,11 @@ class SystemProgress(ABC):
|
||||||
return self.pressure > other.pressure
|
return self.pressure > other.pressure
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def max_possible_pressure(self) -> int:
|
def open_valves(self) -> Iterator[SystemProgress]:
|
||||||
...
|
...
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def open_valves(self) -> Iterator[SystemProgress]:
|
def get_info(self) -> SystemInfo:
|
||||||
...
|
...
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -134,6 +152,17 @@ class SystemProgress(ABC):
|
||||||
class OneActorProgress(SystemProgress):
|
class OneActorProgress(SystemProgress):
|
||||||
actor: Actor
|
actor: Actor
|
||||||
|
|
||||||
|
def get_info(self) -> SystemInfo:
|
||||||
|
return SystemInfo(
|
||||||
|
min_pressure=self.min_possible_pressure(),
|
||||||
|
max_pressure=self.max_possible_pressure(),
|
||||||
|
closed_vales=self.closed_valves,
|
||||||
|
opening=frozenset()
|
||||||
|
)
|
||||||
|
|
||||||
|
def min_possible_pressure(self) -> int:
|
||||||
|
return self.pressure + self.flow_rate * (self.max_time - self.time)
|
||||||
|
|
||||||
def max_possible_pressure(self) -> int:
|
def max_possible_pressure(self) -> int:
|
||||||
closed = sum(valve.flow_rate for valve in self.closed_valves)
|
closed = sum(valve.flow_rate for valve in self.closed_valves)
|
||||||
return self.pressure + (self.flow_rate + closed) * (self.max_time - self.time)
|
return self.pressure + (self.flow_rate + closed) * (self.max_time - self.time)
|
||||||
|
|
@ -154,7 +183,6 @@ class OneActorProgress(SystemProgress):
|
||||||
pressure=self.pressure + self.flow_rate * (actor.next_time - self.time),
|
pressure=self.pressure + self.flow_rate * (actor.next_time - self.time),
|
||||||
closed_valves=closed_valves,
|
closed_valves=closed_valves,
|
||||||
actor=actor,
|
actor=actor,
|
||||||
path=self.path | {(actor.position.name, actor.next_time, True)}
|
|
||||||
)
|
)
|
||||||
yield next
|
yield next
|
||||||
|
|
||||||
|
|
@ -164,27 +192,41 @@ class TwoActorProgress(SystemProgress):
|
||||||
actor1: Actor
|
actor1: Actor
|
||||||
actor2: Actor
|
actor2: Actor
|
||||||
|
|
||||||
|
def get_info(self) -> SystemInfo:
|
||||||
|
opening: set[Valve] = set()
|
||||||
|
if self.actor1.next_time != self.time and not self.actor1.finished:
|
||||||
|
opening.add(self.actor1.position)
|
||||||
|
if self.actor2.next_time != self.time and not self.actor2.finished:
|
||||||
|
opening.add(self.actor2.position)
|
||||||
|
|
||||||
|
return SystemInfo(
|
||||||
|
min_pressure=self.min_possible_pressure(),
|
||||||
|
max_pressure=self.max_possible_pressure(),
|
||||||
|
closed_vales=self.closed_valves,
|
||||||
|
opening=frozenset(opening)
|
||||||
|
)
|
||||||
|
|
||||||
|
def min_possible_pressure(self) -> int:
|
||||||
|
pressure = self.pressure + self.flow_rate * (self.max_time - self.time)
|
||||||
|
if self.actor1.next_time != self.time and not self.actor1.finished:
|
||||||
|
pressure += self.actor1.position.flow_rate * (self.max_time - self.actor1.next_time)
|
||||||
|
if self.actor2.next_time != self.time and not self.actor2.finished:
|
||||||
|
pressure += self.actor2.position.flow_rate * (self.max_time - self.actor2.next_time)
|
||||||
|
return pressure
|
||||||
|
|
||||||
def max_possible_pressure(self) -> int:
|
def max_possible_pressure(self) -> int:
|
||||||
closed = sum(valve.flow_rate for valve in self.closed_valves)
|
closed = sum(valve.flow_rate for valve in self.closed_valves)
|
||||||
|
pressure = self.pressure + (self.flow_rate + closed) * (self.max_time - self.time)
|
||||||
|
|
||||||
other = 0
|
|
||||||
if self.actor1.next_time != self.time and not self.actor1.finished:
|
if self.actor1.next_time != self.time and not self.actor1.finished:
|
||||||
other += self.actor1.position.flow_rate * (self.max_time - self.actor1.next_time)
|
pressure += self.actor1.position.flow_rate * (self.max_time - self.actor1.next_time)
|
||||||
if self.actor2.next_time != self.time and not self.actor2.finished:
|
if self.actor2.next_time != self.time and not self.actor2.finished:
|
||||||
other += self.actor2.position.flow_rate * (self.max_time - self.actor2.next_time)
|
pressure += self.actor2.position.flow_rate * (self.max_time - self.actor2.next_time)
|
||||||
|
return pressure
|
||||||
return self.pressure + (self.flow_rate + closed) * (self.max_time - self.time) + other
|
|
||||||
|
|
||||||
def open_valves(self) -> Iterator[SystemProgress]:
|
def open_valves(self) -> Iterator[SystemProgress]:
|
||||||
if self.actor1.next_time == self.time:
|
|
||||||
actor1_actions = self.one_actor(self.actor1)
|
actor1_actions = self.one_actor(self.actor1)
|
||||||
else:
|
|
||||||
actor1_actions = [self.actor1]
|
|
||||||
|
|
||||||
if self.actor2.next_time == self.time:
|
|
||||||
actor2_actions = self.one_actor(self.actor2)
|
actor2_actions = self.one_actor(self.actor2)
|
||||||
else:
|
|
||||||
actor2_actions = [self.actor2]
|
|
||||||
|
|
||||||
for actor1, actor2 in product(actor1_actions, actor2_actions):
|
for actor1, actor2 in product(actor1_actions, actor2_actions):
|
||||||
if not actor1.finished and not actor2.finished and actor1.position == actor2.position:
|
if not actor1.finished and not actor2.finished and actor1.position == actor2.position:
|
||||||
|
|
@ -194,25 +236,15 @@ class TwoActorProgress(SystemProgress):
|
||||||
flow_rate = self.flow_rate
|
flow_rate = self.flow_rate
|
||||||
next_time = min(actor1.next_time, actor2.next_time)
|
next_time = min(actor1.next_time, actor2.next_time)
|
||||||
|
|
||||||
path: set[tuple[str, int, bool]] = set(self.path)
|
|
||||||
|
|
||||||
if not actor1.finished:
|
if not actor1.finished:
|
||||||
closed_valves = closed_valves.difference({actor1.position})
|
closed_valves = closed_valves.difference({actor1.position})
|
||||||
if actor1.next_time == next_time:
|
if actor1.next_time == next_time:
|
||||||
flow_rate += actor1.position.flow_rate
|
flow_rate += actor1.position.flow_rate
|
||||||
path -= {(actor2.position.name, actor1.next_time, False)}
|
|
||||||
path.add((actor1.position.name, actor1.next_time, True))
|
|
||||||
else:
|
|
||||||
path.add((actor1.position.name, actor1.next_time, False))
|
|
||||||
|
|
||||||
if not actor2.finished:
|
if not actor2.finished:
|
||||||
closed_valves = closed_valves.difference({actor2.position})
|
closed_valves = closed_valves.difference({actor2.position})
|
||||||
if actor2.next_time == next_time:
|
if actor2.next_time == next_time:
|
||||||
flow_rate += actor2.position.flow_rate
|
flow_rate += actor2.position.flow_rate
|
||||||
path -= {(actor2.position.name, actor1.next_time, False)}
|
|
||||||
path.add((actor2.position.name, actor2.next_time, True))
|
|
||||||
else:
|
|
||||||
path.add((actor2.position.name, actor2.next_time, False))
|
|
||||||
|
|
||||||
next = TwoActorProgress(
|
next = TwoActorProgress(
|
||||||
max_time=self.max_time,
|
max_time=self.max_time,
|
||||||
|
|
@ -223,7 +255,6 @@ class TwoActorProgress(SystemProgress):
|
||||||
closed_valves=closed_valves,
|
closed_valves=closed_valves,
|
||||||
actor1=actor1,
|
actor1=actor1,
|
||||||
actor2=actor2,
|
actor2=actor2,
|
||||||
path=frozenset(path)
|
|
||||||
)
|
)
|
||||||
yield next
|
yield next
|
||||||
|
|
||||||
|
|
@ -250,24 +281,21 @@ class Network:
|
||||||
queue: PriorityQueue[SystemProgress] = PriorityQueue()
|
queue: PriorityQueue[SystemProgress] = PriorityQueue()
|
||||||
queue.put(SystemProgress.create(
|
queue.put(SystemProgress.create(
|
||||||
max_time=minutes,
|
max_time=minutes,
|
||||||
pressure=0,
|
|
||||||
flow_rate=0,
|
|
||||||
closed_valves=frozenset(closed_valves),
|
closed_valves=frozenset(closed_valves),
|
||||||
start=start,
|
start=start,
|
||||||
num_actors=number_actors
|
num_actors=number_actors
|
||||||
))
|
))
|
||||||
max_pressure = 0
|
min_pressure = 0
|
||||||
seen: set[frozenset[tuple[str, int, bool]]] = set()
|
known_systems: set[SystemInfo] = set()
|
||||||
while not queue.empty():
|
while not queue.empty():
|
||||||
current = queue.get()
|
current = queue.get()
|
||||||
if current.time == minutes:
|
if current.time == minutes:
|
||||||
return current.pressure
|
return current.pressure
|
||||||
if max_pressure > current.max_possible_pressure():
|
info = current.get_info()
|
||||||
|
if min_pressure > info.max_pressure or info in known_systems:
|
||||||
continue
|
continue
|
||||||
if current.path in seen:
|
known_systems.add(info)
|
||||||
continue
|
min_pressure = max(min_pressure, info.min_pressure)
|
||||||
seen.add(current.path)
|
|
||||||
max_pressure = max(max_pressure, current.pressure)
|
|
||||||
|
|
||||||
for next in current.open_valves():
|
for next in current.open_valves():
|
||||||
queue.put(next)
|
queue.put(next)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue