feat: Completed day 8 and fixed up clippy warnings in code.
All checks were successful
Continuous integration / Check (push) Successful in 38s
Continuous integration / Test Suite (push) Successful in 41s
Continuous integration / Rustfmt (push) Successful in 29s
Continuous integration / Clippy (push) Successful in 41s
Continuous integration / build (push) Successful in 40s

This commit is contained in:
2025-12-08 10:06:53 +00:00
parent bfc853fb76
commit c47649e1fb
7 changed files with 1263 additions and 19 deletions

File diff suppressed because it is too large Load Diff

View 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

View 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

View File

@@ -19,7 +19,7 @@ async fn main() -> Result<(), Box<dyn Error>> {
Box::new(day05::Day05 {}),
Box::new(day06::Day06 {}),
Box::new(day07::Day07 {}),
// Box::new(day08::Day08 {}),
Box::new(day08::Day08 {}),
// Box::new(day09::Day09 {}),
// Box::new(day10::Day10 {}),
// Box::new(day11::Day11 {}),

View File

@@ -45,10 +45,10 @@ impl Solution for Day06 {
}
}
if let Some(op) = op {
if !numbers.is_empty() {
final_problems.push((numbers, op));
}
if let Some(op) = op
&& !numbers.is_empty()
{
final_problems.push((numbers, op));
}
}
@@ -78,11 +78,8 @@ impl Solution for Day06 {
let mut problems: Vec<Vec<char>> = Vec::new();
for col in 0..width {
let mut column_chars = Vec::new();
for row in 0..height {
column_chars.push(grid[row][col]);
}
problems.push(column_chars);
let column: Vec<char> = grid.iter().map(|row| row[col]).collect();
problems.push(column);
}
// Split columns into groups separated by all-space columns
@@ -116,7 +113,7 @@ impl Solution for Day06 {
let mut num_str = String::new();
for &c in &col[..height - 1] {
// all but the last row
if c.is_digit(10) {
if c.is_ascii_digit() {
num_str.push(c);
}
}

View File

@@ -25,7 +25,7 @@ impl Solution for Day07 {
}
impl Day07 {
fn solve_paths(&self, input: &Vec<String>) -> (usize, usize) {
fn solve_paths(&self, input: &[String]) -> (usize, usize) {
let lines: Vec<_> = input.iter().map(String::as_bytes).collect();
let width = lines[0].len();
let start = lines[0].iter().position(|&b| b == b'S').unwrap();

View File

@@ -1,3 +1,6 @@
use std::cmp::Reverse;
use std::error::Error;
use super::Solution;
pub struct Day08 {}
@@ -5,16 +8,30 @@ pub struct Day08 {}
impl Solution for Day08 {
fn part1(
&self,
_input: &mut Vec<String>,
input: &mut Vec<String>,
) -> Result<Box<dyn std::fmt::Display>, Box<dyn std::error::Error>> {
Ok(Box::new("Ready"))
let points = self.parse_points(input)?;
if points.is_empty() {
eprintln!("No points provided.");
return Err("no points".into());
}
// default k = 1000 for the puzzle, but allow override with env var or small change here
let k = 1000usize; // for test input
Ok(Box::new(self.solve_part1(&points, k)))
}
fn part2(
&self,
_input: &mut Vec<String>,
input: &mut Vec<String>,
) -> Result<Box<dyn std::fmt::Display>, Box<dyn std::error::Error>> {
Ok(Box::new("Ready"))
let points = self.parse_points(input)?;
if points.is_empty() {
eprintln!("No points provided.");
return Err("no points".into());
}
Ok(Box::new(self.solve_part2(&points)))
}
fn get_day(&self) -> u8 {
@@ -22,7 +39,197 @@ impl Solution for Day08 {
}
}
impl Day08 {}
impl Day08 {
/// Build all pairwise edges from points (upper triangle).
fn build_edges(&self, points: &[Point]) -> Vec<Edge> {
let n = points.len();
let mut edges = Vec::with_capacity(n.saturating_mul(n.saturating_sub(1)) / 2);
for i in 0..n {
for j in (i + 1)..n {
edges.push(Edge {
a: i,
b: j,
dist2: points[i].dist2(&points[j]),
});
}
}
edges
}
/// Solve: take first k edges sorted by distance, union them, then multiply top 3 component sizes.
fn solve_part1(&self, points: &[Point], k: usize) -> u128 {
let n = points.len();
if n == 0 {
return 0;
}
let mut edges = self.build_edges(points);
// sort ascending by distance (stable)
edges.sort_by_key(|e| e.dist2);
let mut uf = UnionFind::new(n);
let take = k.min(edges.len());
for e in edges.into_iter().take(take) {
uf.union(e.a, e.b);
}
let mut sizes = uf.sizes();
sizes.sort_by_key(|&s| Reverse(s));
// multiply top 3 (or fewer if less)
let mut prod: u128 = 1;
for s in sizes.iter().take(3) {
prod = prod.saturating_mul(*s as u128);
}
prod
}
/// Part Two:
/// walk through edges; when union_count == n-1, return X_a * X_b of that last union.
fn solve_part2(&self, points: &[Point]) -> i128 {
let n = points.len();
if n <= 1 {
return 0;
}
let mut edges = self.build_edges(points);
edges.sort_by_key(|e| e.dist2);
let mut uf = UnionFind::new(n);
let mut unions = 0usize;
let target = n - 1;
for e in edges {
if uf.union(e.a, e.b) {
unions += 1;
if unions == target {
let xa = points[e.a].x as i128;
let xb = points[e.b].x as i128;
return xa * xb;
}
}
}
// should never happen unless input is malformed
0
}
/// Parse many lines of "x,y,z".
fn parse_points(&self, input: &[String]) -> Result<Vec<Point>, Box<dyn Error>> {
let mut points = Vec::new();
for (idx, line) in input.iter().enumerate() {
let line = line.trim();
if line.is_empty() {
continue;
}
match Point::from_csv_line(line) {
Ok(p) => points.push(p),
Err(e) => return Err(format!("line {}: {}", idx + 1, e).into()),
}
}
Ok(points)
}
}
/// A 3D point with integer coords.
#[derive(Debug, Clone, Copy)]
struct Point {
x: i64,
y: i64,
z: i64,
}
impl Point {
fn from_csv_line(s: &str) -> Result<Self, Box<dyn Error>> {
let parts: Vec<_> = s.trim().split(',').collect();
if parts.len() != 3 {
return Err(format!("invalid point line: {}", s).into());
}
let x = parts[0].parse::<i64>()?;
let y = parts[1].parse::<i64>()?;
let z = parts[2].parse::<i64>()?;
Ok(Point { x, y, z })
}
/// squared Euclidean distance, as u128 to be safe.
fn dist2(&self, other: &Point) -> u128 {
let dx = (self.x - other.x) as i128;
let dy = (self.y - other.y) as i128;
let dz = (self.z - other.z) as i128;
let s = dx * dx + dy * dy + dz * dz;
if s < 0 {
// should never happen, but cast safely
(-s) as u128
} else {
s as u128
}
}
}
/// Edge between two indices with squared distance.
#[derive(Debug, Clone)]
struct Edge {
a: usize,
b: usize,
dist2: u128,
}
/// Simple union-find / disjoint-set with union by size + path compression.
#[derive(Debug)]
struct UnionFind {
parent: Vec<isize>, // if negative, -size; else parent index
}
impl UnionFind {
fn new(n: usize) -> Self {
UnionFind {
parent: vec![-1; n],
}
}
fn find(&mut self, x: usize) -> usize {
// path compression
let mut root = x;
while self.parent[root] >= 0 {
root = self.parent[root] as usize;
}
// compress
let mut cur = x;
while cur != root {
let next = self.parent[cur] as usize;
self.parent[cur] = root as isize;
cur = next;
}
root
}
fn union(&mut self, a: usize, b: usize) -> bool {
let mut ra = self.find(a);
let mut rb = self.find(b);
if ra == rb {
return false;
}
let sa = (-self.parent[ra]) as usize;
let sb = (-self.parent[rb]) as usize;
// union by size: attach smaller to larger
if sa < sb {
std::mem::swap(&mut ra, &mut rb);
}
// now ra has >= size
self.parent[ra] = -((sa + sb) as isize);
self.parent[rb] = ra as isize;
true
}
fn sizes(&self) -> Vec<usize> {
let n = self.parent.len();
let mut out = Vec::new();
for i in 0..n {
if self.parent[i] < 0 {
out.push((-self.parent[i]) as usize);
}
}
out
}
}
/// Test from puzzle input
#[cfg(test)]
@@ -44,7 +251,7 @@ mod test {
.unwrap()
.to_string();
assert_eq!(answer, "Ready");
assert_eq!(answer, "20");
}
#[test]
@@ -61,6 +268,6 @@ mod test {
.unwrap()
.to_string();
assert_eq!(answer, "Ready");
assert_eq!(answer, "25272");
}
}