2025: day08: part 1
This commit is contained in:
parent
b91aad3b14
commit
63ffe8794b
6 changed files with 1213 additions and 0 deletions
|
|
@ -7,6 +7,7 @@ use aoc2025::day04;
|
|||
use aoc2025::day05;
|
||||
use aoc2025::day06;
|
||||
use aoc2025::day07;
|
||||
use aoc2025::day08;
|
||||
|
||||
fn aoc2025_all(c: &mut Criterion) {
|
||||
c.bench_function("day01", |b| b.iter(|| day01::run().unwrap()));
|
||||
|
|
@ -16,6 +17,7 @@ fn aoc2025_all(c: &mut Criterion) {
|
|||
c.bench_function("day05", |b| b.iter(|| day05::run().unwrap()));
|
||||
c.bench_function("day06", |b| b.iter(|| day06::run().unwrap()));
|
||||
c.bench_function("day07", |b| b.iter(|| day07::run().unwrap()));
|
||||
c.bench_function("day08", |b| b.iter(|| day08::run().unwrap()));
|
||||
}
|
||||
|
||||
criterion_group! {
|
||||
|
|
|
|||
1000
aoc2025/input/day08.txt
Normal file
1000
aoc2025/input/day08.txt
Normal file
File diff suppressed because it is too large
Load diff
20
aoc2025/input/day08_provided.txt
Normal file
20
aoc2025/input/day08_provided.txt
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
162,817,812
|
||||
57,618,57
|
||||
906,360,560
|
||||
592,479,940
|
||||
352,342,300
|
||||
466,668,158
|
||||
542,29,236
|
||||
431,825,988
|
||||
739,650,466
|
||||
52,470,668
|
||||
216,146,977
|
||||
819,987,18
|
||||
117,168,530
|
||||
805,96,715
|
||||
346,949,466
|
||||
970,615,88
|
||||
941,993,340
|
||||
862,61,35
|
||||
984,92,344
|
||||
425,690,689
|
||||
188
aoc2025/src/day08.rs
Normal file
188
aoc2025/src/day08.rs
Normal file
|
|
@ -0,0 +1,188 @@
|
|||
use anyhow::{Context, Result, bail};
|
||||
use std::{collections::HashMap, fmt::Write, hash::Hash, str::FromStr};
|
||||
|
||||
const INPUT: &str = include_str!("../input/day08.txt");
|
||||
|
||||
pub fn run() -> Result<String> {
|
||||
let mut res = String::with_capacity(128);
|
||||
writeln!(res, "part 1: {}", part1(INPUT, 1000)?)?;
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Hash, PartialEq, Eq)]
|
||||
struct JunctionBox {
|
||||
x: usize,
|
||||
y: usize,
|
||||
z: usize,
|
||||
}
|
||||
|
||||
impl FromStr for JunctionBox {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self> {
|
||||
let (x, rest) = s
|
||||
.split_once(',')
|
||||
.context("couldn't split JunctionBox coordinates on comma")?;
|
||||
let (y, z) = rest
|
||||
.split_once(',')
|
||||
.context("couldn't split JunctionBox coordinates on second comma")?;
|
||||
|
||||
let x = x.parse()?;
|
||||
let y = y.parse()?;
|
||||
let z = z.parse()?;
|
||||
|
||||
Ok(Self { x, y, z })
|
||||
}
|
||||
}
|
||||
|
||||
impl JunctionBox {
|
||||
fn distance_to(&self, other: &JunctionBox) -> f64 {
|
||||
((self.x as f64 - other.x as f64).powi(2)
|
||||
+ (self.y as f64 - other.y as f64).powi(2)
|
||||
+ (self.z as f64 - other.z as f64).powi(2))
|
||||
.sqrt()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
struct UnionFindKey(usize);
|
||||
|
||||
/// UnionFind implementation that maintains a circular linked list in addition to the usual tree for
|
||||
/// each disjoint subset, allowing easy iteration on a subset's members.
|
||||
struct UnionFind<T: Sized + Hash> {
|
||||
members_to_keys: HashMap<T, UnionFindKey>,
|
||||
parent: Vec<UnionFindKey>,
|
||||
#[allow(unused)]
|
||||
next: Vec<UnionFindKey>,
|
||||
size: Vec<usize>,
|
||||
}
|
||||
|
||||
impl<T: Sized + Hash + Eq> UnionFind<T> {
|
||||
fn from(members: Vec<T>) -> Self {
|
||||
let keys: Vec<UnionFindKey> = (0..members.len()).map(UnionFindKey).collect();
|
||||
Self {
|
||||
parent: keys.clone(),
|
||||
next: keys.clone(),
|
||||
size: (0..members.len()).map(|_| 1).collect(),
|
||||
members_to_keys: members.into_iter().zip(keys).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_key(&self, member: &T) -> Result<UnionFindKey> {
|
||||
self.members_to_keys
|
||||
.get(member)
|
||||
.context("unknown member")
|
||||
.copied()
|
||||
}
|
||||
|
||||
fn union(&mut self, left: &T, right: &T) -> Result<UnionFindKey> {
|
||||
let left = self.get_key(left)?;
|
||||
let right = self.get_key(right)?;
|
||||
Ok(self.union_by_key(left, right))
|
||||
}
|
||||
|
||||
fn union_by_key(&mut self, left: UnionFindKey, right: UnionFindKey) -> UnionFindKey {
|
||||
let mut left_root = self.find_by_key(left);
|
||||
let mut right_root = self.find_by_key(right);
|
||||
|
||||
if left_root == right_root {
|
||||
return left_root;
|
||||
}
|
||||
|
||||
if self.size[left_root.0] < self.size[right_root.0] {
|
||||
(left_root, right_root) = (right_root, left_root)
|
||||
}
|
||||
|
||||
self.parent[right_root.0] = left_root;
|
||||
let new_size = self.size[left_root.0] + self.size[right_root.0];
|
||||
self.size[left_root.0] = new_size;
|
||||
|
||||
left_root
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn find(&mut self, member: &T) -> Result<UnionFindKey> {
|
||||
let key = self.get_key(member)?;
|
||||
Ok(self.find_by_key(key))
|
||||
}
|
||||
|
||||
// This could be implemented with &self using interior mutability...
|
||||
fn find_by_key(&mut self, key: UnionFindKey) -> UnionFindKey {
|
||||
if key != self.parent[key.0] {
|
||||
self.parent[key.0] = self.find_by_key(self.parent[key.0]);
|
||||
}
|
||||
|
||||
self.parent[key.0]
|
||||
}
|
||||
|
||||
fn roots(&self) -> impl Iterator<Item = UnionFindKey> {
|
||||
self.parent
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(idx, &key)| if idx == key.0 { Some(key) } else { None })
|
||||
}
|
||||
|
||||
fn get_size(&mut self, key: UnionFindKey) -> usize {
|
||||
let root = self.find_by_key(key);
|
||||
self.size[root.0]
|
||||
}
|
||||
}
|
||||
|
||||
fn part1(input: &str, connections: usize) -> Result<usize> {
|
||||
let points = input
|
||||
.lines()
|
||||
.map(JunctionBox::from_str)
|
||||
.collect::<Result<Vec<_>>>()?;
|
||||
|
||||
let mut distances = Vec::new();
|
||||
for (i, &pi) in points.iter().enumerate() {
|
||||
for &pj in points.iter().skip(i + 1) {
|
||||
distances.push((pi.distance_to(&pj), (pi, pj)));
|
||||
}
|
||||
}
|
||||
distances.sort_by(|(dist1, _), (dist2, _)| dist1.total_cmp(dist2));
|
||||
if connections > distances.len() {
|
||||
bail!(
|
||||
"There are less possible matchings than the required number of connections: {} matchings",
|
||||
distances.len()
|
||||
);
|
||||
}
|
||||
|
||||
let mut uf = UnionFind::from(points);
|
||||
for (_, (p1, p2)) in distances.into_iter().take(connections) {
|
||||
uf.union(&p1, &p2)?;
|
||||
}
|
||||
|
||||
let roots = uf.roots().collect::<Vec<_>>();
|
||||
let mut sizes = roots.iter().map(|r| uf.get_size(*r)).collect::<Vec<_>>();
|
||||
sizes.sort_by_key(|v| std::cmp::Reverse(*v));
|
||||
|
||||
Ok(sizes[0] * sizes[1] * sizes[2])
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
const PROVIDED: &str = include_str!("../input/day08_provided.txt");
|
||||
|
||||
#[test]
|
||||
fn part1_provided() {
|
||||
assert_eq!(part1(PROVIDED, 10).unwrap(), 40);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn part1_real() {
|
||||
assert_eq!(part1(INPUT, 1000).unwrap(), 127551);
|
||||
}
|
||||
|
||||
//#[test]
|
||||
//fn part2_provided() {
|
||||
// assert_eq!(part2(PROVIDED).unwrap(), 40);
|
||||
//}
|
||||
|
||||
//#[test]
|
||||
//fn part2_real() {
|
||||
// assert_eq!(part2(INPUT).unwrap(), 18818811755665);
|
||||
//}
|
||||
}
|
||||
|
|
@ -5,3 +5,4 @@ pub mod day04;
|
|||
pub mod day05;
|
||||
pub mod day06;
|
||||
pub mod day07;
|
||||
pub mod day08;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ use aoc2025::day04;
|
|||
use aoc2025::day05;
|
||||
use aoc2025::day06;
|
||||
use aoc2025::day07;
|
||||
use aoc2025::day08;
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let days: &[DayFunc] = &[
|
||||
|
|
@ -19,6 +20,7 @@ fn main() -> Result<()> {
|
|||
day05::run,
|
||||
day06::run,
|
||||
day07::run,
|
||||
day08::run,
|
||||
];
|
||||
|
||||
aoc::run(days)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue