Add solution for "Day 8: Playground", part 2
This commit is contained in:
Generated
+59
-11
@@ -6,6 +6,7 @@ version = 4
|
||||
name = "advent_of_code_2025"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"grapherity",
|
||||
"grid",
|
||||
"memchr",
|
||||
"primes",
|
||||
@@ -23,16 +24,52 @@ dependencies = [
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grid"
|
||||
version = "1.0.0"
|
||||
name = "autocfg"
|
||||
version = "1.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9e2d4c0a8296178d8802098410ca05d86b17a10bb5ab559b3fb404c1f948220"
|
||||
checksum = "f2032f911046de80f0a198e0901378627c33f59ea0ac00e363d481118bd70a53"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "grapherity"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "98e207bd6e1a0c23dc4a6d87eb51070c58aa7ec3b4ef4522bab9c50a7a7fbdea"
|
||||
dependencies = [
|
||||
"typed-generational-arena",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "grid"
|
||||
version = "1.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b40ca9252762c466af32d0b1002e91e4e1bc5398f77455e55474deb466355ff5"
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.6"
|
||||
version = "2.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f52b00d39961fc5b2736ea853c9cc86238e165017a493d1d5c8eac6bdc4cc273"
|
||||
checksum = "88904434abc2901f197fe8cc55f0445e7ded921dba5911dad2e2b39b48e663c4"
|
||||
|
||||
[[package]]
|
||||
name = "nonzero_ext"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db1b4163932b207be6e3a06412aed4d84cca40dc087419f231b3a38cba2ca8e9"
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "primes"
|
||||
@@ -42,9 +79,9 @@ checksum = "0466ef49edd4a5a4bc9d62804a34e89366810bd8bfc3ed537101e3d099f245c5"
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.12.2"
|
||||
version = "1.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "843bc0191f75f3e22651ae5f1e72939ab2f72a4bc30fa80a066bd66edefc24d4"
|
||||
checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -54,9 +91,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-automata"
|
||||
version = "0.4.13"
|
||||
version = "0.4.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5276caf25ac86c8d810222b3dbb938e512c55c6831a10f3e6ed1c93b84041f1c"
|
||||
checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
@@ -65,9 +102,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex-syntax"
|
||||
version = "0.8.8"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a2d987857b319362043e95f5353c0535c1f58eec5336fdfcf626430af7def58"
|
||||
checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4"
|
||||
|
||||
[[package]]
|
||||
name = "sanitise-file-name"
|
||||
@@ -83,3 +120,14 @@ checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248"
|
||||
dependencies = [
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typed-generational-arena"
|
||||
version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "23107afb81ff70f7588dca56c1eeab4dad8b3a141b0e85d2a61fb575d1f12d62"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"nonzero_ext",
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
@@ -4,6 +4,7 @@ version = "0.1.0"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
grapherity = "0.2.1"
|
||||
grid = "1.0.0"
|
||||
memchr = "2.7.6"
|
||||
primes = "0.4.0"
|
||||
|
||||
@@ -60,6 +60,12 @@ Going row by row, for each column in the input data we count the number of diffe
|
||||
|
||||
:mag_right: Puzzle: <https://adventofcode.com/2025/day/8>, :white_check_mark: Solver: [`Playground`](src/solvers/playground.rs)
|
||||
|
||||
For part 1 we track a number of smallest distances between junction boxes up to the specified maximum in a priority queue, always dropping the highest when we find a better one. At the end, we use these to find connected circuits, and count their sizes.
|
||||
|
||||
For part 2 we instead construct a spanning tree of all junction boxes, such that the maximum distance between any pair is minimal. Once all junction boxes have been added to this graph, the pair of junction boxes with the longest distance in the spanning tree.
|
||||
|
||||
When adding any new junction box, we either take connect it via the shortest edge, if it is already longer than the current maximum in the tree. Otherwise, we connect it via the first edge shorter than the current maximum, and for additional edges shorter than the current maximum we find the longest edge on the unique path between the two vertices now already connected, remove it, and add the new shorter one. The current maximum is tracked again in a separate priority queue, and eventually gives the solution.
|
||||
|
||||
### Day 9: Movie Theater
|
||||
|
||||
:mag_right: Puzzle: <https://adventofcode.com/2025/day/9>, :white_check_mark: Solver: [`MovieTheater`](src/solvers/movie_theater.rs)
|
||||
|
||||
+124
-38
@@ -1,5 +1,8 @@
|
||||
use crate::common::geometry::Point3;
|
||||
use crate::solvers::Solver;
|
||||
use grapherity::algorithms;
|
||||
use grapherity::models::graph::{Graph, GraphEdgeMap, Vertex};
|
||||
use grapherity::prelude::*;
|
||||
use std::collections::{BTreeMap, HashSet};
|
||||
use std::io::BufRead;
|
||||
|
||||
@@ -16,57 +19,142 @@ impl Playground {
|
||||
}
|
||||
}
|
||||
|
||||
// Collects all junction box coordinates.
|
||||
// Collects all junction box coordinates in a Vec.
|
||||
fn junction_boxes<R: BufRead>(reader: R) -> Vec<Point3> {
|
||||
let mut junction_boxes = Vec::new();
|
||||
for line in reader.lines().map_while(Result::ok) {
|
||||
junction_boxes.push(line.parse::<Point3>().unwrap());
|
||||
}
|
||||
junction_boxes
|
||||
reader
|
||||
.lines()
|
||||
.map_while(Result::ok)
|
||||
.map(|s| s.parse::<Point3>().unwrap())
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Maps the closest pairs of junction boxes to their distances, up to the maximum number of
|
||||
// connections.
|
||||
fn distances<'a>(
|
||||
&self,
|
||||
junction_boxes: &'a Vec<Point3>,
|
||||
) -> BTreeMap<u64, (&'a Point3, &'a Point3)> {
|
||||
let mut distances = BTreeMap::new();
|
||||
for i in 0..junction_boxes.len() {
|
||||
let (_, right) = junction_boxes.split_at(i + 1);
|
||||
for b in right {
|
||||
let d = junction_boxes[i].distance_sqr(b);
|
||||
distances.insert(d, (&junction_boxes[i], b));
|
||||
if distances.len() > self.connections {
|
||||
distances.pop_last();
|
||||
// Calculates all distances between junction boxes and processes them.
|
||||
fn process_distances(junction_boxes: &Vec<Point3>, connections: usize) -> (u64, u64) {
|
||||
// Closest pairs of junction boxes (indices), mapped to and sorted by their distances for part 1.
|
||||
let mut smallest_distances = BTreeMap::new();
|
||||
|
||||
// Spanning tree of closest connections between junction boxes for part 2.
|
||||
let mut graph = Graph::new();
|
||||
let mut vertices = vec![graph.add_vertex()];
|
||||
let mut edge_distances = graph.edge_map(0);
|
||||
// Info for the edge with the highest distance: (distance, vertex index 1, vertex index 2).
|
||||
let mut spanning_distances = BTreeMap::new();
|
||||
|
||||
// Calculates distances between all pairs of junction boxes and updates the data structures for both parts.
|
||||
let l = junction_boxes.len();
|
||||
for i in 1..l {
|
||||
// The new vertex must be connected in the same iteration it is created. We track a fallback edge in case
|
||||
// all its distances are above the current maximum. This fallback is None, if the new junction box has been
|
||||
// connected in the graph, or otherwise contains the shortest distance candidate to do so.
|
||||
vertices.push(graph.add_vertex());
|
||||
let mut connection_fallback = Some((u64::MAX, 0));
|
||||
|
||||
for j in 0..i {
|
||||
let d = junction_boxes[i].distance_sqr(&junction_boxes[j]);
|
||||
|
||||
// Updates distances map for part 1.
|
||||
smallest_distances.insert(d, (i, j));
|
||||
if smallest_distances.len() > connections {
|
||||
smallest_distances.pop_last();
|
||||
}
|
||||
|
||||
// Updates graph for part 2.
|
||||
if connection_fallback.is_some_and(|(x, _)| d <= x) {
|
||||
connection_fallback = Some((d, j));
|
||||
}
|
||||
// Ignores distances that are greater than the current maximum.
|
||||
if spanning_distances
|
||||
.last_key_value()
|
||||
.is_none_or(|(&x, &_)| d <= x)
|
||||
{
|
||||
if connection_fallback.is_none() {
|
||||
let path = algorithms::dfs_find_path(&graph, vertices[i], vertices[j])
|
||||
.expect("vertices should be connected already");
|
||||
let to_del = *path
|
||||
.iter()
|
||||
.max_by(|&e, &f| edge_distances[*e].cmp(&edge_distances[*f]))
|
||||
.expect("path should not be empty");
|
||||
if d < edge_distances[to_del] {
|
||||
// The new edge (distance d, from i to j) is better than the edge to_del on the unique path
|
||||
// from i to j, so we remove it and add the new one.
|
||||
let d_to_del = edge_distances[to_del];
|
||||
graph.delete_edge(to_del);
|
||||
spanning_distances.remove(&d_to_del);
|
||||
Self::connect_junction_box(
|
||||
&mut graph,
|
||||
&vertices,
|
||||
&mut edge_distances,
|
||||
&mut spanning_distances,
|
||||
d,
|
||||
i,
|
||||
j,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
connection_fallback = None;
|
||||
Self::connect_junction_box(
|
||||
&mut graph,
|
||||
&vertices,
|
||||
&mut edge_distances,
|
||||
&mut spanning_distances,
|
||||
d,
|
||||
i,
|
||||
j,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
distances
|
||||
if let Some((d, j)) = connection_fallback {
|
||||
Self::connect_junction_box(
|
||||
&mut graph,
|
||||
&vertices,
|
||||
&mut edge_distances,
|
||||
&mut spanning_distances,
|
||||
d,
|
||||
i,
|
||||
j,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let (_, (i, j)) = spanning_distances.pop_last().expect("empty spanning tree");
|
||||
let part2 = u64::try_from(junction_boxes[i].0).unwrap()
|
||||
* u64::try_from(junction_boxes[j].0).unwrap();
|
||||
(Self::circuit_sizes(&smallest_distances), part2)
|
||||
}
|
||||
|
||||
fn connect_junction_box(
|
||||
graph: &mut Graph,
|
||||
vertices: &[Vertex],
|
||||
edge_distances: &mut GraphEdgeMap<u64>,
|
||||
spanning_distances: &mut BTreeMap<u64, (usize, usize)>,
|
||||
d: u64,
|
||||
i: usize,
|
||||
j: usize,
|
||||
) {
|
||||
let e = graph.add_edge(vertices[i], vertices[j]);
|
||||
edge_distances[e] = d;
|
||||
spanning_distances.insert(d, (i, j));
|
||||
}
|
||||
|
||||
// Aggregates all circuits and returns the product of the sizes of the 3 largest ones.
|
||||
fn circuit_sizes(distances: &BTreeMap<u64, (&Point3, &Point3)>) -> u64 {
|
||||
let mut circuits: Vec<HashSet<&Point3>> = Vec::new();
|
||||
fn circuit_sizes(distances: &BTreeMap<u64, (usize, usize)>) -> u64 {
|
||||
let mut circuits: Vec<HashSet<usize>> = Vec::new();
|
||||
for junction_boxes in distances.values() {
|
||||
let index_a = circuits.iter().position(|c| c.contains(&junction_boxes.0));
|
||||
let index_b = circuits.iter().position(|c| c.contains(&junction_boxes.1));
|
||||
if let Some(index_a) = index_a {
|
||||
if let Some(index_b) = index_b {
|
||||
if index_a != index_b {
|
||||
let (low, high) = if index_a < index_b {
|
||||
(index_a, index_b)
|
||||
} else {
|
||||
(index_b, index_a)
|
||||
};
|
||||
let c1 = circuits.iter().position(|c| c.contains(&junction_boxes.0));
|
||||
let c2 = circuits.iter().position(|c| c.contains(&junction_boxes.1));
|
||||
if let Some(c1) = c1 {
|
||||
if let Some(c2) = c2 {
|
||||
if c1 != c2 {
|
||||
let (low, high) = if c1 < c2 { (c1, c2) } else { (c2, c1) };
|
||||
let mut c = circuits.remove(high);
|
||||
circuits[low].extend(c.drain());
|
||||
}
|
||||
} else {
|
||||
circuits[index_a].insert(junction_boxes.1);
|
||||
circuits[c1].insert(junction_boxes.1);
|
||||
}
|
||||
} else {
|
||||
if let Some(index_b) = index_b {
|
||||
if let Some(index_b) = c2 {
|
||||
circuits[index_b].insert(junction_boxes.0);
|
||||
} else {
|
||||
let mut set = HashSet::new();
|
||||
@@ -91,8 +179,6 @@ impl Solver for Playground {
|
||||
|
||||
fn process_data<R: BufRead>(&self, reader: R) -> (u64, u64) {
|
||||
let junction_boxes = Self::junction_boxes(reader);
|
||||
let distances = self.distances(&junction_boxes);
|
||||
|
||||
(Self::circuit_sizes(&distances), 0)
|
||||
Self::process_distances(&junction_boxes, self.connections)
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -70,7 +70,7 @@ fn laboratories() {
|
||||
#[test]
|
||||
fn playground() {
|
||||
assert_eq!(
|
||||
Ok((40, 0)),
|
||||
Ok((40, 25272)),
|
||||
solvers::run_solver(
|
||||
solvers::playground::Playground::new(Some(10)),
|
||||
&EXAMPLE_PATHS
|
||||
|
||||
+1
-1
@@ -68,7 +68,7 @@ fn laboratories() {
|
||||
#[test]
|
||||
fn playground() {
|
||||
assert_eq!(
|
||||
Ok((352584, 0)),
|
||||
Ok((352584, 9617397716)),
|
||||
solvers::run_solver(
|
||||
solvers::playground::Playground::new(None),
|
||||
&solvers::DATA_PATHS
|
||||
|
||||
Reference in New Issue
Block a user