Day 12
This commit is contained in:
parent
7e550fd322
commit
499c2ab588
4 changed files with 280 additions and 41 deletions
|
|
@ -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
222
src/bin/day12.rs
Normal 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 ®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(())
|
||||||
|
}
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
56
src/lib.rs
56
src/lib.rs
|
|
@ -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)]
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue