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 aoc_2024::{load, print_res};
|
||||
use arrayvec::ArrayVec;
|
||||
use aoc_2024::{load, neighbours, print_res};
|
||||
use bstr::{BString, ByteSlice};
|
||||
|
||||
type Parsed = Vec<Vec<u8>>;
|
||||
|
|
@ -24,25 +23,6 @@ pub fn parsing(input: &BString) -> color_eyre::Result<Parsed> {
|
|||
.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));
|
||||
|
|
|
|||
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 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<Parsed> {
|
|||
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;
|
||||
|
|
|
|||
56
src/lib.rs
56
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, 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)]
|
||||
struct Args {
|
||||
#[arg(short, long)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue