From 499c2ab588f49cdbe5dc02cb38aacbbaf6bf154a Mon Sep 17 00:00:00 2001 From: Quentin Boyer Date: Thu, 12 Dec 2024 23:42:44 +0100 Subject: [PATCH] Day 12 --- src/bin/day10.rs | 22 +---- src/bin/day12.rs | 222 +++++++++++++++++++++++++++++++++++++++++++++++ src/bin/day6.rs | 21 +---- src/lib.rs | 56 ++++++++++++ 4 files changed, 280 insertions(+), 41 deletions(-) create mode 100644 src/bin/day12.rs diff --git a/src/bin/day10.rs b/src/bin/day10.rs index 521d4cb..63c9f94 100644 --- a/src/bin/day10.rs +++ b/src/bin/day10.rs @@ -1,7 +1,6 @@ use std::{collections::HashSet, time::Instant}; -use aoc_2024::{load, print_res}; -use arrayvec::ArrayVec; +use aoc_2024::{load, neighbours, print_res}; use bstr::{BString, ByteSlice}; type Parsed = Vec>; @@ -24,25 +23,6 @@ pub fn parsing(input: &BString) -> color_eyre::Result { .collect() } -fn neighbours(l: usize, c: usize, map: &Parsed) -> ArrayVec<(usize, usize), 4> { - let mut a = ArrayVec::new(); - - if l != 0 { - a.push((l - 1, c)); - } - if c != 0 { - a.push((l, c - 1)); - } - if l + 1 != map.len() { - a.push((l + 1, c)) - } - if c + 1 != map[0].len() { - a.push((l, c + 1)) - } - - a -} - fn trailhead_score(l: usize, c: usize, map: &Parsed) -> usize { let mut locations = HashSet::new(); locations.insert((l, c)); diff --git a/src/bin/day12.rs b/src/bin/day12.rs new file mode 100644 index 0000000..d1aaaf5 --- /dev/null +++ b/src/bin/day12.rs @@ -0,0 +1,222 @@ +use std::{collections::HashSet, time::Instant}; + +use aoc_2024::{load, neighbours, print_res, Direction}; +use bstr::{BString, ByteSlice}; +use itertools::Itertools; + +type Parsed<'a> = Vec<&'a [u8]>; + +#[inline(never)] +pub fn parsing(input: &BString) -> color_eyre::Result { + Ok(input.lines().collect_vec()) +} + +fn flood_fill( + l: usize, + c: usize, + input: &Parsed, + seen: &mut [Vec], +) -> (u8, Vec<(usize, usize)>) { + let mut out = vec![]; + + let ty = input[l][c]; + assert!(!seen[l][c]); + + let mut todo = vec![(l, c)]; + + while let Some((l, c)) = todo.pop() { + if seen[l][c] { + continue; + } + + seen[l][c] = true; + out.push((l, c)); + + for (nl, nc) in neighbours(l, c, input) { + if input[nl][nc] == ty && !seen[nl][nc] { + todo.push((nl, nc)) + } + } + } + + (ty, out) +} + +#[inline(never)] +pub fn part1(input: Parsed) { + let mut seen = input.iter().map(|x| vec![false; x.len()]).collect_vec(); + let mut total_price = 0; + + for (l, &line) in input.iter().enumerate() { + for c in 0..line.len() { + if seen[l][c] { + continue; + } + + let (ty, region) = flood_fill(l, c, &input, &mut seen); + + let mut perimeter = 0; + for &(l, c) in ®ion { + perimeter += 4 - neighbours(l, c, &input) + .iter() + .filter(|&&(nl, nc)| input[nl][nc] == ty) + .count(); + } + + total_price += perimeter * region.len(); + } + } + + print_res!("Total price: {total_price}") +} + +fn sides(ty: u8, region: Vec<(usize, usize)>, input: &Parsed) -> usize { + if region.len() == 1 { + return 4; + } + + let type_neighbours = |l, c| { + neighbours(l, c, input) + .iter() + .filter(|&&(nl, nc)| input[nl][nc] == ty) + .count() + }; + + let border = region + .into_iter() + .filter(|&(l, c)| type_neighbours(l, c) != 4) + .collect_vec(); + + let looks_out = |d: Direction, l: usize, c: usize| -> bool { + match d { + Direction::Up => { + if l == 0 { + true + } else { + input[l - 1][c] != ty + } + } + Direction::Right => { + if c + 1 == input[0].len() { + true + } else { + input[l][c + 1] != ty + } + } + Direction::Down => { + if l + 1 == input[0].len() { + true + } else { + input[l + 1][c] != ty + } + } + Direction::Left => { + if c == 0 { + true + } else { + input[l][c - 1] != ty + } + } + } + }; + + let correct_orientation = |walk: Direction, l: usize, c: usize| -> bool { + !looks_out(walk, l, c) && looks_out(walk.turn_right(), l, c) + }; + + let mut sides = 0; + + let mut done = HashSet::new(); + + for (mut l, mut c) in border { + for mut walk in Direction::all() { + let start = (walk, (l, c)); + if !correct_orientation(walk, l, c) || done.contains(&start) { + continue; + } + done.insert(start); + + (l, c) = walk.walk(l, c); + 'walk: while (walk, (l, c)) != start { + done.insert((walk, (l, c))); + + let type_neigh = type_neighbours(l, c); + + if !looks_out(walk.turn_right(), l, c) { + walk = walk.turn_right(); + (l, c) = walk.walk(l, c); + + // Finished an outer side, find the next one + sides += 1; + continue 'walk; + } + + if !looks_out(walk, l, c) { + (l, c) = walk.walk(l, c); + continue 'walk; + } + + // Finished an inner side, find the next one + sides += 1; + // We have a second 1 len size + if type_neigh == 1 { + sides += 1; + } + + assert!(!correct_orientation(walk, l, c)); + for _ in 0..4 { + if correct_orientation(walk, l, c) { + continue 'walk; + } + + walk = walk.turn_right(); + } + + panic!("Found no correct direction"); + } + } + } + + sides +} + +#[inline(never)] +pub fn part2(input: Parsed) { + let mut seen = input.iter().map(|x| vec![false; x.len()]).collect_vec(); + let mut total_price = 0; + + for (l, &line) in input.iter().enumerate() { + for c in 0..line.len() { + if seen[l][c] { + continue; + } + + let (ty, region) = flood_fill(l, c, &input, &mut seen); + let area = region.len(); + total_price += sides(ty, region, &input) * area; + } + } + + print_res!("Total price: {total_price}") +} + +pub fn main() -> color_eyre::Result<()> { + let context = load()?; + + let start = Instant::now(); + let parsed = parsing(&context.input)?; + let elapsed = humantime::format_duration(start.elapsed()); + + let start = Instant::now(); + if context.part == 1 { + part1(parsed); + } else { + part2(parsed); + } + let elapsed_part = humantime::format_duration(start.elapsed()); + + println!(" Parsing: {elapsed}"); + println!(" Solving: {elapsed_part}"); + + Ok(()) +} diff --git a/src/bin/day6.rs b/src/bin/day6.rs index 7998136..fa3b342 100644 --- a/src/bin/day6.rs +++ b/src/bin/day6.rs @@ -1,6 +1,6 @@ use std::{collections::HashSet, time::Instant}; -use aoc_2024::{load, print_res}; +use aoc_2024::{load, print_res, Direction}; use bstr::{BString, ByteSlice}; use color_eyre::eyre::ContextCompat; @@ -45,25 +45,6 @@ pub fn parsing(input: &BString) -> color_eyre::Result { Ok((guard.with_context(|| "No guard found")?, lines)) } -#[derive(Clone, Copy, PartialEq, Eq, Hash)] -enum Direction { - Up, - Right, - Down, - Left, -} - -impl Direction { - pub fn turn_right(self) -> Self { - match self { - Self::Up => Self::Right, - Self::Right => Self::Down, - Self::Down => Self::Left, - Self::Left => Self::Up, - } - } -} - impl Point { fn walk(self, direction: Direction) -> Self { let Self { line, col } = self; diff --git a/src/lib.rs b/src/lib.rs index 2fb5dc1..36c6c39 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,10 +1,66 @@ use std::path::PathBuf; +use arrayvec::ArrayVec; use bstr::{BString, ByteSlice}; use clap::Parser; pub mod tinyvec; +pub fn neighbours(l: usize, c: usize, map: L) -> ArrayVec<(usize, usize), 4> +where + L: AsRef<[C]>, + C: AsRef<[T]>, +{ + let mut a = ArrayVec::new(); + + if l != 0 { + a.push((l - 1, c)); + } + if c != 0 { + a.push((l, c - 1)); + } + if l + 1 != map.as_ref().len() { + a.push((l + 1, c)) + } + if c + 1 != map.as_ref()[0].as_ref().len() { + a.push((l, c + 1)) + } + + a +} + +#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)] +pub enum Direction { + Up, + Right, + Down, + Left, +} + +impl Direction { + pub fn turn_right(self) -> Self { + match self { + Self::Up => Self::Right, + Self::Right => Self::Down, + Self::Down => Self::Left, + Self::Left => Self::Up, + } + } + + pub fn walk(&self, l: usize, c: usize) -> (usize, usize) { + match self { + Direction::Up => (l - 1, c), + Direction::Right => (l, c + 1), + Direction::Down => (l + 1, c), + Direction::Left => (l, c - 1), + } + } + + pub fn all() -> [Self; 4] { + [Self::Up, Self::Right, Self::Down, Self::Left] + } +} + #[derive(Parser)] struct Args { #[arg(short, long)]