day12: cleanup and speedup

This commit is contained in:
2025-12-12 03:10:27 -08:00
parent 004591374c
commit fd6b0c846b

View File

@@ -1,101 +1,64 @@
use std::iter::repeat_n;
use std::{collections::BinaryHeap, f64::consts, fmt::Display};
use aoc_runner_derive::{aoc, aoc_generator}; use aoc_runner_derive::{aoc, aoc_generator};
use grid::{Coord2d, Grid, MirrorAxis}; use grid::{Coord2d, Grid, MirrorAxis};
use itertools::{Format, Itertools}; use itertools::Itertools;
use std::collections::HashSet;
use std::fmt::Display;
use std::iter::repeat_n;
type CellType = bool;
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct PresentShape { struct PresentShape {
idx: u8, variants: Vec<Grid<CellType>>,
shape: Grid<u8>,
variants: Vec<Grid<u8>>,
} }
impl From<&str> for PresentShape { impl From<&str> for PresentShape {
fn from(value: &str) -> Self { fn from(value: &str) -> Self {
let (idx, rest) = value.split_once(":\n").unwrap(); let (_idx, rest) = value.split_once(":\n").unwrap();
let shape: Grid<u8> = rest.parse().unwrap(); let shape_raw: Grid<u8> = rest.parse().unwrap();
let mut shape: Grid<CellType> = shape_raw.same_shape(false);
for pos in shape_raw.find_all(&b'#') {
shape.set(&pos, true);
}
Self { Self {
idx: idx.parse().unwrap(),
variants: (0..4) variants: (0..4)
.map(|rot| shape.rotated(rot)) .map(|rot| shape.rotated(rot))
.chain([MirrorAxis::X, MirrorAxis::Y].map(|axis| shape.mirrored(axis))) .chain([MirrorAxis::X, MirrorAxis::Y].map(|axis| shape.mirrored(axis)))
.unique() .unique()
.collect(), .collect(),
shape,
} }
} }
} }
impl Display for PresentShape { impl Display for PresentShape {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}:\n", self.idx))?; f.write_fmt(format_args!("{}", self.variants[0]))
f.write_fmt(format_args!("{}", self.shape))
} }
} }
impl PresentShape { impl PresentShape {
fn make(&self) -> Present {
Present {
shape: self.clone(),
location: None,
variant: 0,
}
}
fn popcount(&self) -> u64 { fn popcount(&self) -> u64 {
self.shape.data.iter().filter(|c| **c != b'.').count() as u64 self.variants[0].count(&true) as u64
} }
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct Present { struct Present {
shape: PresentShape, shape: usize,
location: Option<Coord2d>,
variant: usize,
} }
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct ChristmasTree { struct ChristmasTree {
area: Grid<u8>, area: Grid<CellType>,
presents: Vec<Present>, presents: Vec<Present>,
} }
impl ChristmasTree {}
impl Display for ChristmasTree {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!("{}", self.area))?;
for (i, p) in self.presents.iter().enumerate() {
f.write_fmt(format_args!(" {i}: @{:?} rot {}\n", p.location, p.variant))?;
f.write_fmt(format_args!("{}", p.shape.variants[p.variant]))?;
}
std::fmt::Result::Ok(())
}
}
#[derive(Debug, Clone)] #[derive(Debug, Clone)]
struct PresentsProblem { struct PresentsProblem {
shapes: Vec<PresentShape>,
trees: Vec<ChristmasTree>, trees: Vec<ChristmasTree>,
} shapes: Vec<PresentShape>,
impl PresentsProblem {
fn present(&self, idx: usize) -> Present {
Present {
shape: self.shapes[idx].clone(),
location: None,
variant: 0,
}
}
fn add_tree(&mut self, w: usize, h: usize, presents: Vec<Present>) {
self.trees.push(ChristmasTree {
area: Grid::with_shape(w, h, b'.'),
presents,
})
}
} }
#[aoc_generator(day12)] #[aoc_generator(day12)]
@@ -116,88 +79,113 @@ fn parse(input: &str) -> PresentsProblem {
let presents = present_counts let presents = present_counts
.iter() .iter()
.enumerate() .enumerate()
.flat_map(|(i, count)| repeat_n(shapes[i].make(), *count)) .flat_map(|(i, count)| repeat_n(Present { shape: i }, *count))
.collect(); .collect();
trees.push(ChristmasTree { trees.push(ChristmasTree {
area: Grid::with_shape(w.parse().unwrap(), h.parse().unwrap(), b'.'), area: Grid::with_shape(w.parse().unwrap(), h.parse().unwrap(), false),
presents, presents,
}); });
} }
PresentsProblem { shapes, trees } PresentsProblem { trees, shapes }
} }
// place b on a and test if any # overlap any non-. // place b on a and test if any # overlap any non-.
fn overlaps(a: &Grid<u8>, b: &Grid<u8>, p: &Coord2d) -> bool { // fn overlaps(a: &Grid<CellType>, b: &Grid<CellType>, p: &Coord2d) -> bool {
b.find_all(&b'#') // b.find_all(&true)
.any(|c| a.get(&(c + p)).is_some_and(|c| *c != b'.')) // .any(|c| a.get(&(c + p)).is_some_and(|c| *c))
} // }
fn place_shape( impl PresentsProblem {
mut t: ChristmasTree, fn place_shape(
idx: usize, &self,
pos: Coord2d, mut t: ChristmasTree,
variant: usize, idx: usize,
) -> Option<ChristmasTree> { pos: Coord2d,
let variant = &t.presents[idx].shape.variants[variant]; variant: usize,
) -> Option<ChristmasTree> {
let variant = &self.shapes[t.presents[idx].shape].variants[variant];
if !overlaps(&t.area, variant, &pos) { for xy in variant.find_all(&true) {
for xy in variant.find_all(&b'#') { if let Some(was) = t.area.set(&(pos + &(xy)), true)
t.area.set(&(pos + &(xy)), b'0' + idx as u8).unwrap(); && was
{
// overlapped
return None;
}
} }
Some(t) Some(t)
} else {
None
} }
}
fn place_next(mut t: ChristmasTree, cur: usize) -> Option<ChristmasTree> { fn place_next(
if cur == t.presents.len() { &self,
return Some(t); t: ChristmasTree,
} cur: usize,
let variants = &t.presents[cur].shape.variants; cache: &mut HashSet<(usize, Grid<CellType>)>, // impossible shapes
'variant: for (i, _v) in variants.iter().enumerate() { ) -> Option<ChristmasTree> {
//make sure the longest 'bar' can fit if cur == t.presents.len() {
for y in 0..&t.area.height() - 2 { // done
for x in 0..&t.area.width() - 2 { return Some(t);
if let Some(new_t) = place_shape( }
t.clone(), if cache.contains(&(cur, t.area.clone())) {
cur, // doomed
Coord2d { return None;
x: x as i64, }
y: y as i64, let variants = &self.shapes[t.presents[cur].shape].variants;
}, for (i, _v) in variants.iter().enumerate() {
i, for y in 0..&t.area.height() - 2 {
) { for x in 0..&t.area.width() - 2 {
if let Some(solution) = place_next(new_t, cur + 1) { if let Some(new_t) = self.place_shape(
return Some(solution); t.clone(),
cur,
Coord2d {
x: x as i64,
y: y as i64,
},
i,
) {
if let Some(solution) = self.place_next(new_t, cur + 1, cache) {
return Some(solution);
} else {
cache.insert((cur, t.area.clone()));
}
} }
} }
} }
} }
None
} }
None
} }
#[aoc(day12, part1)] #[aoc(day12, part1)]
fn part1(input: &PresentsProblem) -> u64 { fn part1(input: &PresentsProblem) -> u64 {
let input = input.clone(); let input = input.clone();
let mut count = 0; let mut count = 0;
// for t in input.trees[0..2].iter() {
// println!("trying:\n{t}");
// if let Some(r) = place_next(t.clone(), 0) {
// count += 1;
// println!("ok!\n{r}");
// } else {
// println!("failed :(");
// }
// }
for t in input.trees.iter() { for t in input.trees.iter() {
let area = (t.area.width() * t.area.height()) as u64; let area = (t.area.width() * t.area.height()) as u64;
let occupied_area = t.presents.iter().map(|p| p.shape.popcount()).sum::<u64>(); let (occupied_area, present_count) = t
if occupied_area <= area { .presents
count += 1 .iter()
.map(|p| (input.shapes[p.shape].popcount(), 1))
.reduce(|(o_a, pc_a), (o_b, pc_b)| (o_a + o_b, pc_a + pc_b))
.unwrap();
if occupied_area > area {
// definitely impossible
continue;
}
let available_3x3s = (t.area.width() / 3) * (t.area.height() / 3);
if available_3x3s >= present_count {
// definitely_possible
count += 1;
continue;
}
if input
.place_next(t.clone(), 0, &mut HashSet::new())
.is_some()
{
count += 1;
continue;
} }
} }
@@ -205,7 +193,7 @@ fn part1(input: &PresentsProblem) -> u64 {
} }
#[aoc(day12, part2)] #[aoc(day12, part2)]
fn part2(input: &PresentsProblem) -> u64 { fn part2(_input: &PresentsProblem) -> u64 {
0 0
} }
@@ -251,12 +239,12 @@ mod tests {
#[rstest] #[rstest]
#[case(EXAMPLE, 0)] #[case(EXAMPLE, 0)]
fn part1_example(#[case] input: &str, #[case] expected: u64) { fn part1_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part1(&parse(EXAMPLE)), 2); assert_eq!(part1(&parse(input)), expected);
} }
#[rstest] #[rstest]
#[case(EXAMPLE, 0)] #[case(EXAMPLE, 0)]
fn part2_example(#[case] input: &str, #[case] expected: u64) { fn part2_example(#[case] input: &str, #[case] expected: u64) {
assert_eq!(part2(&parse(EXAMPLE)), 0); assert_eq!(part2(&parse(input)), expected);
} }
} }