some final touches

This commit is contained in:
Ruediger Ludwig 2022-12-30 08:44:41 +01:00
parent 0843624f17
commit cf05072def
3 changed files with 77 additions and 101 deletions

View file

@ -1,5 +1,5 @@
from __future__ import annotations from __future__ import annotations
from abc import ABC, abstractmethod, abstractproperty from abc import ABC, abstractmethod
from dataclasses import dataclass, field from dataclasses import dataclass, field
from itertools import product from itertools import product
from queue import PriorityQueue from queue import PriorityQueue
@ -81,21 +81,21 @@ class Actor:
class SystemProgress(ABC): class SystemProgress(ABC):
max_time: int max_time: int
prev_time: int prev_time: int
next_time: int time: int
pressure: int pressure: int
flow_rate: int flow_rate: int
closed_valves: frozenset[Valve] closed_valves: frozenset[Valve]
path: str 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.next_time: if actor.finished or actor.next_time != self.time:
yield actor yield actor
elif not self.closed_valves: elif not self.closed_valves:
yield Actor(actor.position, self.max_time, True) yield Actor(actor.position, self.max_time, True)
else: else:
reached_any_target = False reached_any_target = False
for target in self.closed_valves: for target in self.closed_valves:
finished = self.next_time + actor.position.travel_time(target.name) + 1 finished = self.time + actor.position.travel_time(target.name) + 1
if finished < self.max_time: if finished < self.max_time:
reached_any_target = True reached_any_target = True
yield Actor(target, finished, False) yield Actor(target, finished, False)
@ -109,22 +109,19 @@ class SystemProgress(ABC):
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, 0, 0, pressure, flow_rate, closed_valves,
start.name, Actor(start, 0, False)) frozenset(), Actor(start, 0, False))
case 2: case 2:
return TwoActorProgress(max_time, 0, 0, pressure, flow_rate, closed_valves, return TwoActorProgress(max_time, 0, 0, pressure, flow_rate, closed_valves,
start.name, Actor(start, 0, False), Actor(start, 0, False)) frozenset(), Actor(start, 0, False), Actor(start, 0, False))
case _: case _:
assert False, "Unreachable" assert False, "Unreachable"
def __lt__(self, other: OneActorProgress) -> bool: def __lt__(self, other: OneActorProgress) -> bool:
mx = min(self.next_time, other.next_time) if self.time != other.time:
return self.pressure_at_time(mx) > other.pressure_at_time(mx) return self.time < other.time
return self.pressure > other.pressure
@abstractmethod @abstractmethod
def pressure_at_time(self, time: int) -> int:
...
@abstractproperty
def max_possible_pressure(self) -> int: def max_possible_pressure(self) -> int:
... ...
@ -137,38 +134,29 @@ class SystemProgress(ABC):
class OneActorProgress(SystemProgress): class OneActorProgress(SystemProgress):
actor: Actor actor: Actor
def pressure_at_time(self, time: int) -> int:
pressure = self.pressure + self.flow_rate * (time - self.prev_time)
if time > self.next_time:
pressure += self.actor.position.flow_rate * (time - self.actor.next_time + 1)
return pressure
@property
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 + self.actor.position.flow_rate return self.pressure + (self.flow_rate + closed) * (self.max_time - self.time)
+ closed) * (self.max_time - self.next_time)
def open_valves(self) -> Iterator[SystemProgress]: def open_valves(self) -> Iterator[SystemProgress]:
flow_rate = self.flow_rate + self.actor.position.flow_rate
pressure = self.pressure + self.flow_rate * (self.next_time - self.prev_time)
for actor in self.one_actor(self.actor): for actor in self.one_actor(self.actor):
closed_valves = self.closed_valves closed_valves = self.closed_valves
if not actor.finished: if not actor.finished:
closed_valves = closed_valves.difference({actor.position}) closed_valves = closed_valves.difference({actor.position})
path = f"{self.path} {pressure=}\n" \ flow_rate = self.flow_rate + actor.position.flow_rate
f"1: {actor.next_time}->{actor.position.name}\n" \ else:
f" +{actor.position.flow_rate}\n" flow_rate = self.flow_rate
yield OneActorProgress( next = OneActorProgress(
max_time=self.max_time, max_time=self.max_time,
prev_time=self.next_time, prev_time=self.time,
next_time=actor.next_time, time=actor.next_time,
flow_rate=flow_rate, flow_rate=flow_rate,
pressure=pressure, pressure=self.pressure + self.flow_rate * (actor.next_time - self.time),
closed_valves=closed_valves, closed_valves=closed_valves,
actor=actor, actor=actor,
path=path path=self.path | {(actor.position.name, actor.next_time, True)}
) )
yield next
@dataclass(slots=True, frozen=True) @dataclass(slots=True, frozen=True)
@ -176,75 +164,68 @@ class TwoActorProgress(SystemProgress):
actor1: Actor actor1: Actor
actor2: Actor actor2: Actor
def pressure_at_time(self, time: int) -> int:
pressure = self.pressure + self.flow_rate * (time - self.prev_time)
if time > self.next_time:
if self.actor1.next_time <= time:
pressure += self.actor1.position.flow_rate * (time - self.actor1.next_time + 1)
if self.actor2.next_time <= time:
pressure += self.actor2.position.flow_rate * (time - self.actor2.next_time + 1)
return pressure
@property
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)
(self.max_time - self.next_time - 1)
flow = self.flow_rate * (self.max_time - self.next_time - 1) other = 0
if not self.actor1.finished: if self.actor1.next_time != self.time and not self.actor1.finished:
actor1 = self.actor1.position.flow_rate * (self.max_time - self.actor1.next_time) other += self.actor1.position.flow_rate * (self.max_time - self.actor1.next_time)
else: if self.actor2.next_time != self.time and not self.actor2.finished:
actor1 = 0 other += self.actor2.position.flow_rate * (self.max_time - self.actor2.next_time)
if not self.actor2.finished:
actor2 = self.actor2.position.flow_rate * (self.max_time - self.actor2.next_time) return self.pressure + (self.flow_rate + closed) * (self.max_time - self.time) + other
else:
actor2 = 0
return self.pressure + actor1 + actor2 + closed + flow
def open_valves(self) -> Iterator[SystemProgress]: def open_valves(self) -> Iterator[SystemProgress]:
pressure = self.pressure + self.flow_rate * (self.next_time - self.prev_time) if self.actor1.next_time == self.time:
actor1_actions = self.one_actor(self.actor1)
else:
actor1_actions = [self.actor1]
flow_rate = self.flow_rate if self.actor2.next_time == self.time:
if self.actor1.next_time == self.next_time: actor2_actions = self.one_actor(self.actor2)
flow_rate += self.actor1.position.flow_rate else:
if self.actor2.next_time == self.next_time: actor2_actions = [self.actor2]
flow_rate += self.actor2.position.flow_rate
actor1_actions = list(self.one_actor(self.actor1))
actor2_actions = list(self.one_actor(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:
continue continue
closed_valves = self.closed_valves closed_valves = self.closed_valves
if not actor1.finished: flow_rate = self.flow_rate
closed_valves = closed_valves.difference({actor1.position})
if not actor2.finished:
closed_valves = closed_valves.difference({actor2.position})
next_time = min(actor1.next_time, actor2.next_time) next_time = min(actor1.next_time, actor2.next_time)
path = self.path path: set[tuple[str, int, bool]] = set(self.path)
next_flow = 0
if actor1.next_time == next_time:
path += f" -> (S:{actor1.next_time}/{actor1.position.name})"
next_flow += actor1.position.flow_rate
if actor2.next_time == next_time:
path += f" -> (E:{actor2.next_time}/{actor2.position.name})"
next_flow += actor2.position.flow_rate
next_flow = 0
yield TwoActorProgress( if not actor1.finished:
closed_valves = closed_valves.difference({actor1.position})
if actor1.next_time == next_time:
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:
closed_valves = closed_valves.difference({actor2.position})
if actor2.next_time == next_time:
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(
max_time=self.max_time, max_time=self.max_time,
prev_time=self.next_time, prev_time=self.time,
next_time=next_time, time=next_time,
flow_rate=flow_rate, flow_rate=flow_rate,
pressure=pressure, pressure=self.pressure + self.flow_rate * (next_time - self.time),
closed_valves=closed_valves, closed_valves=closed_valves,
path=path,
actor1=actor1, actor1=actor1,
actor2=actor2, actor2=actor2,
path=frozenset(path)
) )
yield next
@dataclass(slots=True) @dataclass(slots=True)
@ -261,9 +242,7 @@ class Network:
for follow in raw.following: for follow in raw.following:
current.following.append(valves[follow]) current.following.append(valves[follow])
network = Network(valves) return Network(valves)
# network.create_paths()
return network
def under_pressure(self, minutes: int, number_actors: Literal[1] | Literal[2]) -> int: def under_pressure(self, minutes: int, number_actors: Literal[1] | Literal[2]) -> int:
closed_valves = [valve for valve in self.valves.values() if valve.flow_rate > 0] closed_valves = [valve for valve in self.valves.values() if valve.flow_rate > 0]
@ -277,23 +256,20 @@ class Network:
start=start, start=start,
num_actors=number_actors num_actors=number_actors
)) ))
max_system: SystemProgress | None = None max_pressure = 0
tick = 0 seen: set[frozenset[tuple[str, int, bool]]] = set()
max_counter = 0
while not queue.empty(): while not queue.empty():
tick += 1
current = queue.get() current = queue.get()
if current.next_time == minutes: if current.time == minutes:
if max_system is None or max_system.pressure < current.pressure: return current.pressure
max_system = current if max_pressure > current.max_possible_pressure():
max_counter += 1
continue continue
if current.path in seen:
continue
seen.add(current.path)
max_pressure = max(max_pressure, current.pressure)
for next in current.open_valves(): for next in current.open_valves():
if max_system is None or next.max_possible_pressure >= max_system.pressure: queue.put(next)
queue.put(next)
if max_system is None: raise Exception("No best System found")
raise Exception("No best System found")
return max_system.pressure

View file

@ -83,7 +83,7 @@ class Cave:
old = self.cave[block.y] old = self.cave[block.y]
self.cave[block.y] = old[:block.x] + '#' + old[block.x + 1:] self.cave[block.y] = old[:block.x] + '#' + old[block.x + 1:]
@ classmethod @classmethod
def create(cls, width: int, gas_pushes: str) -> Self: def create(cls, width: int, gas_pushes: str) -> Self:
cave = [] cave = []
return cls(width, cave, cycle(gas_pushes), cycle(Pattern(pattern) for pattern in patterns)) return cls(width, cave, cycle(gas_pushes), cycle(Pattern(pattern) for pattern in patterns))

View file

@ -304,7 +304,7 @@ class PasswordCubeJungle(PasswordJungle):
return result return result
@ dataclass(slots=True) @dataclass(slots=True)
class PasswordSimpleJungle(PasswordJungle): class PasswordSimpleJungle(PasswordJungle):
def wrap(self, player: Player) -> Player: def wrap(self, player: Player) -> Player:
match player.facing: match player.facing: