Skip to content

Commit e7ff9ca

Browse files
committed
Day 16 (part 1)
1 parent 8b37ed7 commit e7ff9ca

File tree

2 files changed

+321
-0
lines changed

2 files changed

+321
-0
lines changed

input/day16.txt

+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
Valve HM has flow rate=0; tunnels lead to valves LS, YS
2+
Valve IY has flow rate=15; tunnels lead to valves YI, MU, KN, QS, QM
3+
Valve VI has flow rate=22; tunnels lead to valves LE, SE, RB, JR
4+
Valve SE has flow rate=0; tunnels lead to valves VI, AZ
5+
Valve QU has flow rate=0; tunnels lead to valves YC, QK
6+
Valve RB has flow rate=0; tunnels lead to valves AN, VI
7+
Valve PU has flow rate=0; tunnels lead to valves JR, IM
8+
Valve OA has flow rate=0; tunnels lead to valves KZ, FR
9+
Valve AQ has flow rate=23; tunnels lead to valves FA, QM, GE
10+
Valve QS has flow rate=0; tunnels lead to valves IM, IY
11+
Valve HC has flow rate=24; tunnel leads to valve XH
12+
Valve QI has flow rate=0; tunnels lead to valves KQ, LS
13+
Valve FA has flow rate=0; tunnels lead to valves HA, AQ
14+
Valve BA has flow rate=0; tunnels lead to valves KZ, ME
15+
Valve DH has flow rate=0; tunnels lead to valves LT, HA
16+
Valve TE has flow rate=0; tunnels lead to valves AA, ZJ
17+
Valve AA has flow rate=0; tunnels lead to valves YS, XT, TE, GY, FS
18+
Valve YC has flow rate=9; tunnels lead to valves DV, XH, DJ, QU
19+
Valve KN has flow rate=0; tunnels lead to valves IY, AZ
20+
Valve GS has flow rate=0; tunnels lead to valves FS, KZ
21+
Valve DJ has flow rate=0; tunnels lead to valves YC, UV
22+
Valve GY has flow rate=0; tunnels lead to valves QK, AA
23+
Valve ZJ has flow rate=6; tunnels lead to valves RC, HS, UV, ME, TE
24+
Valve RC has flow rate=0; tunnels lead to valves BY, ZJ
25+
Valve QK has flow rate=10; tunnels lead to valves QU, XX, HS, RM, GY
26+
Valve AN has flow rate=0; tunnels lead to valves HA, RB
27+
Valve XT has flow rate=0; tunnels lead to valves AA, KQ
28+
Valve LT has flow rate=0; tunnels lead to valves IM, DH
29+
Valve YI has flow rate=0; tunnels lead to valves LE, IY
30+
Valve BK has flow rate=0; tunnels lead to valves LS, RM
31+
Valve LE has flow rate=0; tunnels lead to valves VI, YI
32+
Valve IM has flow rate=19; tunnels lead to valves PU, EC, QS, LT
33+
Valve SK has flow rate=0; tunnels lead to valves RF, AZ
34+
Valve RM has flow rate=0; tunnels lead to valves QK, BK
35+
Valve YM has flow rate=0; tunnels lead to valves LS, KZ
36+
Valve DV has flow rate=0; tunnels lead to valves YC, AI
37+
Valve QM has flow rate=0; tunnels lead to valves IY, AQ
38+
Valve KZ has flow rate=5; tunnels lead to valves BA, GS, YM, OA, XX
39+
Valve FS has flow rate=0; tunnels lead to valves GS, AA
40+
Valve UV has flow rate=0; tunnels lead to valves DJ, ZJ
41+
Valve AZ has flow rate=20; tunnels lead to valves SE, KN, SK, MS
42+
Valve BY has flow rate=0; tunnels lead to valves RC, LS
43+
Valve OY has flow rate=0; tunnels lead to valves KQ, EI
44+
Valve XX has flow rate=0; tunnels lead to valves KZ, QK
45+
Valve ME has flow rate=0; tunnels lead to valves BA, ZJ
46+
Valve YS has flow rate=0; tunnels lead to valves AA, HM
47+
Valve MS has flow rate=0; tunnels lead to valves AZ, HA
48+
Valve HS has flow rate=0; tunnels lead to valves QK, ZJ
49+
Valve LS has flow rate=3; tunnels lead to valves BK, HM, QI, BY, YM
50+
Valve KQ has flow rate=17; tunnels lead to valves OY, XT, QI
51+
Valve MU has flow rate=0; tunnels lead to valves IY, HA
52+
Valve EC has flow rate=0; tunnels lead to valves IM, GE
53+
Valve XH has flow rate=0; tunnels lead to valves HC, YC
54+
Valve JR has flow rate=0; tunnels lead to valves PU, VI
55+
Valve EI has flow rate=0; tunnels lead to valves OY, RF
56+
Valve AI has flow rate=25; tunnel leads to valve DV
57+
Valve GE has flow rate=0; tunnels lead to valves AQ, EC
58+
Valve RF has flow rate=18; tunnels lead to valves EI, FR, SK
59+
Valve FR has flow rate=0; tunnels lead to valves OA, RF
60+
Valve HA has flow rate=12; tunnels lead to valves AN, FA, MU, MS, DH

