1177 lines
35 KiB
Rust
1177 lines
35 KiB
Rust
use super::template::{DayTrait, ResultType};
|
|
use crate::common::parser::{eol_terminated, extract_result, ignore, trim0, trim1, trim_left1};
|
|
use itertools::Itertools;
|
|
use nom::{
|
|
branch::alt,
|
|
bytes::complete::tag,
|
|
character::complete::{alpha1, i64},
|
|
error::Error,
|
|
multi::{many1, separated_list1},
|
|
Err, IResult,
|
|
};
|
|
use std::collections::BinaryHeap;
|
|
use std::collections::HashMap;
|
|
use std::fmt::{Debug, Display};
|
|
use std::hash::Hash;
|
|
use std::iter::Sum;
|
|
use std::ops::{Add, Deref, Mul, Sub};
|
|
use thiserror::Error;
|
|
|
|
const DAY_NUMBER: usize = 16;
|
|
|
|
pub struct Day;
|
|
|
|
impl DayTrait for Day {
|
|
fn get_day_number(&self) -> usize {
|
|
DAY_NUMBER
|
|
}
|
|
|
|
fn part1(&self, lines: &str) -> anyhow::Result<ResultType> {
|
|
let system = ValveSystem::build(lines, "AA")?;
|
|
let result = system.maximum_flow(system.single_actor(Time(30)))?;
|
|
Ok(ResultType::Integer(*result))
|
|
}
|
|
|
|
fn part2(&self, lines: &str) -> anyhow::Result<ResultType> {
|
|
let system = ValveSystem::build(lines, "AA")?;
|
|
let result = system.maximum_flow(system.double_actor(Time(26)))?;
|
|
Ok(ResultType::Integer(*result))
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
|
struct Time(usize);
|
|
|
|
impl Time {
|
|
#[inline]
|
|
fn sub_to_zero(&self, other: Time) -> Time {
|
|
if self.0 > other.0 {
|
|
Time(self.0 - other.0)
|
|
} else {
|
|
Time(0)
|
|
}
|
|
}
|
|
|
|
#[inline]
|
|
fn inc(&self) -> Self {
|
|
Self(self.0 + 1)
|
|
}
|
|
}
|
|
|
|
impl Deref for Time {
|
|
type Target = usize;
|
|
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl Add for Time {
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn add(self, rhs: Self) -> Self::Output {
|
|
Self(self.0 + rhs.0)
|
|
}
|
|
}
|
|
|
|
impl Sub for Time {
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn sub(self, rhs: Self) -> Self::Output {
|
|
Self(self.0 - rhs.0)
|
|
}
|
|
}
|
|
|
|
impl Mul<Flow> for Time {
|
|
type Output = Flow;
|
|
|
|
fn mul(self, rhs: Flow) -> Self::Output {
|
|
Flow(self.0 as i64 * *rhs)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
|
struct Flow(i64);
|
|
impl Deref for Flow {
|
|
type Target = i64;
|
|
|
|
#[inline]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
impl Add for Flow {
|
|
type Output = Self;
|
|
|
|
#[inline]
|
|
fn add(self, rhs: Self) -> Self::Output {
|
|
Self(self.0 + rhs.0)
|
|
}
|
|
}
|
|
|
|
impl Sum for Flow {
|
|
fn sum<I: Iterator<Item = Self>>(iter: I) -> Self {
|
|
iter.fold(Self(0), |a, b| a + b)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
|
struct Index(usize);
|
|
|
|
impl Deref for Index {
|
|
type Target = usize;
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Error)]
|
|
enum ValveError {
|
|
#[error("Not a valid description: {0}")]
|
|
ParsingError(String),
|
|
|
|
#[error("Not a valid valve: {0}")]
|
|
ValveNotFound(String),
|
|
|
|
#[error("Did nt find optimum flow")]
|
|
NoOptimumFound,
|
|
}
|
|
|
|
impl From<Err<Error<&str>>> for ValveError {
|
|
fn from(error: Err<Error<&str>>) -> Self {
|
|
ValveError::ParsingError(error.to_string())
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
struct RawValve<'a> {
|
|
id: &'a str,
|
|
flow: Flow,
|
|
neighbors: Vec<&'a str>,
|
|
}
|
|
|
|
impl<'a> RawValve<'a> {
|
|
fn create(id: &'a str, flow: Flow, neighbors: Vec<&'a str>) -> Self {
|
|
RawValve {
|
|
id,
|
|
flow,
|
|
neighbors,
|
|
}
|
|
}
|
|
|
|
fn parse_start(input: &str) -> IResult<&str, (&str, Flow)> {
|
|
let input = ignore(tag("Valve"))(input)?;
|
|
let (input, valve) = trim1(alpha1)(input)?;
|
|
let input = ignore(tag("has flow rate="))(input)?;
|
|
let (input, flow) = trim0(i64)(input)?;
|
|
let input = ignore(tag(";"))(input)?;
|
|
Ok((input, (valve, Flow(flow))))
|
|
}
|
|
|
|
fn parse_singular(input: &str) -> IResult<&str, Vec<&str>> {
|
|
let input = ignore(tag("tunnel leads to valve "))(input)?;
|
|
let (input, valve) = alpha1(input)?;
|
|
|
|
Ok((input, vec![valve]))
|
|
}
|
|
|
|
fn parse_plural(input: &str) -> IResult<&str, Vec<&str>> {
|
|
let input = ignore(tag("tunnels lead to valves "))(input)?;
|
|
let (input, valve) = separated_list1(tag(","), trim0(alpha1))(input)?;
|
|
|
|
Ok((input, valve))
|
|
}
|
|
|
|
fn parse(input: &str) -> IResult<&str, RawValve> {
|
|
let (input, (id, flow)) = RawValve::parse_start(input)?;
|
|
let (input, neighbors) = trim_left1(eol_terminated(alt((
|
|
RawValve::parse_plural,
|
|
RawValve::parse_singular,
|
|
))))(input)?;
|
|
|
|
Ok((input, RawValve::create(id, flow, neighbors)))
|
|
}
|
|
}
|
|
|
|
impl<'a> TryFrom<&'a str> for RawValve<'a> {
|
|
type Error = ValveError;
|
|
|
|
fn try_from(value: &'a str) -> Result<Self, Self::Error> {
|
|
Ok(extract_result(RawValve::parse)(value)?)
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct Valve {
|
|
id: String,
|
|
idx: Index,
|
|
flow: Flow,
|
|
distances: Vec<Time>,
|
|
}
|
|
|
|
impl Valve {
|
|
fn find_neighbors<'a>(
|
|
others: &'a [RawValve],
|
|
id: &str,
|
|
) -> Result<&'a Vec<&'a str>, ValveError> {
|
|
others
|
|
.iter()
|
|
.find(|valve| valve.id == id)
|
|
.map(|valve| &valve.neighbors)
|
|
.ok_or_else(|| ValveError::ValveNotFound(id.to_owned()))
|
|
}
|
|
|
|
fn create(valve: &RawValve, idx: Index, others: &[RawValve]) -> Result<Valve, ValveError> {
|
|
let mut distances = Vec::with_capacity(others.len());
|
|
distances.push((valve.id, Time(0)));
|
|
|
|
let mut check_idx = 0;
|
|
while check_idx < distances.len() {
|
|
let (id, dist) = &distances[check_idx];
|
|
let mut new_items = Valve::find_neighbors(others, id)?
|
|
.iter()
|
|
.filter(|neighbor| distances.iter().all(|(other, _)| other != *neighbor))
|
|
.map(|id| (*id, dist.inc()))
|
|
.collect_vec();
|
|
distances.append(&mut new_items);
|
|
check_idx += 1;
|
|
}
|
|
|
|
distances.sort_unstable_by_key(|(id, _)| *id);
|
|
|
|
Ok(Valve {
|
|
id: valve.id.to_owned(),
|
|
idx,
|
|
flow: valve.flow,
|
|
distances: distances
|
|
.iter()
|
|
.map(|(_, dist)| if *dist > Time(0) { dist.inc() } else { *dist })
|
|
.collect_vec(),
|
|
})
|
|
}
|
|
|
|
fn zero_flow(valve: &RawValve, idx: Index) -> Valve {
|
|
Valve {
|
|
id: valve.id.to_owned(),
|
|
idx,
|
|
flow: valve.flow,
|
|
distances: vec![],
|
|
}
|
|
}
|
|
|
|
pub fn steps_to(&self, idx: Index) -> Time {
|
|
self.distances[*idx]
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ValveSystem {
|
|
valves: Vec<Valve>,
|
|
start_idx: Index,
|
|
}
|
|
|
|
impl ValveSystem {
|
|
fn build(lines: &str, start: &str) -> Result<ValveSystem, ValveError> {
|
|
let mut raw = extract_result(many1(RawValve::parse))(lines)?;
|
|
raw.sort_unstable_by_key(|valve| valve.id);
|
|
|
|
let start_idx = raw
|
|
.iter()
|
|
.enumerate()
|
|
.find(|(_, valve)| start == valve.id)
|
|
.map(|(idx, _)| Index(idx))
|
|
.ok_or_else(|| ValveError::ValveNotFound(start.to_owned()))?;
|
|
|
|
raw.iter()
|
|
.enumerate()
|
|
.map(|(idx, valve)| {
|
|
let idx = Index(idx);
|
|
Ok::<_, ValveError>(if idx == start_idx || valve.flow.is_positive() {
|
|
Valve::create(valve, idx, &raw)?
|
|
} else {
|
|
Valve::zero_flow(valve, idx)
|
|
})
|
|
})
|
|
.try_collect()
|
|
.map(|valves| ValveSystem { valves, start_idx })
|
|
}
|
|
|
|
pub fn get_closed_valves(&self) -> Vec<Index> {
|
|
self.valves
|
|
.iter()
|
|
.filter_map(|valve| valve.flow.is_positive().then_some(valve.idx))
|
|
.collect_vec()
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
fn get_valve_by_id(&self, id: &str) -> Result<&Valve, ValveError> {
|
|
self.valves
|
|
.iter()
|
|
.find(|valve| valve.id == id)
|
|
.ok_or(ValveError::ValveNotFound(id.to_owned()))
|
|
}
|
|
|
|
fn get_valve(&self, idx: Index) -> &Valve {
|
|
&self.valves[*idx]
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn len(&self) -> usize {
|
|
self.valves.len()
|
|
}
|
|
|
|
pub fn single_actor(&self, time: Time) -> SingleActor {
|
|
SingleActor::new(self, time)
|
|
}
|
|
|
|
pub fn double_actor(&self, time: Time) -> DoubleActor {
|
|
DoubleActor::new(self, time)
|
|
}
|
|
|
|
pub fn maximum_flow<AG: ActorGroup>(&self, actor: AG) -> Result<Flow, ValveError> {
|
|
let start = ValveState::init(self, actor);
|
|
let mut queue = BinaryHeap::new();
|
|
queue.push(WeightedState(start));
|
|
let mut known = HashMap::new();
|
|
|
|
while let Some(state) = queue.pop() {
|
|
if state.is_finished() {
|
|
return Ok(state.flow);
|
|
}
|
|
|
|
let hashed = HashableState::new(&state);
|
|
if let Some(value) = known.get(&hashed) {
|
|
if *value >= state.flow {
|
|
continue;
|
|
}
|
|
}
|
|
known.insert(hashed, state.flow);
|
|
|
|
for next in state.calc_next_states(self) {
|
|
queue.push(WeightedState(next));
|
|
}
|
|
}
|
|
Err(ValveError::NoOptimumFound)
|
|
}
|
|
}
|
|
|
|
trait ActorGroup: Sized + Debug + Display + Clone + Eq + Hash {
|
|
fn is_finished(&self) -> bool;
|
|
fn working_on(&self) -> Vec<Index>;
|
|
fn get_now_time(&self) -> Option<Time>;
|
|
fn opened_now(&self) -> Vec<Index>;
|
|
fn opened_later(&self) -> Vec<Index>;
|
|
fn added_flow_now(&self, system: &ValveSystem) -> Flow;
|
|
fn flow_fixed(&self, system: &ValveSystem) -> Flow;
|
|
fn potential_flow(&self, closed: &[Index], system: &ValveSystem) -> Flow;
|
|
fn next_moves(&self, closed: &[Index], system: &ValveSystem) -> Vec<Self>;
|
|
fn show(&self, system: &ValveSystem) -> String;
|
|
|
|
fn calc_next_moves(&self, state: &ValveState<Self>, system: &ValveSystem) -> Vec<Self> {
|
|
self.next_moves(&state.closed, system)
|
|
.into_iter()
|
|
.unique()
|
|
.collect_vec()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
enum Work {
|
|
AtWork(Index, Time),
|
|
Finished,
|
|
}
|
|
|
|
impl Work {
|
|
fn build(idx: Index, time: Time, steps: Time) -> Work {
|
|
if time > steps {
|
|
Work::AtWork(idx, time - steps)
|
|
} else {
|
|
Work::Finished
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
struct SingleActor(Work);
|
|
|
|
impl SingleActor {
|
|
fn new(system: &ValveSystem, time: Time) -> SingleActor {
|
|
SingleActor(Work::AtWork(system.start_idx, time))
|
|
}
|
|
}
|
|
|
|
impl Display for SingleActor {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self.0 {
|
|
Work::AtWork(idx, time) => write!(f, "({:?}/{:?}", time, idx),
|
|
Work::Finished => write!(f, "!"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ActorGroup for SingleActor {
|
|
fn is_finished(&self) -> bool {
|
|
matches!(self.0, Work::Finished)
|
|
}
|
|
|
|
fn show(&self, system: &ValveSystem) -> String {
|
|
match self.0 {
|
|
Work::AtWork(idx, _) => format!("{}", system.get_valve(idx).id),
|
|
Work::Finished => String::from("!"),
|
|
}
|
|
}
|
|
|
|
fn next_moves(&self, closed: &[Index], system: &ValveSystem) -> Vec<Self> {
|
|
let Work::AtWork(from_idx, time) = self.0 else {
|
|
return vec![SingleActor(Work::Finished)];
|
|
};
|
|
if closed.is_empty() {
|
|
return vec![SingleActor(Work::Finished)];
|
|
}
|
|
|
|
let location = system.get_valve(from_idx);
|
|
closed
|
|
.iter()
|
|
.map(move |to_idx| {
|
|
let steps = location.steps_to(*to_idx);
|
|
if time > steps {
|
|
SingleActor(Work::AtWork(*to_idx, time - steps))
|
|
} else {
|
|
SingleActor(Work::Finished)
|
|
}
|
|
})
|
|
.collect_vec()
|
|
}
|
|
|
|
fn working_on(&self) -> Vec<Index> {
|
|
match self.0 {
|
|
Work::AtWork(idx, _) => vec![idx],
|
|
Work::Finished => vec![],
|
|
}
|
|
}
|
|
|
|
fn opened_now(&self) -> Vec<Index> {
|
|
match self.0 {
|
|
Work::AtWork(idx, _) => vec![idx],
|
|
Work::Finished => vec![],
|
|
}
|
|
}
|
|
|
|
fn opened_later(&self) -> Vec<Index> {
|
|
vec![]
|
|
}
|
|
|
|
fn added_flow_now(&self, system: &ValveSystem) -> Flow {
|
|
match self.0 {
|
|
Work::AtWork(idx, time) => time * system.get_valve(idx).flow,
|
|
Work::Finished => Flow(0),
|
|
}
|
|
}
|
|
|
|
fn get_now_time(&self) -> Option<Time> {
|
|
match self.0 {
|
|
Work::AtWork(_, time) => Some(time),
|
|
Work::Finished => None,
|
|
}
|
|
}
|
|
|
|
fn flow_fixed(&self, _system: &ValveSystem) -> Flow {
|
|
Flow(0)
|
|
}
|
|
|
|
fn potential_flow(&self, closed: &[Index], system: &ValveSystem) -> Flow {
|
|
self.next_moves(closed, system)
|
|
.into_iter()
|
|
.filter_map(|work| match work {
|
|
SingleActor(Work::AtWork(idx, time)) => Some((idx, time)),
|
|
SingleActor(Work::Finished) => None,
|
|
})
|
|
.map(|(to_idx, time)| time * system.get_valve(to_idx).flow)
|
|
.sum()
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
enum DoubleWork {
|
|
Simultaneous(Index, Index, Time),
|
|
Sequential(Index, Time, Index, Time),
|
|
OnlyOne(Index, Time),
|
|
Finished,
|
|
}
|
|
|
|
impl DoubleWork {
|
|
fn build(work0: Work, work1: Work) -> DoubleWork {
|
|
match (work0, work1) {
|
|
(Work::Finished, Work::Finished) => DoubleWork::Finished,
|
|
(Work::Finished, Work::AtWork(idx, time))
|
|
| (Work::AtWork(idx, time), Work::Finished) => DoubleWork::OnlyOne(idx, time),
|
|
(Work::AtWork(idx0, time0), Work::AtWork(idx1, time1)) if time0 == time1 => {
|
|
DoubleWork::Simultaneous(idx0.min(idx1), idx0.max(idx1), time0)
|
|
}
|
|
(Work::AtWork(idx0, time0), Work::AtWork(idx1, time1)) => {
|
|
if time0 > time1 {
|
|
DoubleWork::Sequential(idx0, time0, idx1, time1)
|
|
} else {
|
|
DoubleWork::Sequential(idx1, time1, idx0, time0)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
|
struct DoubleActor(DoubleWork);
|
|
|
|
impl Display for DoubleActor {
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
match self.0 {
|
|
DoubleWork::Simultaneous(idx0, idx1, time) => {
|
|
write!(f, "[{:?}/{:?}+{:?}]", time, idx0, idx1)
|
|
}
|
|
DoubleWork::Sequential(idx0, time0, idx1, _) => {
|
|
write!(f, "[{:?}/{:?}>{:?}]", time0, idx0, idx1)
|
|
}
|
|
DoubleWork::OnlyOne(time0, idx0) => {
|
|
write!(f, "({:?}/{:?})", time0, idx0)
|
|
}
|
|
DoubleWork::Finished => {
|
|
write!(f, "!")
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl DoubleActor {
|
|
fn new(system: &ValveSystem, time: Time) -> DoubleActor {
|
|
DoubleActor(DoubleWork::Simultaneous(
|
|
system.start_idx,
|
|
system.start_idx,
|
|
time,
|
|
))
|
|
}
|
|
}
|
|
|
|
impl ActorGroup for DoubleActor {
|
|
fn is_finished(&self) -> bool {
|
|
matches!(self.0, DoubleWork::Finished)
|
|
}
|
|
|
|
fn show(&self, system: &ValveSystem) -> String {
|
|
match self.0 {
|
|
DoubleWork::Simultaneous(idx1, idx2, _) => {
|
|
let name1 = &system.get_valve(idx1).id;
|
|
let name2 = &system.get_valve(idx2).id;
|
|
format!("({}/{})", name1, name2)
|
|
}
|
|
DoubleWork::Sequential(idx1, _, idx2, _) => {
|
|
let name1 = &system.get_valve(idx1).id;
|
|
let name2 = &system.get_valve(idx2).id;
|
|
format!("({}-{})", name1, name2)
|
|
}
|
|
DoubleWork::OnlyOne(idx, _) => {
|
|
let name = &system.get_valve(idx).id;
|
|
format!("({})", name)
|
|
}
|
|
DoubleWork::Finished => String::from("!"),
|
|
}
|
|
}
|
|
|
|
fn next_moves(&self, closed: &[Index], system: &ValveSystem) -> Vec<Self> {
|
|
match self.0 {
|
|
DoubleWork::Finished => vec![DoubleActor(DoubleWork::Finished)],
|
|
|
|
DoubleWork::OnlyOne(from_idx, time) => {
|
|
if closed.is_empty() {
|
|
vec![DoubleActor(DoubleWork::Finished)]
|
|
} else {
|
|
let location = system.get_valve(from_idx);
|
|
closed
|
|
.iter()
|
|
.map(|to_idx| {
|
|
let steps = location.steps_to(*to_idx);
|
|
if time > steps {
|
|
DoubleActor(DoubleWork::OnlyOne(*to_idx, time - steps))
|
|
} else {
|
|
DoubleActor(DoubleWork::Finished)
|
|
}
|
|
})
|
|
.collect_vec()
|
|
}
|
|
}
|
|
|
|
DoubleWork::Sequential(idx0, time0, idx1, time1) => {
|
|
let location0 = system.get_valve(idx0);
|
|
let result = closed
|
|
.iter()
|
|
.filter(|to_idx| **to_idx != idx1)
|
|
.map(|to_idx| {
|
|
let steps0 = location0.steps_to(*to_idx);
|
|
DoubleActor(DoubleWork::build(
|
|
Work::AtWork(idx1, time1),
|
|
Work::build(*to_idx, time0, steps0),
|
|
))
|
|
})
|
|
.collect_vec();
|
|
|
|
if result.is_empty() {
|
|
vec![DoubleActor(DoubleWork::OnlyOne(idx1, time1))]
|
|
} else {
|
|
result
|
|
}
|
|
}
|
|
|
|
DoubleWork::Simultaneous(idx0, idx1, time) => match closed.len() {
|
|
0 => vec![DoubleActor(DoubleWork::Finished)],
|
|
1 => [idx0, idx1]
|
|
.into_iter()
|
|
.map(|from_idx| {
|
|
let steps = system.get_valve(from_idx).steps_to(closed[0]);
|
|
DoubleActor(DoubleWork::build(
|
|
Work::build(closed[0], time, steps),
|
|
Work::Finished,
|
|
))
|
|
})
|
|
.collect_vec(),
|
|
_ => {
|
|
let location0 = system.get_valve(idx0);
|
|
let location1 = system.get_valve(idx1);
|
|
closed
|
|
.iter()
|
|
.permutations(2)
|
|
.map(move |to_idx| {
|
|
let steps0 = location0.steps_to(*to_idx[0]);
|
|
let steps1 = location1.steps_to(*to_idx[1]);
|
|
DoubleActor(DoubleWork::build(
|
|
Work::build(*to_idx[0], time, steps0),
|
|
Work::build(*to_idx[1], time, steps1),
|
|
))
|
|
})
|
|
.collect_vec()
|
|
}
|
|
},
|
|
}
|
|
}
|
|
|
|
fn working_on(&self) -> Vec<Index> {
|
|
match self.0 {
|
|
DoubleWork::Sequential(idx0, _, idx1, _) | DoubleWork::Simultaneous(idx0, idx1, _) => {
|
|
vec![idx0, idx1]
|
|
}
|
|
DoubleWork::OnlyOne(idx, _) => {
|
|
vec![idx]
|
|
}
|
|
DoubleWork::Finished => vec![],
|
|
}
|
|
}
|
|
|
|
fn opened_now(&self) -> Vec<Index> {
|
|
match self.0 {
|
|
DoubleWork::Simultaneous(idx0, idx1, _) => vec![idx0, idx1],
|
|
DoubleWork::Sequential(idx, _, _, _) | DoubleWork::OnlyOne(idx, _) => {
|
|
vec![idx]
|
|
}
|
|
DoubleWork::Finished => vec![],
|
|
}
|
|
}
|
|
|
|
fn opened_later(&self) -> Vec<Index> {
|
|
match self.0 {
|
|
DoubleWork::Sequential(_, _, idx, _) => vec![idx],
|
|
_ => vec![],
|
|
}
|
|
}
|
|
|
|
fn added_flow_now(&self, system: &ValveSystem) -> Flow {
|
|
match self.0 {
|
|
DoubleWork::Simultaneous(idx0, idx1, time) => {
|
|
time * (system.get_valve(idx0).flow + system.get_valve(idx1).flow)
|
|
}
|
|
DoubleWork::Sequential(idx, time, _, _) | DoubleWork::OnlyOne(idx, time) => {
|
|
time * system.get_valve(idx).flow
|
|
}
|
|
DoubleWork::Finished => Flow(0),
|
|
}
|
|
}
|
|
|
|
fn flow_fixed(&self, system: &ValveSystem) -> Flow {
|
|
match self.0 {
|
|
DoubleWork::Sequential(_, _, idx, time) => time * system.get_valve(idx).flow,
|
|
DoubleWork::OnlyOne(_, _)
|
|
| DoubleWork::Simultaneous(_, _, _)
|
|
| DoubleWork::Finished => Flow(0),
|
|
}
|
|
}
|
|
|
|
fn get_now_time(&self) -> Option<Time> {
|
|
match self.0 {
|
|
DoubleWork::Simultaneous(_, _, time)
|
|
| DoubleWork::Sequential(_, time, _, _)
|
|
| DoubleWork::OnlyOne(_, time) => Some(time),
|
|
DoubleWork::Finished => None,
|
|
}
|
|
}
|
|
|
|
fn potential_flow(&self, closed: &[Index], system: &ValveSystem) -> Flow {
|
|
match self.0 {
|
|
DoubleWork::Simultaneous(idx0, idx1, time) => {
|
|
let location0 = system.get_valve(idx0);
|
|
let location1 = system.get_valve(idx1);
|
|
closed
|
|
.iter()
|
|
.map(|to_idx| {
|
|
let steps0 = time.sub_to_zero(location0.steps_to(*to_idx));
|
|
let steps1 = time.sub_to_zero(location1.steps_to(*to_idx));
|
|
steps0.max(steps1) * system.get_valve(*to_idx).flow
|
|
})
|
|
.sum()
|
|
}
|
|
DoubleWork::Sequential(idx0, time0, idx1, time1) => {
|
|
let location0 = system.get_valve(idx0);
|
|
let location1 = system.get_valve(idx1);
|
|
closed
|
|
.iter()
|
|
.map(|to_idx| {
|
|
let steps0 = time0.sub_to_zero(location0.steps_to(*to_idx));
|
|
let steps1 = time1.sub_to_zero(location1.steps_to(*to_idx));
|
|
steps0.max(steps1) * system.get_valve(*to_idx).flow
|
|
})
|
|
.sum()
|
|
}
|
|
DoubleWork::OnlyOne(idx, time) => {
|
|
let location = system.get_valve(idx);
|
|
closed
|
|
.iter()
|
|
.map(|to_idx| {
|
|
let steps = time.sub_to_zero(location.steps_to(*to_idx));
|
|
steps * system.get_valve(*to_idx).flow
|
|
})
|
|
.sum()
|
|
}
|
|
DoubleWork::Finished => Flow(0),
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct ValveState<AG: ActorGroup> {
|
|
closed: Vec<Index>,
|
|
time: Option<Time>,
|
|
flow: Flow,
|
|
potential: Flow,
|
|
actor: AG,
|
|
}
|
|
|
|
impl<AG: ActorGroup> ValveState<AG> {
|
|
#[inline]
|
|
pub fn flow_potential(&self) -> Flow {
|
|
self.flow + self.potential
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn make_history(path: &[(AG, Flow)], finished: bool, system: &ValveSystem) -> String {
|
|
let h = path
|
|
.iter()
|
|
.map(|(actor, _)| actor.show(system))
|
|
.join(" -> ");
|
|
if finished {
|
|
format!("{} -> !", h)
|
|
} else {
|
|
h
|
|
}
|
|
}
|
|
|
|
pub fn calc_next_states(&self, system: &ValveSystem) -> Vec<ValveState<AG>> {
|
|
self.actor
|
|
.calc_next_moves(self, system)
|
|
.into_iter()
|
|
.map(|actor| self.derive_state(actor, system))
|
|
.collect_vec()
|
|
}
|
|
|
|
fn derive_state(&self, actor: AG, system: &ValveSystem) -> ValveState<AG> {
|
|
if !actor.is_finished() {
|
|
let opened = actor.opened_now();
|
|
let closed = self
|
|
.closed
|
|
.iter()
|
|
.filter(|idx| !opened.contains(*idx))
|
|
.copied()
|
|
.collect_vec();
|
|
let added_flow = actor.added_flow_now(system);
|
|
let potential = actor.potential_flow(&closed, system);
|
|
ValveState {
|
|
closed,
|
|
time: self.actor.get_now_time(),
|
|
flow: self.flow + added_flow,
|
|
potential,
|
|
actor,
|
|
}
|
|
} else {
|
|
ValveState {
|
|
closed: vec![],
|
|
time: None,
|
|
flow: self.flow,
|
|
potential: Flow(0),
|
|
actor,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
impl<AG: ActorGroup> ValveState<AG> {
|
|
pub fn is_finished(&self) -> bool {
|
|
self.time.is_none()
|
|
}
|
|
|
|
pub fn init(system: &ValveSystem, actor: AG) -> ValveState<AG> {
|
|
let closed = system.get_closed_valves();
|
|
let potential = actor.potential_flow(&closed, system);
|
|
ValveState {
|
|
closed,
|
|
time: actor.get_now_time(),
|
|
flow: Flow(0),
|
|
potential,
|
|
actor,
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
struct WeightedState<AG: ActorGroup>(ValveState<AG>);
|
|
|
|
impl<AG: ActorGroup> PartialEq for WeightedState<AG> {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
self.time == other.time && self.flow + self.flow_potential() == other.flow_potential()
|
|
}
|
|
}
|
|
|
|
impl<AG: ActorGroup> Eq for WeightedState<AG> {}
|
|
|
|
impl<AG: ActorGroup> PartialOrd for WeightedState<AG> {
|
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
|
Some(self.cmp(other))
|
|
}
|
|
}
|
|
|
|
impl<AG: ActorGroup> Ord for WeightedState<AG> {
|
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
|
self.flow_potential()
|
|
.cmp(&other.flow_potential())
|
|
.then_with(|| self.time.cmp(&other.time))
|
|
}
|
|
}
|
|
|
|
impl<AG: ActorGroup> Deref for WeightedState<AG> {
|
|
type Target = ValveState<AG>;
|
|
|
|
fn deref<'b>(&'b self) -> &'b Self::Target {
|
|
&self.0
|
|
}
|
|
}
|
|
|
|
struct HashableState(Vec<Index>, Vec<Index>);
|
|
|
|
impl HashableState {
|
|
fn new<AG: ActorGroup>(state: &ValveState<AG>) -> Self {
|
|
HashableState(state.closed.clone(), state.actor.opened_later().clone())
|
|
}
|
|
}
|
|
|
|
impl PartialEq for HashableState {
|
|
fn eq(&self, other: &Self) -> bool {
|
|
if self.0.len() != other.0.len() || self.1.len() != other.1.len() {
|
|
return false;
|
|
}
|
|
for (c1, c2) in self.0.iter().zip(other.0.iter()) {
|
|
if *c1 != *c2 {
|
|
return false;
|
|
}
|
|
}
|
|
for (o1, o2) in self.1.iter().zip(other.1.iter()) {
|
|
if *o1 != *o2 {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
true
|
|
}
|
|
}
|
|
|
|
impl Eq for HashableState {}
|
|
|
|
impl Hash for HashableState {
|
|
fn hash<H: std::hash::Hasher>(&self, hasher: &mut H) {
|
|
for c in &self.0 {
|
|
c.hash(hasher);
|
|
}
|
|
if !self.1.is_empty() {
|
|
"!".hash(hasher);
|
|
for o in &self.1 {
|
|
o.hash(hasher);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod test {
|
|
use super::*;
|
|
use crate::common::file::read_string;
|
|
use anyhow::Result;
|
|
|
|
#[test]
|
|
fn test_part1() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let expected = ResultType::Integer(1651);
|
|
let result = day.part1(&lines)?;
|
|
assert_eq!(result, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn parse_plural() -> Result<()> {
|
|
let line = "Valve BB has flow rate=13; tunnels lead to valves CC, AA";
|
|
let expected = RawValve::create("BB", Flow(13), vec!["CC", "AA"]);
|
|
let result = line.try_into()?;
|
|
assert_eq!(expected, result);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn parse_singular() -> Result<()> {
|
|
let line = "Valve HH has flow rate=22; tunnel leads to valve GG";
|
|
let expected = RawValve::create("HH", Flow(22), vec!["GG"]);
|
|
let result = line.try_into()?;
|
|
assert_eq!(expected, result);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn parse_system() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
|
|
assert_eq!(10, system.len());
|
|
|
|
let aa = system.get_valve_by_id("AA")?;
|
|
let ee = system.get_valve_by_id("EE")?;
|
|
|
|
assert_eq!(Time(0), aa.steps_to(aa.idx));
|
|
assert_eq!(Time(3), aa.steps_to(ee.idx));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn single_state_init() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
let actor = system.single_actor(Time(5));
|
|
|
|
let state = ValveState::init(&system, actor);
|
|
assert_eq!(state.potential, Flow(39 + 4 + 60 + 6 + 42));
|
|
assert_eq!(state.time, Some(Time(5)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn next_single_actor_states() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
let actor = system.single_actor(Time(5));
|
|
let state = ValveState::init(&system, actor);
|
|
|
|
let next = state.actor.calc_next_moves(&state, &system);
|
|
let expected = vec![
|
|
SingleActor(Work::AtWork(Index(1), Time(3))),
|
|
SingleActor(Work::AtWork(Index(2), Time(2))),
|
|
SingleActor(Work::AtWork(Index(3), Time(3))),
|
|
SingleActor(Work::AtWork(Index(4), Time(2))),
|
|
SingleActor(Work::Finished),
|
|
SingleActor(Work::AtWork(Index(9), Time(2))),
|
|
];
|
|
assert_eq!(next, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn next_single_states() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
let actor = system.single_actor(Time(6));
|
|
let state = ValveState::init(&system, actor);
|
|
|
|
let next = state.calc_next_states(&system);
|
|
assert_eq!(next.len(), 6);
|
|
let result = &next[3];
|
|
assert_eq!(result.flow, Flow(3 * 3));
|
|
assert_eq!(
|
|
result.closed,
|
|
vec![Index(1), Index(2), Index(3), Index(7), Index(9)]
|
|
);
|
|
assert_eq!(result.potential, Flow(20 * 1));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn double_state_init() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
let actor = system.double_actor(Time(5));
|
|
let state = ValveState::init(&system, actor);
|
|
|
|
assert_eq!(state.potential, Flow(39 + 4 + 60 + 6 + 42));
|
|
assert_eq!(state.time, Some(Time(5)));
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn next_double_actor_states() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
let actor = system.double_actor(Time(5));
|
|
let state = ValveState::init(&system, actor);
|
|
|
|
let next = state.actor.calc_next_moves(&state, &system);
|
|
let expected = vec![
|
|
DoubleActor(DoubleWork::Sequential(Index(1), Time(3), Index(2), Time(2))),
|
|
DoubleActor(DoubleWork::Simultaneous(Index(1), Index(3), Time(3))),
|
|
DoubleActor(DoubleWork::Sequential(Index(1), Time(3), Index(4), Time(2))),
|
|
DoubleActor(DoubleWork::OnlyOne(Index(1), Time(3))),
|
|
DoubleActor(DoubleWork::Sequential(Index(1), Time(3), Index(9), Time(2))),
|
|
DoubleActor(DoubleWork::Sequential(Index(3), Time(3), Index(2), Time(2))),
|
|
DoubleActor(DoubleWork::Simultaneous(Index(2), Index(4), Time(2))),
|
|
DoubleActor(DoubleWork::OnlyOne(Index(2), Time(2))),
|
|
DoubleActor(DoubleWork::Simultaneous(Index(2), Index(9), Time(2))),
|
|
DoubleActor(DoubleWork::Sequential(Index(3), Time(3), Index(4), Time(2))),
|
|
DoubleActor(DoubleWork::OnlyOne(Index(3), Time(3))),
|
|
DoubleActor(DoubleWork::Sequential(Index(3), Time(3), Index(9), Time(2))),
|
|
DoubleActor(DoubleWork::OnlyOne(Index(4), Time(2))),
|
|
DoubleActor(DoubleWork::Simultaneous(Index(4), Index(9), Time(2))),
|
|
DoubleActor(DoubleWork::OnlyOne(Index(9), Time(2))),
|
|
];
|
|
assert_eq!(next, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn next_double_states() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
let actor = system.double_actor(Time(5));
|
|
let state = ValveState::init(&system, actor);
|
|
|
|
let next = state.calc_next_states(&system);
|
|
assert_eq!(next.len(), 15);
|
|
let result = &next[4];
|
|
assert_eq!(result.flow, Flow(13 * 3));
|
|
assert_eq!(result.potential, Flow(21 * 2 + 2 * 1));
|
|
assert_eq!(result.flow_potential(), Flow(13 * 3 + 21 * 2 + 2 * 1));
|
|
assert_eq!(
|
|
result.closed,
|
|
vec![Index(2), Index(3), Index(4), Index(7), Index(9)]
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn special_double_stats() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
let actor = DoubleActor(DoubleWork::Simultaneous(Index(1), Index(7), Time(19)));
|
|
let state = ValveState {
|
|
closed: vec![Index(2), Index(4)],
|
|
time: Some(Time(15)),
|
|
flow: Flow(1628),
|
|
potential: Flow(79),
|
|
actor,
|
|
};
|
|
|
|
let next = state.calc_next_states(&system);
|
|
assert_eq!(next.len(), 2);
|
|
|
|
assert_eq!(
|
|
next[0].actor,
|
|
DoubleActor(DoubleWork::Sequential(
|
|
Index(2),
|
|
Time(17),
|
|
Index(4),
|
|
Time(15)
|
|
))
|
|
);
|
|
assert_eq!(next[0].flow, Flow(1662));
|
|
assert_eq!(next[0].potential, Flow(45));
|
|
|
|
assert_eq!(
|
|
next[1].actor,
|
|
DoubleActor(DoubleWork::Sequential(
|
|
Index(4),
|
|
Time(15),
|
|
Index(2),
|
|
Time(13)
|
|
))
|
|
);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn double_long_run() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let system = ValveSystem::build(&lines, "AA")?;
|
|
let actor = DoubleActor(DoubleWork::Sequential(Index(3), Time(5), Index(7), Time(2)));
|
|
let state = ValveState {
|
|
closed: vec![Index(1), Index(2), Index(4), Index(9)],
|
|
time: Some(Time(10)),
|
|
flow: Flow(100),
|
|
potential: Flow(79),
|
|
actor,
|
|
};
|
|
|
|
let next = state.actor.calc_next_moves(&state, &system);
|
|
let expected = vec![
|
|
DoubleActor(DoubleWork::Simultaneous(Index(1), Index(7), Time(2))),
|
|
DoubleActor(DoubleWork::Sequential(Index(2), Time(3), Index(7), Time(2))),
|
|
DoubleActor(DoubleWork::Sequential(Index(4), Time(3), Index(7), Time(2))),
|
|
DoubleActor(DoubleWork::Sequential(Index(7), Time(2), Index(9), Time(1))),
|
|
];
|
|
assert_eq!(next, expected);
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[test]
|
|
fn test_part2() -> Result<()> {
|
|
let day = Day {};
|
|
let lines = read_string(day.get_day_number(), "example01.txt")?;
|
|
let expected = ResultType::Integer(1707);
|
|
let result = day.part2(&lines)?;
|
|
assert_eq!(result, expected);
|
|
|
|
Ok(())
|
|
}
|
|
}
|