Day 16, part 2

This commit is contained in:
jazzpi 2022-12-21 21:44:12 +01:00
parent 49368e7985
commit a0fa5256e9
3 changed files with 231 additions and 48 deletions

View File

@ -3,15 +3,17 @@ use aoc22::{
util::{self, BnBState},
};
const TOTAL_TIME: usize = 30;
pub fn main() {
let valves = day16::parse_valves(&util::parse_input());
let paths = day16::calc_paths(&valves);
let state = day16::State::new(&valves, false);
let state = day16::State::new(&valves, TOTAL_TIME, 1);
let score = util::maximize(&state, &(&valves, &paths));
let state = util::maximize(&state, &(&valves, &paths));
println!(
"Most pressure released is {}",
score.lower_bound(&(&valves, &paths))
state.lower_bound(&(&valves, &paths))
);
}

20
src/bin/d16p2.rs Normal file
View File

@ -0,0 +1,20 @@
use aoc22::{
day16,
util::{self, BnBState},
};
const TOTAL_TIME: usize = 26;
const N_AGENTS: usize = 2;
pub fn main() {
let valves = day16::parse_valves(&util::parse_input());
let paths = day16::calc_paths(&valves);
let state = day16::State::new(&valves, TOTAL_TIME, N_AGENTS);
let state = util::maximize(&state, &(&valves, &paths));
println!(
"Most pressure released is {}",
state.lower_bound(&(&valves, &paths))
);
}

View File