src/bin/day16.rs

+261
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,261 @@
1+
use std::{
2+
cmp::{Ordering, Reverse},
3+
collections::{BinaryHeap, HashMap, HashSet, VecDeque},
4+
fmt::Display,
5+
fs::File,
6+
io::{BufRead, BufReader},
7+
str::FromStr,
8+
};
9+
10+
use scanf::sscanf;
11+
12+
const TIMESPAN: usize = 30;
13+
const INITIAL_VALVE: &str = "AA";
14+
15+
#[derive(Debug)]
16+
struct Valve {
17+
name: String,
18+
flow_rate: usize,
19+
neighbors: Vec<String>,
20+
}
21+
22+
impl FromStr for Valve {
23+
type Err = &'static str;
24+
fn from_str(s: &str) -> Result<Self, Self::Err> {
25+
let (first, second) = s.split_once("; ").ok_or("no separator ;")?;
26+
27+
let mut name = String::new();
28+
let mut flow_rate: usize = 0;
29+
sscanf!(first, "Valve {} has flow rate={}", name, flow_rate)
30+
.map_err(|_| "cannot parse first part")?;
31+
32+
let second = second
33+
.trim_start_matches("tunnel leads to valve ")
34+
.trim_start_matches("tunnels lead to valves ");
35+
36+
let out_edges: Vec<String> = second.split(", ").map(|s| s.to_string()).collect();
37+
38+
Ok(Self {
39+
name,
40+
flow_rate,
41+
neighbors: out_edges,
42+
})
43+
}
44+
}
45+
46+
#[derive(Debug, Clone)]
47+
struct SearchNode {
48+
t: usize, // time
49+
valve: String, // current valve
50+
open_valves: Vec<String>, // valves currently open
51+
path_gain: usize,
52+
}
53+
54+
impl SearchNode {
55+
fn initial(valve: &Valve) -> Self {
56+
Self {
57+
t: 0,
58+
valve: valve.name.clone(),
59+
open_valves: Vec::new(),
60+
path_gain: 0,
61+
}
62+
}
63+
64+
fn heuristic(&self, sorted_valves: &[(String, usize)]) -> usize {
65+
sorted_valves
66+
.iter()
67+
.filter(|(n, _)| !self.open_valves.contains(n))
68+
.enumerate()
69+
.map(|(i, (_, rate))| {
70+
let t = self.t + 2 * (i + 1);
71+
if t > TIMESPAN {
72+
0
73+
} else {
74+
rate * (TIMESPAN - t)
75+
}
76+
})
77+
.sum()
78+
}
79+
80+
fn evaluate(&self, sorted_valves: &[(String, usize)]) -> usize {
81+
self.path_gain + self.heuristic(sorted_valves)
82+
}
83+
84+
fn expand(
85+
&self,
86+
valves: &HashMap<String, Valve>,
87+
routes: &HashMap<String, HashMap<String, (usize, String)>>,
88+
) -> Vec<SearchNode> {
89+
let mut children: Vec<SearchNode> = Vec::new();
90+
if self.t >= TIMESPAN {
91+
return children;
92+
}
93+
94+
let valve = valves.get(&self.valve).expect("valve not found");
95+
let routes = routes.get(&valve.name).expect("cannot find routes");
96+
97+
for (dest, (dist, _)) in routes.iter() {
98+
// action: go to another valve and open it
99+
if !self.open_valves.contains(dest) {
100+
let dest = valves.get(dest).expect("valve not found");
101+
if dest.flow_rate == 0 {
102+
// useless
103+
continue;
104+
}
105+
let t = self.t + dist + 1;
106+
if t > TIMESPAN {
107+
continue;
108+
}
109+
let mut open_valves = self.open_valves.clone();
110+
open_valves.push(dest.name.clone());
111+
let path_gain = self.path_gain + dest.flow_rate * (TIMESPAN - t);
112+
let next_node = SearchNode {
113+
t,
114+
valve: dest.name.clone(),
115+
open_valves,
116+
path_gain,
117+
};
118+
119+
children.push(next_node);
120+
}
121+
}
122+
children
123+
}
124+
}
125+
126+
impl Display for SearchNode {
127+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
128+
write!(
129+
f,
130+
"SearchNode(t: {}, v: {:?}, path: {:?}, pg: {})",
131+
self.t, self.valve, self.open_valves, self.path_gain
132+
)
133+
}
134+
}
135+
136+
impl PartialEq for SearchNode {
137+
fn eq(&self, other: &Self) -> bool {
138+
self.t == other.t && self.valve == other.valve && self.open_valves == other.open_valves
139+
}
140+
}
141+
142+
impl Eq for SearchNode {}
143+
144+
impl PartialOrd for SearchNode {
145+
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
146+
Some(self.cmp(other))
147+
}
148+
}
149+
150+
impl Ord for SearchNode {
151+
fn cmp(&self, other: &Self) -> Ordering {
152+
self.path_gain.cmp(&other.path_gain)
153+
}
154+
}
155+
156+
fn bfs(source: &Valve, valves: &HashMap<String, Valve>) -> HashMap<String, (usize, String)> {
157+
let mut routes: HashMap<String, (usize, String)> = HashMap::new();
158+
let mut visited: HashSet<String> = HashSet::new();
159+
let mut queue: VecDeque<String> = VecDeque::new();
160+
161+
queue.push_back(source.name.clone());
162+
visited.insert(source.name.clone());
163+
164+
while let Some(name) = queue.pop_front() {
165+
let valve = valves.get(&name).expect("cannot find valve");
166+
for n in valve.neighbors.iter() {
167+
if !visited.contains(n) {
168+
// update distance
169+
let dist = routes.get(&name).map(|x| x.0).unwrap_or(0);
170+
routes.insert(n.clone(), (dist + 1, name.clone()));
171+
// enqueue node
172+
queue.push_back(n.clone());
173+
visited.insert(n.clone());
174+
}
175+
}
176+
}
177+
178+
routes
179+
}
180+
181+
fn main() {
182+
let f = File::open("input/day16.txt").unwrap();
183+
let read = BufReader::new(f);
184+
let lines = read.lines();
185+
186+
let valves: HashMap<String, Valve> = lines
187+
.map(|l| {
188+
let valve: Valve = l.expect("cannot read line").parse().expect("cannot parse");
189+
(valve.name.clone(), valve)
190+
})
191+
.collect();
192+
let mut routes: HashMap<String, HashMap<String, (usize, String)>> = HashMap::new();
193+
194+
let mut sorted_valves: Vec<(String, usize)> = valves
195+
.values()
196+
.filter(|v| v.flow_rate > 0)
197+
.map(|v| (v.name.clone(), v.flow_rate))
198+
.collect();
199+
sorted_valves.sort_by_key(|(_, rate)| Reverse(*rate));
200+
201+
// find shortest paths from each interesting source
202+
for source in valves
203+
.values()
204+
.filter(|v| v.name == INITIAL_VALVE || v.flow_rate > 0)
205+
{
206+
let src_routes = bfs(source, &valves);
207+
routes.insert(source.name.clone(), src_routes);
208+
}
209+
210+
// show routes
211+
// for (s, rs) in routes.iter() {
212+
// println!("Routes from {}", s);
213+
// for (dest, (dist, via)) in rs.iter() {
214+
// println!("\t{}: {} via {}", dest, dist, via);
215+
// }
216+
// }
217+
218+
// search (part 1)
219+
#[allow(clippy::mutable_key_type)]
220+
let mut frontier: BinaryHeap<SearchNode> = BinaryHeap::new(); // highest path_gain first
221+
let initial_node = SearchNode::initial(
222+
valves
223+
.get(INITIAL_VALVE)
224+
.expect("cannot find initial valve"),
225+
);
226+
frontier.push(initial_node);
227+
228+
let mut max_path_gain: usize = 0;
229+
while let Some(node) = frontier.pop() {
230+
if max_path_gain >= 2112 {
231+
// println!("{}", node);
232+
}
233+
234+
// prune
235+
if node.evaluate(&sorted_valves) < max_path_gain {
236+
// println!("pruning {} {}", node, node.evaluate(&sorted_valves));
237+
continue;
238+
}
239+
240+
let children = node.expand(&valves, &routes);
241+
242+
// terminal state
243+
if children.is_empty() {
244+
if node.path_gain > max_path_gain {
245+
println!("New max {}", node.path_gain);
246+
println!("\t for {}", node);
247+
max_path_gain = node.path_gain;
248+
}
249+
continue;
250+
}
251+
252+
for child in children {
253+
if max_path_gain >= 2112 {
254+
// println!("\t{}", child.borrow_mut());
255+
}
256+
frontier.push(child);
257+
}
258+
}
259+
260+
println!("{}", max_path_gain);
261+
}

0 commit comments

Comments
 (0)