This commit is contained in:
Quentin Boyer 2024-12-12 23:42:44 +01:00
parent 7e550fd322
commit 499c2ab588
4 changed files with 280 additions and 41 deletions

View file

@ -1,7 +1,6 @@
use std::{collections::HashSet, time::Instant}; use std::{collections::HashSet, time::Instant};
use aoc_2024::{load, print_res}; use aoc_2024::{load, neighbours, print_res};
use arrayvec::ArrayVec;
use bstr::{BString, ByteSlice}; use bstr::{BString, ByteSlice};
type Parsed = Vec<Vec<u8>>; type Parsed = Vec<Vec<u8>>;
@ -24,25 +23,6 @@ pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
.collect() .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 { fn trailhead_score(l: usize, c: usize, map: &Parsed) -> usize {
let mut locations = HashSet::new(); let mut locations = HashSet::new();
locations.insert((l, c)); locations.insert((l, c));

222
src/bin/day12.rs Normal file
View file

@ -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<Parsed> {
Ok(input.lines().collect_vec())
}
fn flood_fill(
l: usize,
c: usize,
input: &Parsed,
seen: &mut [Vec<bool>],
) -> (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 &region {
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(())
}

View file

@ -1,6 +1,6 @@
use std::{collections::HashSet, time::Instant}; use std::{collections::HashSet, time::Instant};
use aoc_2024::{load, print_res}; use aoc_2024::{load, print_res, Direction};
use bstr::{BString, ByteSlice}; use bstr::{BString, ByteSlice};
use color_eyre::eyre::ContextCompat; use color_eyre::eyre::ContextCompat;
@ -45,25 +45,6 @@ pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
Ok((guard.with_context(|| "No guard found")?, lines)) 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 { impl Point {
fn walk(self, direction: Direction) -> Self { fn walk(self, direction: Direction) -> Self {
let Self { line, col } = self; let Self { line, col } = self;

View file

@ -1,10 +1,66 @@
use std::path::PathBuf; use std::path::PathBuf;
use arrayvec::ArrayVec;
use bstr::{BString, ByteSlice}; use bstr::{BString, ByteSlice};
use clap::Parser; use clap::Parser;
pub mod tinyvec; pub mod tinyvec;
pub fn neighbours<L, C, T>(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)] #[derive(Parser)]
struct Args { struct Args {
#[arg(short, long)] #[arg(short, long)]