removed prints

This commit is contained in:
Ruediger Ludwig 2022-12-28 15:41:09 +01:00
parent 56764331a4
commit 42b8049d31
5 changed files with 406 additions and 0 deletions

View file

View file

@ -0,0 +1,10 @@
Valve AA has flow rate=0; tunnels lead to valves DD, II, BB
Valve BB has flow rate=13; tunnels lead to valves CC, AA
Valve CC has flow rate=2; tunnels lead to valves DD, BB
Valve DD has flow rate=20; tunnels lead to valves CC, AA, EE
Valve EE has flow rate=3; tunnels lead to valves FF, DD
Valve FF has flow rate=0; tunnels lead to valves EE, GG
Valve GG has flow rate=0; tunnels lead to valves FF, HH
Valve HH has flow rate=22; tunnel leads to valve GG
Valve II has flow rate=0; tunnels lead to valves AA, JJ
Valve JJ has flow rate=21; tunnel leads to valve II

View file

@ -0,0 +1,59 @@
Valve SY has flow rate=0; tunnels lead to valves GW, LW
Valve TS has flow rate=0; tunnels lead to valves CC, OP
Valve LU has flow rate=0; tunnels lead to valves PS, XJ
Valve ND has flow rate=0; tunnels lead to valves EN, TL
Valve PD has flow rate=0; tunnels lead to valves TL, LI
Valve VF has flow rate=0; tunnels lead to valves LW, RX
Valve LD has flow rate=0; tunnels lead to valves AD, LP
Valve DG has flow rate=0; tunnels lead to valves DR, SS
Valve IG has flow rate=8; tunnels lead to valves AN, YA, GA
Valve LK has flow rate=0; tunnels lead to valves HQ, LW
Valve TD has flow rate=14; tunnels lead to valves BG, CQ
Valve CQ has flow rate=0; tunnels lead to valves TD, HD
Valve AZ has flow rate=0; tunnels lead to valves AD, XW
Valve ZU has flow rate=0; tunnels lead to valves TL, AN
Valve HD has flow rate=0; tunnels lead to valves BP, CQ
Valve FX has flow rate=0; tunnels lead to valves LW, XM
Valve CU has flow rate=18; tunnels lead to valves BX, VA, RX, DF
Valve SS has flow rate=17; tunnels lead to valves DG, ZD, ZG
Valve BP has flow rate=19; tunnels lead to valves HD, ZD
Valve DZ has flow rate=0; tunnels lead to valves XS, CC
Valve PS has flow rate=0; tunnels lead to valves GH, LU
Valve TA has flow rate=0; tunnels lead to valves LI, AA
Valve BG has flow rate=0; tunnels lead to valves TD, ZG
Valve WP has flow rate=0; tunnels lead to valves OB, AA
Valve XS has flow rate=9; tunnels lead to valves EN, DZ
Valve AA has flow rate=0; tunnels lead to valves WG, GA, VO, WP, TA
Valve LW has flow rate=25; tunnels lead to valves LK, FX, SY, VF
Valve AD has flow rate=23; tunnels lead to valves DF, GW, AZ, LD, FM
Valve EN has flow rate=0; tunnels lead to valves ND, XS
Valve ZG has flow rate=0; tunnels lead to valves SS, BG
Valve LI has flow rate=11; tunnels lead to valves YA, XM, TA, PD
Valve VO has flow rate=0; tunnels lead to valves AA, OD
Valve AN has flow rate=0; tunnels lead to valves IG, ZU
Valve GH has flow rate=15; tunnels lead to valves VA, PS
Valve OP has flow rate=4; tunnels lead to valves AJ, TS, FM, BX, NM
Valve BX has flow rate=0; tunnels lead to valves OP, CU
Valve RX has flow rate=0; tunnels lead to valves CU, VF
Valve FM has flow rate=0; tunnels lead to valves OP, AD
Valve OB has flow rate=0; tunnels lead to valves WP, XW
Valve CC has flow rate=3; tunnels lead to valves QS, LP, DZ, OD, TS
Valve LP has flow rate=0; tunnels lead to valves LD, CC
Valve NM has flow rate=0; tunnels lead to valves WH, OP
Valve HQ has flow rate=0; tunnels lead to valves XW, LK
Valve GW has flow rate=0; tunnels lead to valves SY, AD
Valve QS has flow rate=0; tunnels lead to valves CC, XW
Valve DF has flow rate=0; tunnels lead to valves AD, CU
Valve XM has flow rate=0; tunnels lead to valves LI, FX
Valve VA has flow rate=0; tunnels lead to valves CU, GH
Valve GA has flow rate=0; tunnels lead to valves IG, AA
Valve YA has flow rate=0; tunnels lead to valves LI, IG
Valve XW has flow rate=20; tunnels lead to valves OB, HQ, QS, WH, AZ
Valve XJ has flow rate=24; tunnel leads to valve LU
Valve AJ has flow rate=0; tunnels lead to valves WG, OP
Valve WH has flow rate=0; tunnels lead to valves XW, NM
Valve TL has flow rate=13; tunnels lead to valves PD, DR, ZU, ND
Valve OD has flow rate=0; tunnels lead to valves CC, VO
Valve ZD has flow rate=0; tunnels lead to valves SS, BP
Valve DR has flow rate=0; tunnels lead to valves DG, TL
Valve WG has flow rate=0; tunnels lead to valves AJ, AA

