removed prints
This commit is contained in:
parent
56764331a4
commit
42b8049d31
5 changed files with 406 additions and 0 deletions
0
advent/days/day16/__init__.py
Normal file
0
advent/days/day16/__init__.py
Normal file
10
advent/days/day16/data/example01.txt
Normal file
10
advent/days/day16/data/example01.txt
Normal 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
|
||||
59
advent/days/day16/data/input.txt
Normal file
59
advent/days/day16/data/input.txt
Normal 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
|
||||
299
advent/days/day16/solution.py
Normal file
299
advent/days/day16/solution.py
Normal 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
|
||||
38
advent/days/day16/test_solution.py
Normal file
38
advent/days/day16/test_solution.py
Normal 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
|
||||
Loading…
Add table
Add a link
Reference in a new issue