Added Solution for 2023 day 25
This commit is contained in:
parent
ef29e0a8b5
commit
cdb2c491fa
5 changed files with 1478 additions and 0 deletions
15
2023/day25_snowverload/Cargo.toml
Normal file
15
2023/day25_snowverload/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
||||||
|
[package]
|
||||||
|
name = "day25_snowverload"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
criterion = "0.5.1"
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "test_benchmark"
|
||||||
|
harness = false
|
89
2023/day25_snowverload/challenge.txt
Normal file
89
2023/day25_snowverload/challenge.txt
Normal file
|
@ -0,0 +1,89 @@
|
||||||
|
\--- Day 25: Snowverload ---
|
||||||
|
----------
|
||||||
|
|
||||||
|
*Still* somehow without snow, you go to the last place you haven't checked: the center of Snow Island, directly below the waterfall.
|
||||||
|
|
||||||
|
Here, someone has clearly been trying to fix the problem. Scattered everywhere are hundreds of weather machines, almanacs, communication modules, hoof prints, machine parts, mirrors, lenses, and so on.
|
||||||
|
|
||||||
|
Somehow, everything has been *wired together* into a massive snow-producing apparatus, but nothing seems to be running. You check a tiny screen on one of the communication modules: `Error 2023`. It doesn't say what `Error 2023` means, but it *does* have the phone number for a support line printed on it.
|
||||||
|
|
||||||
|
"Hi, you've reached Weather Machines And So On, Inc. How can I help you?" You explain the situation.
|
||||||
|
|
||||||
|
"Error 2023, you say? Why, that's a power overload error, of course! It means you have too many components plugged in. Try unplugging some components and--" You explain that there are hundreds of components here and you're in a bit of a hurry.
|
||||||
|
|
||||||
|
"Well, let's see how bad it is; do you see a *big red reset button* somewhere? It should be on its own module. If you push it, it probably won't fix anything, but it'll report how overloaded things are." After a minute or two, you find the reset button; it's so big that it takes two hands just to get enough leverage to push it. Its screen then displays:
|
||||||
|
|
||||||
|
```
|
||||||
|
SYSTEM OVERLOAD!
|
||||||
|
|
||||||
|
Connected components would require
|
||||||
|
power equal to at least 100 stars!
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
"Wait, *how* many components did you say are plugged in? With that much equipment, you could produce snow for an *entire*--" You disconnect the call.
|
||||||
|
|
||||||
|
You have nowhere near that many stars - you need to find a way to disconnect at least half of the equipment here, but it's already Christmas! You only have time to disconnect *three wires*.
|
||||||
|
|
||||||
|
Fortunately, someone left a wiring diagram (your puzzle input) that shows *how the components are connected*. For example:
|
||||||
|
|
||||||
|
```
|
||||||
|
jqt: rhn xhk nvd
|
||||||
|
rsh: frs pzl lsr
|
||||||
|
xhk: hfx
|
||||||
|
cmg: qnr nvd lhk bvb
|
||||||
|
rhn: xhk bvb hfx
|
||||||
|
bvb: xhk hfx
|
||||||
|
pzl: lsr hfx nvd
|
||||||
|
qnr: nvd
|
||||||
|
ntq: jqt hfx bvb xhk
|
||||||
|
nvd: lhk
|
||||||
|
lsr: lhk
|
||||||
|
rzs: qnr cmg lsr rsh
|
||||||
|
frs: qnr lhk lsr
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Each line shows the *name of a component*, a colon, and then *a list of other components* to which that component is connected. Connections aren't directional; `abc: xyz` and `xyz: abc` both represent the same configuration. Each connection between two components is represented only once, so some components might only ever appear on the left or right side of a colon.
|
||||||
|
|
||||||
|
In this example, if you disconnect the wire between `hfx`/`pzl`, the wire between `bvb`/`cmg`, and the wire between `nvd`/`jqt`, you will *divide the components into two separate, disconnected groups*:
|
||||||
|
|
||||||
|
* `*9*` components: `cmg`, `frs`, `lhk`, `lsr`, `nvd`, `pzl`, `qnr`, `rsh`, and `rzs`.
|
||||||
|
* `*6*` components: `bvb`, `hfx`, `jqt`, `ntq`, `rhn`, and `xhk`.
|
||||||
|
|
||||||
|
Multiplying the sizes of these groups together produces `*54*`.
|
||||||
|
|
||||||
|
Find the three wires you need to disconnect in order to divide the components into two separate groups. *What do you get if you multiply the sizes of these two groups together?*
|
||||||
|
|
||||||
|
Your puzzle answer was `546804`.
|
||||||
|
|
||||||
|
\--- Part Two ---
|
||||||
|
----------
|
||||||
|
|
||||||
|
You climb over weather machines, under giant springs, and narrowly avoid a pile of pipes as you find and disconnect the three wires.
|
||||||
|
|
||||||
|
A moment after you disconnect the last wire, the big red reset button module makes a small ding noise:
|
||||||
|
|
||||||
|
```
|
||||||
|
System overload resolved!
|
||||||
|
Power required is now 50 stars.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
Out of the corner of your eye, you notice goggles and a loose-fitting hard hat peeking at you from behind an ultra crucible. You think you see a faint glow, but before you can investigate, you hear another small ding:
|
||||||
|
|
||||||
|
```
|
||||||
|
Power required is now 49 stars.
|
||||||
|
|
||||||
|
Please supply the necessary stars and
|
||||||
|
push the button to restart the system.
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
If you like, you can .
|
||||||
|
|
||||||
|
Both parts of this puzzle are complete! They provide two gold stars: \*\*
|
||||||
|
|
||||||
|
At this point, all that is left is for you to [admire your Advent calendar](/2023).
|
||||||
|
|
||||||
|
If you still want to see it, you can [get your puzzle input](25/input).
|
159
2023/day25_snowverload/src/lib.rs
Normal file
159
2023/day25_snowverload/src/lib.rs
Normal file
|
@ -0,0 +1,159 @@
|
||||||
|
use core::fmt::Display;
|
||||||
|
use std::collections::{HashMap, VecDeque};
|
||||||
|
|
||||||
|
const MAX_DISCONNECTS: usize = 3;
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
|
pub enum GraphError<'a> {
|
||||||
|
LineMalformed(&'a str),
|
||||||
|
NoDisconnection,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Display for GraphError<'_> {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::LineMalformed(v) => write!(f, "Line must consist of at least two components, separated by \": \": {v}"),
|
||||||
|
Self::NoDisconnection => write!(f, "Unable to find a way to disconnect this network"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn run(input: &str) -> Result<usize, GraphError> {
|
||||||
|
let graph = try_parse_network(input)?;
|
||||||
|
try_separate(&graph).map_err(|_| GraphError::NoDisconnection)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_parse_network(input: &str) -> Result<Vec<Vec<usize>>, GraphError> {
|
||||||
|
let mut res = Vec::new();
|
||||||
|
let mut ids = HashMap::new();
|
||||||
|
for line in input.lines() {
|
||||||
|
let words: Vec<_> = line.split([':', ' ']).collect();
|
||||||
|
if words.len() < 3 {
|
||||||
|
return Err(GraphError::LineMalformed(line));
|
||||||
|
}
|
||||||
|
let name = words[0];
|
||||||
|
let lhs = *ids.entry(name).or_insert_with(|| {res.push(Vec::new()); res.len()-1 });
|
||||||
|
words.iter().skip(2).for_each(|name| {
|
||||||
|
let rhs = *ids.entry(name).or_insert_with(|| {res.push(Vec::new()); res.len()-1 });
|
||||||
|
res[lhs].push(rhs);
|
||||||
|
res[rhs].push(lhs);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn try_separate(graph: &[Vec<usize>]) -> Result<usize, ()> {
|
||||||
|
// Find nodes that can't be disconnected because there are more connections between them than
|
||||||
|
// we are allowed to cut.
|
||||||
|
let mut strongly_connected = vec![Vec::new(); graph.len()];
|
||||||
|
graph.iter().enumerate().for_each(|(lhs, conn)| {
|
||||||
|
conn.iter().cloned().for_each(|rhs| {
|
||||||
|
// max_len is a tradeoff betweeen the runtime of this loop vs. the large one below. 11
|
||||||
|
// is benchmarked to be the best for my input. This eliminates 3209 out of the 3310
|
||||||
|
// total connections.
|
||||||
|
if lhs < rhs && is_strongly_connected(graph, lhs, rhs, 11) {
|
||||||
|
strongly_connected[lhs].push(rhs);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Try cutting everything that remains and see what sticks
|
||||||
|
for (first_lhs, conn) in graph.iter().enumerate() {
|
||||||
|
for first_rhs in conn.iter().cloned().filter(|&rhs| rhs > first_lhs && !strongly_connected[first_lhs].contains(&rhs)) {
|
||||||
|
for (second_lhs, conn) in graph.iter().enumerate().skip(first_lhs) {
|
||||||
|
for second_rhs in conn.iter().cloned().filter(|&rhs| rhs > second_lhs && !strongly_connected[second_lhs].contains(&rhs) && (first_lhs, first_rhs) != (second_lhs, rhs)) {
|
||||||
|
for (third_lhs, conn) in graph.iter().enumerate().skip(second_lhs) {
|
||||||
|
for third_rhs in conn.iter().cloned().filter(|&rhs|
|
||||||
|
rhs > third_lhs &&
|
||||||
|
!strongly_connected[third_lhs].contains(&rhs) &&
|
||||||
|
![(first_lhs, first_rhs), (second_lhs, second_rhs)].contains(&(third_lhs, rhs))) {
|
||||||
|
let unaffected_idx = (0..).find(|idx| ![first_lhs, first_rhs, second_lhs, second_rhs, third_lhs, third_rhs].contains(idx)).unwrap();
|
||||||
|
let mut new = graph.to_vec();
|
||||||
|
new[first_lhs] = new[first_lhs].iter().cloned().filter(|rhs| *rhs != first_rhs).collect();
|
||||||
|
new[first_rhs] = new[first_rhs].iter().cloned().filter(|rhs| *rhs != first_lhs).collect();
|
||||||
|
new[second_lhs] = new[second_lhs].iter().cloned().filter(|rhs| *rhs != second_rhs).collect();
|
||||||
|
new[second_rhs] = new[second_rhs].iter().cloned().filter(|rhs| *rhs != second_lhs).collect();
|
||||||
|
new[third_lhs] = new[third_lhs].iter().cloned().filter(|rhs| *rhs != third_rhs).collect();
|
||||||
|
new[third_rhs] = new[third_rhs].iter().cloned().filter(|rhs| *rhs != third_lhs).collect();
|
||||||
|
let size = flood_fill(&new, unaffected_idx);
|
||||||
|
if size < graph.len()-MAX_DISCONNECTS {
|
||||||
|
return Ok(size*(graph.len()-size));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_strongly_connected(graph: &[Vec<usize>], start: usize, dest: usize, max_len: usize) -> bool {
|
||||||
|
let mut used = vec![Vec::new(); graph.len()];
|
||||||
|
let mut found = 0;
|
||||||
|
let mut open_set = VecDeque::from([Vec::from([start])]);
|
||||||
|
let stop: Vec<_> = graph[start].iter().cloned().chain(std::iter::once(start)).collect();
|
||||||
|
while let Some(path) = open_set.pop_front() {
|
||||||
|
let curr = *path.last().unwrap();
|
||||||
|
if curr == dest {
|
||||||
|
if found == MAX_DISCONNECTS {
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
path.windows(2).for_each(|w| {
|
||||||
|
used[w[0]].push(w[1]);
|
||||||
|
used[w[1]].push(w[0]);
|
||||||
|
// discard any remaining paths that share any connection with this one, since
|
||||||
|
// they wouldn't really be redundant to it.
|
||||||
|
open_set.iter_mut().filter(|p| p.windows(2).any(|pw| pw == w || pw[0] == w[1] && pw[1] == w[0])).for_each(|p| *p = stop.to_vec());
|
||||||
|
});
|
||||||
|
found += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if path.len() == max_len {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
graph[curr].iter().for_each(|next| {
|
||||||
|
if !path.contains(next) && !used[curr].contains(next) {
|
||||||
|
open_set.push_back(path.iter().cloned().chain(std::iter::once(*next)).collect());
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flood_fill(graph: &[Vec<usize>], starting_idx: usize) -> usize {
|
||||||
|
let mut reachable = vec![false; graph.len()];
|
||||||
|
reachable[starting_idx] = true;
|
||||||
|
let mut open_set = Vec::from([starting_idx]);
|
||||||
|
while let Some(curr) = open_set.pop() {
|
||||||
|
graph[curr].iter().for_each(|neighbour| {
|
||||||
|
if !reachable[*neighbour] {
|
||||||
|
reachable[*neighbour] = true;
|
||||||
|
open_set.push(*neighbour);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
reachable.iter().filter(|n| **n).count()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::fs::read_to_string;
|
||||||
|
|
||||||
|
fn read_file(name: &str) -> String {
|
||||||
|
read_to_string(name).expect(&format!("Unable to read file: {name}")[..]).trim().to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_sample() {
|
||||||
|
let sample_input = read_file("tests/sample_input");
|
||||||
|
assert_eq!(run(&sample_input), Ok(54));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_challenge() {
|
||||||
|
let challenge_input = read_file("tests/challenge_input");
|
||||||
|
assert_eq!(run(&challenge_input), Ok(546804));
|
||||||
|
}
|
||||||
|
}
|
1202
2023/day25_snowverload/tests/challenge_input
Normal file
1202
2023/day25_snowverload/tests/challenge_input
Normal file
File diff suppressed because it is too large
Load diff
13
2023/day25_snowverload/tests/sample_input
Normal file
13
2023/day25_snowverload/tests/sample_input
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
jqt: rhn xhk nvd
|
||||||
|
rsh: frs pzl lsr
|
||||||
|
xhk: hfx
|
||||||
|
cmg: qnr nvd lhk bvb
|
||||||
|
rhn: xhk bvb hfx
|
||||||
|
bvb: xhk hfx
|
||||||
|
pzl: lsr hfx nvd
|
||||||
|
qnr: nvd
|
||||||
|
ntq: jqt hfx bvb xhk
|
||||||
|
nvd: lhk
|
||||||
|
lsr: lhk
|
||||||
|
rzs: qnr cmg lsr rsh
|
||||||
|
frs: qnr lhk lsr
|
Loading…
Add table
Add a link
Reference in a new issue