Added Solution for 2023 day 25

This commit is contained in:
Burnus 2023-12-29 06:36:13 +01:00
parent ef29e0a8b5
commit cdb2c491fa
5 changed files with 1478 additions and 0 deletions

View 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

View 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).

View 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));
}
}

File diff suppressed because it is too large Load diff

View 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