2022 Day 16 Part 1
This commit is contained in:
parent
178e2e5011
commit
6c645a607d
1 changed files with 361 additions and 0 deletions
361
2022/src/bin/day16.rs
Normal file
361
2022/src/bin/day16.rs
Normal file
|
|
@ -0,0 +1,361 @@
|
||||||
|
use std::{
|
||||||
|
collections::{BinaryHeap, HashMap, HashSet},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
|
use regex::Regex;
|
||||||
|
|
||||||
|
const INPUT: &str = include_str!("../../input/day16.txt");
|
||||||
|
|
||||||
|
fn main() {
|
||||||
|
println!("{}", part1(INPUT));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn part1(input: &str) -> u32 {
|
||||||
|
let graph = Graph::parse(input);
|
||||||
|
|
||||||
|
graph.max_flow(30)
|
||||||
|
}
|
||||||
|
|
||||||
|
// I really want this to be Copy, so here's a hack to avoid strings.
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
struct Valve(u64);
|
||||||
|
|
||||||
|
impl FromStr for Valve {
|
||||||
|
type Err = String;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s.len() != 2 {
|
||||||
|
return Err(s.to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
let u = s
|
||||||
|
.chars()
|
||||||
|
.rev()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, c)| (c as u64) << (8 * i))
|
||||||
|
.sum();
|
||||||
|
|
||||||
|
Ok(Self(u))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&str> for Valve {
|
||||||
|
fn from(value: &str) -> Self {
|
||||||
|
Self::from_str(value).unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Valve {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
let u = self.0;
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"{}{}",
|
||||||
|
char::from_u32((u >> 8).try_into().unwrap()).unwrap(),
|
||||||
|
char::from_u32((u & 0xff).try_into().unwrap()).unwrap()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Flow = u32;
|
||||||
|
type Pressure = u32;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct Graph {
|
||||||
|
valves: HashMap<Valve, Pressure>,
|
||||||
|
tunnels: HashMap<Valve, Vec<Valve>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Graph {
|
||||||
|
fn parse(text: &str) -> Self {
|
||||||
|
let mut valves = HashMap::new();
|
||||||
|
let mut tunnels: HashMap<Valve, Vec<Valve>> = HashMap::new();
|
||||||
|
|
||||||
|
let re = Regex::new(r"^Valve (.*) has flow rate=(.*); tunnels? leads? to valves? (.*)$")
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
for line in aoc::lines(text) {
|
||||||
|
let caps = re.captures(line).unwrap();
|
||||||
|
|
||||||
|
let valve = Valve::from_str(&caps[1]).unwrap();
|
||||||
|
let flow_rate: u32 = caps[2].parse().unwrap();
|
||||||
|
let neighbors: Vec<Valve> =
|
||||||
|
caps[3].split(", ").map(|s| s.try_into().unwrap()).collect();
|
||||||
|
|
||||||
|
valves.insert(valve, flow_rate);
|
||||||
|
for n in neighbors {
|
||||||
|
tunnels
|
||||||
|
.entry(valve)
|
||||||
|
.and_modify(|dests| dests.push(n))
|
||||||
|
.or_insert_with(|| vec![n]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Self { valves, tunnels }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
enum Action {
|
||||||
|
MoveTo(Valve),
|
||||||
|
Open(Valve),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||||
|
struct Path(Vec<Action>);
|
||||||
|
|
||||||
|
impl Path {
|
||||||
|
fn current_location(&self) -> Valve {
|
||||||
|
match self.0.last() {
|
||||||
|
None => Valve::from("AA"),
|
||||||
|
Some(Action::MoveTo(v)) => *v,
|
||||||
|
Some(Action::Open(v)) => *v,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn minutes_elapsed(&self) -> usize {
|
||||||
|
self.0.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn successors(
|
||||||
|
&self,
|
||||||
|
valves: &HashMap<Valve, Pressure>,
|
||||||
|
paths: &HashMap<(Valve, Valve), Option<ShortestPath<Valve>>>,
|
||||||
|
) -> Vec<Vec<Action>> {
|
||||||
|
let here = self.current_location();
|
||||||
|
|
||||||
|
let open_valves: HashSet<Valve> = self
|
||||||
|
.0
|
||||||
|
.iter()
|
||||||
|
.cloned()
|
||||||
|
.filter_map(|a| match a {
|
||||||
|
Action::Open(v) => Some(v),
|
||||||
|
Action::MoveTo(_) => None,
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let mut suffixes = vec![];
|
||||||
|
|
||||||
|
for (&dest, &pressure) in valves {
|
||||||
|
if pressure == 0 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if open_valves.contains(&dest) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let Some(ref sp) = paths[&(here, dest)] else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut actions = vec![];
|
||||||
|
for &node in &sp.nodes[1..] {
|
||||||
|
// skip self start
|
||||||
|
actions.push(Action::MoveTo(node));
|
||||||
|
}
|
||||||
|
actions.push(Action::Open(dest));
|
||||||
|
|
||||||
|
suffixes.push(actions);
|
||||||
|
}
|
||||||
|
|
||||||
|
suffixes
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flow(&self, valves: &HashMap<Valve, Pressure>, total_minutes: usize) -> Flow {
|
||||||
|
self.0
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, a)| {
|
||||||
|
let pressure = match a {
|
||||||
|
Action::MoveTo(_) => 0,
|
||||||
|
Action::Open(ref v) => valves[v],
|
||||||
|
};
|
||||||
|
let minutes_left = total_minutes - i - 1;
|
||||||
|
pressure * (minutes_left as u32)
|
||||||
|
})
|
||||||
|
.sum()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
struct State {
|
||||||
|
path: Path,
|
||||||
|
flow: Flow,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PartialOrd for State {
|
||||||
|
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||||
|
Some(self.cmp(other))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Ord for State {
|
||||||
|
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||||
|
// BinaryHeap is a **MAX-HEAP**. To get a min-heap, we run the comparisons backwards.
|
||||||
|
|
||||||
|
let self_minutes = self.path.minutes_elapsed();
|
||||||
|
let other_minutes = other.path.minutes_elapsed();
|
||||||
|
|
||||||
|
// Sort more remaining time first...
|
||||||
|
let cmp_minutes = self_minutes.cmp(&other_minutes);
|
||||||
|
|
||||||
|
// ... and break ties by higher flow.
|
||||||
|
cmp_minutes.then_with(|| other.flow.cmp(&self.flow))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Graph {
|
||||||
|
fn max_flow(&self, total_minutes: usize) -> Flow {
|
||||||
|
let paths = {
|
||||||
|
let nodes: Vec<Valve> = self.valves.keys().cloned().collect();
|
||||||
|
let mut edges: HashMap<(Valve, Valve), u64> = HashMap::new();
|
||||||
|
for (&from, tos) in &self.tunnels {
|
||||||
|
for &to in tos {
|
||||||
|
edges.insert((from, to), 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
all_pairs_shortest_paths(nodes, edges)
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut queue: BinaryHeap<State> = BinaryHeap::new();
|
||||||
|
let mut expanded: HashSet<Valve> = HashSet::new();
|
||||||
|
|
||||||
|
let start = State {
|
||||||
|
path: Path(vec![]),
|
||||||
|
flow: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
queue.push(start.clone());
|
||||||
|
|
||||||
|
let mut best: State = start;
|
||||||
|
|
||||||
|
while let Some(state) = queue.pop() {
|
||||||
|
let here = state.path.current_location();
|
||||||
|
expanded.insert(here);
|
||||||
|
|
||||||
|
if state.flow > best.flow {
|
||||||
|
best = state.clone();
|
||||||
|
}
|
||||||
|
|
||||||
|
for actions in state.path.successors(&self.valves, &paths) {
|
||||||
|
let path = {
|
||||||
|
let mut aa = state.path.0.clone();
|
||||||
|
aa.extend(actions);
|
||||||
|
Path(aa)
|
||||||
|
};
|
||||||
|
|
||||||
|
if path.minutes_elapsed() > total_minutes {
|
||||||
|
// 🌋🌋🌋
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let flow = path.flow(&self.valves, total_minutes);
|
||||||
|
|
||||||
|
queue.push(State { path, flow });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
best.flow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
struct ShortestPath<V> {
|
||||||
|
nodes: Vec<V>,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn all_pairs_shortest_paths<V>(
|
||||||
|
nodes: Vec<V>,
|
||||||
|
edges: HashMap<(V, V), u64>,
|
||||||
|
) -> HashMap<(V, V), Option<ShortestPath<V>>>
|
||||||
|
where
|
||||||
|
V: Eq + std::hash::Hash + Copy + core::fmt::Debug,
|
||||||
|
{
|
||||||
|
let mut dist: HashMap<(V, V), u64> = HashMap::new();
|
||||||
|
let mut next: HashMap<(V, V), V> = HashMap::new();
|
||||||
|
|
||||||
|
for (&(u, v), &w) in &edges {
|
||||||
|
dist.insert((u, v), w);
|
||||||
|
next.insert((u, v), v);
|
||||||
|
next.insert((v, u), u);
|
||||||
|
}
|
||||||
|
for &v in &nodes {
|
||||||
|
dist.insert((v, v), 0);
|
||||||
|
next.insert((v, v), v);
|
||||||
|
}
|
||||||
|
|
||||||
|
for &k in &nodes {
|
||||||
|
for &i in &nodes {
|
||||||
|
for &j in &nodes {
|
||||||
|
let Some(d_ik) = dist.get(&(i,k)) else { continue };
|
||||||
|
let Some(d_kj) = dist.get(&(k,j)) else { continue };
|
||||||
|
let d_ikj = d_ik + d_kj;
|
||||||
|
|
||||||
|
let Some(&d_ij) = dist.get(&(i,j)) else {
|
||||||
|
dist.insert((i, j) , d_ikj);
|
||||||
|
next.insert((i, j) , next[&(i,k)]);
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
if d_ij > d_ikj {
|
||||||
|
dist.insert((i, j), d_ikj);
|
||||||
|
next.insert((i, j), next[&(i, k)]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut paths: HashMap<(V, V), Option<ShortestPath<V>>> = HashMap::new();
|
||||||
|
|
||||||
|
let find_path = |mut u: V, v: V| -> Option<Vec<V>> {
|
||||||
|
let mut path = vec![u];
|
||||||
|
|
||||||
|
while u != v {
|
||||||
|
let Some(&k) = next.get(&(u, v)) else {
|
||||||
|
return None
|
||||||
|
};
|
||||||
|
|
||||||
|
u = k;
|
||||||
|
path.push(u);
|
||||||
|
}
|
||||||
|
Some(path)
|
||||||
|
};
|
||||||
|
|
||||||
|
for &i in &nodes {
|
||||||
|
for &j in &nodes {
|
||||||
|
let Some(nodes) = find_path(i, j) else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
paths.insert((i, j), Some(ShortestPath { nodes }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
paths
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn heap_order() {
|
||||||
|
let mut queue: BinaryHeap<State> = BinaryHeap::new();
|
||||||
|
|
||||||
|
let worse = State {
|
||||||
|
path: Path(vec![Action::MoveTo("DD".into()), Action::Open("DD".into())]),
|
||||||
|
flow: 560,
|
||||||
|
};
|
||||||
|
let better = State {
|
||||||
|
path: Path(vec![
|
||||||
|
Action::MoveTo("DD".into()),
|
||||||
|
Action::MoveTo("BB".into()),
|
||||||
|
]),
|
||||||
|
flow: 0,
|
||||||
|
};
|
||||||
|
queue.push(worse);
|
||||||
|
queue.push(better.clone());
|
||||||
|
|
||||||
|
assert_eq!(better, queue.pop().unwrap());
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue