feat: Completed day 9 part 1.. Part 2 just takes way too long to run
All checks were successful
Continuous integration / Check (push) Successful in 40s
Continuous integration / Test Suite (push) Successful in 41s
Continuous integration / Rustfmt (push) Successful in 28s
Continuous integration / Clippy (push) Successful in 42s
Continuous integration / build (push) Successful in 40s

This commit is contained in:
2025-12-09 21:57:41 +00:00
parent c47649e1fb
commit 58f9bab125
5 changed files with 798 additions and 21 deletions

View File

@@ -20,23 +20,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
Box::new(day06::Day06 {}),
Box::new(day07::Day07 {}),
Box::new(day08::Day08 {}),
// Box::new(day09::Day09 {}),
Box::new(day09::Day09 {}),
// Box::new(day10::Day10 {}),
// Box::new(day11::Day11 {}),
// Box::new(day12::Day12 {}),
// Box::new(day13::Day13 {}),
// Box::new(day14::Day14 {}),
// Box::new(day15::Day15 {}),
// Box::new(day16::Day16 {}),
// Box::new(day17::Day17 {}),
// Box::new(day18::Day18 {}),
// Box::new(day19::Day19 {}),
// Box::new(day20::Day20 {}),
// Box::new(day21::Day21 {}),
// Box::new(day22::Day22 {}),
// Box::new(day23::Day23 {}),
// Box::new(day24::Day24 {}),
// Box::new(day25::Day25 {}),
];
// Vector to store every solution as a thread handle

View File

@@ -1,20 +1,24 @@
use super::Solution;
use std::collections::VecDeque;
pub struct Day09 {}
impl Solution for Day09 {
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)?;
Ok(Box::new(self.max_rectangle_area(&points)))
}
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 red = self.parse_points(input)?;
Ok(Box::new(self.largest_red_green_rectangle(&red)))
}
fn get_day(&self) -> u8 {
@@ -22,7 +26,281 @@ impl Solution for Day09 {
}
}
impl Day09 {}
impl Day09 {
fn parse_points(&self, reader: &[String]) -> Result<Vec<Point>, Box<dyn std::error::Error>> {
let mut pts = Vec::new();
for line in reader.iter() {
let trimmed = line.trim();
if trimmed.is_empty() {
continue;
}
let mut parts = trimmed.split(',');
let x_str = parts.next().ok_or("Missing x")?.trim();
let y_str = parts.next().ok_or("Missing y")?.trim();
let x = x_str.parse::<i32>()?;
let y = y_str.parse::<i32>()?;
pts.push(Point { x, y });
}
Ok(pts)
}
fn max_rectangle_area(&self, points: &[Point]) -> i64 {
let n = points.len();
let mut best = 0i64;
for i in 0..n {
for j in (i + 1)..n {
let a = points[i];
let b = points[j];
let area = self.rectangle_area(a, b);
if area > best {
best = area;
}
}
}
best
}
fn build_edges(&self, red: &[Point]) -> Vec<Point> {
let mut v = Vec::new();
let n = red.len();
for i in 0..n {
let a = red[i];
let b = red[(i + 1) % n];
if a.x == b.x {
let x = a.x;
let y1 = a.y.min(b.y);
let y2 = a.y.max(b.y);
for y in y1..=y2 {
v.push(Point { x, y });
}
} else if a.y == b.y {
let y = a.y;
let x1 = a.x.min(b.x);
let x2 = a.x.max(b.x);
for x in x1..=x2 {
v.push(Point { x, y });
}
} else {
panic!("Adjacent red points must be aligned.");
}
}
v
}
fn flood_fill_interior(
&self,
edges: &[Point],
min_x: i32,
min_y: i32,
width: usize,
height: usize,
) -> BitGrid {
let mut grid = BitGrid::new(min_x, min_y, width, height);
for &p in edges {
grid.set(p.x, p.y);
}
// Flood-fill from outside
let mut visited = BitGrid::new(min_x, min_y, width, height);
let mut q = VecDeque::new();
let start = Point { x: min_x, y: min_y };
q.push_back(start);
visited.set(start.x, start.y);
let dirs = [(1, 0), (-1, 0), (0, 1), (0, -1)];
while let Some(p) = q.pop_front() {
for (dx, dy) in dirs {
let np = Point {
x: p.x + dx,
y: p.y + dy,
};
if np.x < min_x
|| np.x >= min_x + width as i32
|| np.y < min_y
|| np.y >= min_y + height as i32
{
continue;
}
if grid.get(np.x, np.y) {
// edge blocks movement
continue;
}
if !visited.get(np.x, np.y) {
visited.set(np.x, np.y);
q.push_back(np);
}
}
}
// Build final valid = edges + interior
let mut valid = BitGrid::new(min_x, min_y, width, height);
for y in 0..height {
for block in 0..valid.blocks_per_row {
let outside = visited.rows[y][block];
let edge = grid.rows[y][block];
let interior = (!outside) & (!edge);
valid.rows[y][block] = edge | interior;
}
}
valid
}
fn rectangle_ok(&self, a: Point, b: Point, grid: &BitGrid) -> bool {
let x1 = a.x.min(b.x);
let x2 = a.x.max(b.x);
let y1 = a.y.min(b.y);
let y2 = a.y.max(b.y);
let start = (x1 - grid.min_x) as usize;
let end = (x2 - grid.min_x) as usize;
let block_start = start / 64;
let block_end = end / 64;
for y in y1..=y2 {
let row_index = (y - grid.min_y) as usize;
let row = &grid.rows[row_index];
for (blk, _) in row.iter().enumerate().take(block_end + 1).skip(block_start) {
let left_bit = if blk == block_start { start % 64 } else { 0 };
let right_bit = if blk == block_end { end % 64 } else { 63 };
let mask = if left_bit == 0 && right_bit == 63 {
!0u64
} else {
((1u64 << (right_bit + 1)) - 1) & !((1u64 << left_bit) - 1)
};
let bits = row[blk];
if (bits & mask) != mask {
return false;
}
}
}
true
}
fn largest_red_green_rectangle(&self, red: &[Point]) -> i64 {
// Determine bounding box (expand by 2 for flood-fill safety)
let min_x = red.iter().map(|p| p.x).min().unwrap() - 2;
let max_x = red.iter().map(|p| p.x).max().unwrap() + 2;
let min_y = red.iter().map(|p| p.y).min().unwrap() - 2;
let max_y = red.iter().map(|p| p.y).max().unwrap() + 2;
let width = (max_x - min_x + 1) as usize;
let height = (max_y - min_y + 1) as usize;
let edges = self.build_edges(red);
let valid = self.flood_fill_interior(&edges, min_x, min_y, width, height);
// Ensure red tiles are "valid"
let mut valid = valid;
for &p in red {
valid.set(p.x, p.y);
}
// Compute max rectangle
let mut best = 0;
for i in 0..red.len() {
for j in (i + 1)..red.len() {
let a = red[i];
let b = red[j];
if self.rectangle_ok(a, b, &valid) {
let area = self.rectangle_area(a, b);
if area > best {
best = area;
}
}
}
}
best
}
fn rectangle_area(&self, a: Point, b: Point) -> i64 {
let width = ((a.x - b.x).abs() + 1) as i64;
let height = ((a.y - b.y).abs() + 1) as i64;
width * height
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
struct Point {
x: i32,
y: i32,
}
#[derive(Clone)]
struct BitGrid {
min_x: i32,
min_y: i32,
width: usize,
height: usize,
blocks_per_row: usize,
rows: Vec<Vec<u64>>,
}
impl BitGrid {
fn new(min_x: i32, min_y: i32, width: usize, height: usize) -> Self {
let blocks_per_row = width.div_ceil(64);
let mut rows = Vec::with_capacity(height);
for _ in 0..height {
rows.push(vec![0u64; blocks_per_row]);
}
Self {
min_x,
min_y,
width,
height,
blocks_per_row,
rows,
}
}
#[inline]
fn coord_to_block_bit(&self, x: i32, y: i32) -> Option<(usize, u64)> {
let dx = x - self.min_x;
let dy = y - self.min_y;
if dx < 0 || dy < 0 {
return None;
}
let ux = dx as usize;
let uy = dy as usize;
if ux >= self.width || uy >= self.height {
return None;
}
let block = ux / 64;
let bit = 1u64 << (ux % 64);
Some((uy * self.blocks_per_row + block, bit))
}
#[inline]
fn set(&mut self, x: i32, y: i32) {
if let Some((flat_index, bit)) = self.coord_to_block_bit(x, y) {
let row = flat_index / self.blocks_per_row;
let col = flat_index % self.blocks_per_row;
self.rows[row][col] |= bit;
}
}
#[inline]
fn get(&self, x: i32, y: i32) -> bool {
if let Some((flat_index, bit)) = self.coord_to_block_bit(x, y) {
let row = flat_index / self.blocks_per_row;
let col = flat_index % self.blocks_per_row;
(self.rows[row][col] & bit) != 0
} else {
false
}
}
}
/// Test from puzzle input
#[cfg(test)]
@@ -44,7 +322,7 @@ mod test {
.unwrap()
.to_string();
assert_eq!(answer, "Ready");
assert_eq!(answer, "50");
}
#[test]
@@ -61,6 +339,6 @@ mod test {
.unwrap()
.to_string();
assert_eq!(answer, "Ready");
assert_eq!(answer, "24");
}
}