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