View file

@ -0,0 +1,299 @@
from __future__ import annotations
from abc import ABC, abstractmethod, abstractproperty
from dataclasses import dataclass, field
from itertools import product
from queue import PriorityQueue
from typing import Iterator, Literal, Self
from advent.parser.parser import P
day_num = 16
def part1(lines: Iterator[str]) -> int:
system = Network.parse(lines)
return system.under_pressure(30, 1)
def part2(lines: Iterator[str]) -> int:
system = Network.parse(lines)
return system.under_pressure(26, 2)
valve_parser = P.map3(
P.second(P.string("Valve "), P.upper().word()),
P.second(P.string(" has flow rate="), P.unsigned()),
P.second(P.either(P.string("; tunnels lead to valves "), P.string("; tunnel leads to valve ")),
P.upper().word().sep_by(P.string(", "))),
lambda name, flow_rate, following: RawValve(name, flow_rate, following)
)
@dataclass(slots=True)
class RawValve:
name: str
flow_rate: int
following: list[str]
@classmethod
def parse(cls, line: str) -> Self:
result = valve_parser.parse(line).get()
return result
@dataclass(slots=True, unsafe_hash=True)
class Valve:
name: str
flow_rate: int
following: list[Valve] = field(hash=False, compare=False)
paths: dict[str, int] | None = field(default=None, hash=False, init=False, compare=False)
def __repr__(self) -> str:
return f"{self.name}:{self.flow_rate}->{','.join(v.name for v in self.following)}"
def travel_time(self, to: str) -> int:
if self.paths is None:
self.create_paths()
return self.paths[to] # type: ignore
def create_paths(self):
paths: dict[str, int] = {}
to_check: list[tuple[Valve, int]] = [(self, 0)]
while to_check:
current, steps = to_check[0]
to_check = to_check[1:]
paths[current.name] = steps
for next in current.following:
if paths.get(next.name, steps + 2) > steps + 1:
to_check.append((next, steps + 1))
self.paths = paths
@dataclass(slots=True, frozen=True)
class Actor:
position: Valve
next_time: int
finished: bool
@dataclass(slots=True, frozen=True)
class SystemProgress(ABC):
max_time: int
prev_time: int
next_time: int
pressure: int
flow_rate: int
closed_valves: frozenset[Valve]
path: str
def one_actor(self, actor: Actor) -> Iterator[Actor]:
if actor.finished or actor.next_time != self.next_time:
yield actor
elif not self.closed_valves:
yield Actor(actor.position, self.max_time, True)
else:
reached_any_target = False
for target in self.closed_valves:
finished = self.next_time + actor.position.travel_time(target.name) + 1
if finished < self.max_time:
reached_any_target = True
yield Actor(target, finished, False)
if not reached_any_target:
yield Actor(actor.position, self.max_time, True)
@classmethod
def create(cls, max_time: int, pressure: int, flow_rate: int,
closed_valves: frozenset[Valve], start: Valve,
num_actors: Literal[1] | Literal[2]) -> SystemProgress:
match num_actors:
case 1:
return OneActorProgress(max_time, 0, 0, pressure, flow_rate, closed_valves,
start.name, Actor(start, 0, False))
case 2:
return TwoActorProgress(max_time, 0, 0, pressure, flow_rate, closed_valves,
start.name, Actor(start, 0, False), Actor(start, 0, False))
case _:
assert False, "Unreachable"
def __lt__(self, other: OneActorProgress) -> bool:
mx = min(self.next_time, other.next_time)
return self.pressure_at_time(mx) > other.pressure_at_time(mx)
@abstractmethod
def pressure_at_time(self, time: int) -> int:
...
@abstractproperty
def max_possible_pressure(self) -> int:
...
@abstractmethod
def open_valves(self) -> Iterator[SystemProgress]:
...
@dataclass(slots=True, frozen=True)
class OneActorProgress(SystemProgress):
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:
closed = sum(valve.flow_rate for valve in self.closed_valves)
return self.pressure + (self.flow_rate + self.actor.position.flow_rate
+ closed) * (self.max_time - self.next_time)
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):
closed_valves = self.closed_valves
if not actor.finished:
closed_valves = closed_valves.difference({actor.position})
path = f"{self.path} {pressure=}\n" \
f"1: {actor.next_time}->{actor.position.name}\n" \
f" +{actor.position.flow_rate}\n"
yield OneActorProgress(
max_time=self.max_time,
prev_time=self.next_time,
next_time=actor.next_time,
flow_rate=flow_rate,
pressure=pressure,
closed_valves=closed_valves,
actor=actor,
path=path
)
@dataclass(slots=True, frozen=True)
class TwoActorProgress(SystemProgress):
actor1: 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:
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)
if not self.actor1.finished:
actor1 = self.actor1.position.flow_rate * (self.max_time - self.actor1.next_time)
else:
actor1 = 0
if not self.actor2.finished:
actor2 = self.actor2.position.flow_rate * (self.max_time - self.actor2.next_time)
else:
actor2 = 0
return self.pressure + actor1 + actor2 + closed + flow
def open_valves(self) -> Iterator[SystemProgress]:
pressure = self.pressure + self.flow_rate * (self.next_time - self.prev_time)
flow_rate = self.flow_rate
if self.actor1.next_time == self.next_time:
flow_rate += self.actor1.position.flow_rate
if self.actor2.next_time == self.next_time:
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):
if not actor1.finished and not actor2.finished and actor1.position == actor2.position:
continue
closed_valves = self.closed_valves
if not actor1.finished:
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)
path = 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(
max_time=self.max_time,
prev_time=self.next_time,
next_time=next_time,
flow_rate=flow_rate,
pressure=pressure,
closed_valves=closed_valves,
path=path,
actor1=actor1,
actor2=actor2,
)
@dataclass(slots=True)
class Network:
valves: dict[str, Valve]
paths: dict[tuple[str, str], list[str]] = field(default_factory=dict)
@classmethod
def parse(cls, lines: Iterator[str]) -> Self:
raw_system = [RawValve.parse(line) for line in lines]
valves = {valve.name: Valve(valve.name, valve.flow_rate, []) for valve in raw_system}
for raw in raw_system:
current = valves[raw.name]
for follow in raw.following:
current.following.append(valves[follow])
network = Network(valves)
# network.create_paths()
return network
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]
start = self.valves["AA"]
queue: PriorityQueue[SystemProgress] = PriorityQueue()
queue.put(SystemProgress.create(
max_time=minutes,
pressure=0,
flow_rate=0,
closed_valves=frozenset(closed_valves),
start=start,
num_actors=number_actors
))
max_system: SystemProgress | None = None
tick = 0
max_counter = 0
while not queue.empty():
tick += 1
current = queue.get()
if current.next_time == minutes:
if max_system is None or max_system.pressure < current.pressure:
max_system = current
max_counter += 1
continue
for next in current.open_valves():
if max_system is None or next.max_possible_pressure >= max_system.pressure:
queue.put(next)
if max_system is None:
raise Exception("No best System found")
return max_system.pressure

View file

@ -0,0 +1,38 @@
from advent.common import input
from .solution import Network, RawValve, day_num, part1, part2
def test_part1():
lines = input.read_lines(day_num, 'example01.txt')
expected = 1651
result = part1(lines)
assert result == expected
def test_part2():
lines = input.read_lines(day_num, 'example01.txt')
expected = 1707
result = part2(lines)
assert result == expected
def test_parse():
line = "Valve AA has flow rate=0; tunnels lead to valves DD, II, BB"
expected = RawValve("AA", 0, ["DD", "II", "BB"])
result = RawValve.parse(line)
assert result == expected
def test_open_system():
lines = input.read_lines(day_num, 'example01.txt')
system = Network.parse(lines)
expected = 1651
assert system.under_pressure(30, 1) == expected
def test_open_system_elephant():
lines = input.read_lines(day_num, 'example01.txt')
system = Network.parse(lines)
expected = 1707
assert system.under_pressure(26, 2) == expected