@ -1,17 +1,13 @@
use std::{
collections::{HashMap, HashSet, VecDeque},
hash::Hash,
sync::Mutex,
};
use itertools::Itertools;
use lazy_static::lazy_static;
use regex::Regex;
use crate::util;
const TOTAL_TIME: usize = 30;
#[derive(Debug, Clone)]
pub struct Valve {
pub flow_rate: usize,
@ -98,20 +94,28 @@ pub fn calc_paths(valves: &Vec<Valve>) -> Vec<Vec<Path>> {
result
}
#[derive(Debug, Clone, Hash, PartialEq)]
pub enum Goal {
Idle,
NothingReachable,
Opening(usize),
}
#[derive(Debug, Clone)]
pub struct State {
pub opened: u64,
pub location: usize,
pub locations: Vec<usize>,
pub goals: Vec<Goal>,
pub flow_rate: usize,
pub time_remaining: usize,
pub pressure_released: usize,
pub history: Vec<Vec<usize>>,
interesting: HashSet<usize>,
history: Vec<usize>,
}
impl State {
pub fn new(valves: &Vec<Valve>) -> State {
pub fn new(valves: &Vec<Valve>, initial_time: usize, agents: usize) -> State {
let interesting = valves
.iter()
.enumerate()
@ -119,12 +123,146 @@ impl State {
.collect();
State {
opened: 0,
location: 0, // "AA" has to be the first valve alphabetically
locations: vec![0; agents],
goals: vec![Goal::Idle; agents],
flow_rate: 0,
time_remaining: TOTAL_TIME,
time_remaining: initial_time,
pressure_released: 0,
interesting,
history: vec![0],
history: vec![vec![0]; agents],
}
}
fn find_goals(&self, (_, paths): &ExtraArgs) -> Vec<State> {
let target_dists: HashMap<usize, usize> = self
.goals
.iter()
.enumerate()
.filter_map(|(i, g)| {
if let Goal::Opening(t) = g {
Some((*t, paths[self.locations[i]][*t].dist))
} else {
None
}
})
.collect();
let new_goals: HashMap<usize, Vec<usize>> = self
.goals
.iter()
.positions(|g| matches!(g, Goal::Idle))
.map(|i| {
let loc = self.locations[i];
(
i,
self.interesting
.iter()
.filter_map(|target| {
let agent_dist = paths[loc][*target].dist;
if agent_dist + 2 >= self.time_remaining {
// Can't reach + open the valve as well as
// letting anything flow through
return None;
}
if let Some(dist) = target_dists.get(target) {
if *dist > agent_dist {
// Someone else is targeting this, but they're slower
Some(*target)
} else {
// Someone else is targeting this, and they're faster
None
}
} else {
// Noone else is targeting this
Some(*target)
}
})
.collect_vec(),
)
})
.collect();
if new_goals.len() == 1 {
let (i, targets) = new_goals.iter().next().unwrap();
if targets.is_empty() {
let mut state = self.clone();
state.goals[*i] = Goal::NothingReachable;
return vec![state];
}
targets
.iter()
.map(|t| {
let mut state = self.clone();
state.goals[*i] = Goal::Opening(*t);
state
})
.collect()
} else if new_goals.len() == 2 {
let mut it = new_goals.iter();
let (i1, targets1) = it.next().unwrap();
let (i2, targets2) = it.next().unwrap();
if targets1.len() == 0 {
if targets2.len() == 0 {
let mut state = self.clone();
state.goals[*i1] = Goal::NothingReachable;
state.goals[*i2] = Goal::NothingReachable;
return vec![state];
}
return targets2
.iter()
.map(|t| {
let mut state = self.clone();
state.goals[*i1] = Goal::NothingReachable;
state.goals[*i2] = Goal::Opening(*t);
state
})
.collect();
} else if targets2.len() == 0 {
return targets1
.iter()
.map(|t| {
let mut state = self.clone();
state.goals[*i1] = Goal::Opening(*t);
state.goals[*i2] = Goal::NothingReachable;
state
})
.collect();
} else if targets1.len() == 1 && targets1 == targets2 {
// Both agents can only reach one and the same goal
let mut state = self.clone();
let target = targets1[0];
if paths[self.locations[*i1]][target].dist < paths[self.locations[*i2]][target].dist
{
state.goals[*i1] = Goal::Opening(target);
state.goals[*i2] = Goal::NothingReachable;
} else {
state.goals[*i1] = Goal::NothingReachable;
state.goals[*i2] = Goal::Opening(target);
}
return vec![state];
}
let mut result = Vec::new();
for target1 in targets1 {
for target2 in targets2 {
if target1 == target2 {
continue;
}
let mut state = self.clone();
state.goals[*i1] = Goal::Opening(*target1);
state.goals[*i2] = Goal::Opening(*target2);
result.push(state);
}
}
result
} else {
unimplemented!("Can't find goals for >2 agents at the same time");
}
}
}
@ -134,45 +272,61 @@ impl util::BnBState<ExtraArgs<'_>> for State {
fn possible_actions(&self, (valves, paths): &ExtraArgs) -> Vec<State> {
assert!(!self.finished());
let mut result = Vec::new();
let loc = self.location;
if (self.opened & (1 << loc)) == 0 && self.interesting.contains(&loc) {
let mut open = self.clone();
open.opened |= 1 << loc;
// First increase pressure released, then the flow rate
open.pressure_released += open.flow_rate;
open.flow_rate += valves[loc].flow_rate;
open.time_remaining -= 1;
open.interesting.remove(&loc);
result.push(open);
let goalless = self
.goals
.iter()
.enumerate()
.filter(|(_, g)| matches!(g, Goal::Idle))
.collect_vec();
if goalless.len() > 0 {
return self.find_goals(&(valves, paths));
}
for l in &self.interesting {
let i = l.clone();
let dist = paths[loc][i].dist;
if dist == 0 || dist == usize::MAX || self.time_remaining < dist {
continue;
let min_goal_dist = self
.goals
.iter()
.enumerate()
.filter_map(|(i, g)| {
if let Goal::Opening(target) = g {
Some(paths[self.locations[i]][*target].dist)
} else {
None
}
})
.min()
.unwrap();
let time_spent = min_goal_dist + 1;
let mut completed = self.clone();
completed.pressure_released += completed.flow_rate * time_spent;
completed.time_remaining -= time_spent;
for (i, goal) in self.goals.iter().enumerate() {
if let Goal::Opening(goal) = goal {
let path = &paths[self.locations[i]][*goal];
if path.dist == min_goal_dist {
completed.locations[i] = *goal;
completed.opened |= 1 << goal;
completed.goals[i] = Goal::Idle;
if completed.interesting.remove(goal) {
// Only increase flow rate if no other agent already
// opened this valve
completed.flow_rate += valves[*goal].flow_rate;
}
completed.history[i].push(*goal);
} else {
completed.locations[i] = path.path[time_spent - 1];
}
}
let mut moved = self.clone();
moved.location = i;
moved.pressure_released += moved.flow_rate * dist;
moved.time_remaining -= dist;
moved.history.push(i);
result.push(moved);
}
let mut do_nothing = self.clone();
do_nothing.pressure_released += do_nothing.flow_rate * self.time_remaining;
do_nothing.time_remaining = 0;
result.push(do_nothing);
result
return vec![completed];
}
fn finished(&self) -> bool {
self.time_remaining == 0 || self.interesting.is_empty()
self.time_remaining == 0
|| self
.goals
.iter()
.all(|g| matches!(g, Goal::NothingReachable)) //self.interesting.is_empty()
}
fn lower_bound(&self, _: &ExtraArgs) -> usize {
@ -186,7 +340,12 @@ impl util::BnBState<ExtraArgs<'_>> for State {
if (self.opened & (1 << i)) != 0 {
continue;
}
let dist = paths[self.location][i].dist;
let dist = self
.locations
.iter()
.map(|loc| paths[*loc][i].dist)
.min()
.unwrap();
if dist < self.time_remaining {
let flow = valves[i].flow_rate;
additional_flow += flow * (self.time_remaining - dist - 1);
@ -200,7 +359,8 @@ impl util::BnBState<ExtraArgs<'_>> for State {
impl Hash for State {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.opened.hash(state);
self.location.hash(state);
self.locations.hash(state);
self.goals.hash(state);
self.time_remaining.hash(state);
self.pressure_released.hash(state);
}
@ -209,7 +369,8 @@ impl Hash for State {
impl PartialEq for State {
fn eq(&self, other: &Self) -> bool {
self.opened == other.opened
&& self.location == other.location
&& self.locations == other.locations
&& self.goals == other.goals
&& self.time_remaining == other.time_remaining
&& self.pressure_released == other.pressure_released
}