diff --git a/src/day12.rs b/src/day12.rs index 9b2c739..f3fd20b 100644 --- a/src/day12.rs +++ b/src/day12.rs @@ -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 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)] struct PresentShape { - idx: u8, - shape: Grid, - variants: Vec>, + variants: Vec>, } impl From<&str> for PresentShape { fn from(value: &str) -> Self { - let (idx, rest) = value.split_once(":\n").unwrap(); + let (_idx, rest) = value.split_once(":\n").unwrap(); - let shape: Grid = rest.parse().unwrap(); + let shape_raw: Grid = rest.parse().unwrap(); + let mut shape: Grid = shape_raw.same_shape(false); + for pos in shape_raw.find_all(&b'#') { + shape.set(&pos, true); + } Self { - idx: idx.parse().unwrap(), variants: (0..4) .map(|rot| shape.rotated(rot)) .chain([MirrorAxis::X, MirrorAxis::Y].map(|axis| shape.mirrored(axis))) .unique() .collect(), - shape, } } } impl Display for PresentShape { 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.shape)) + f.write_fmt(format_args!("{}", self.variants[0])) } } impl PresentShape { - fn make(&self) -> Present { - Present { - shape: self.clone(), - location: None, - variant: 0, - } - } 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)] struct Present { - shape: PresentShape, - location: Option, - variant: usize, + shape: usize, } #[derive(Debug, Clone)] struct ChristmasTree { - area: Grid, + area: Grid, presents: Vec, } -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)] struct PresentsProblem { - shapes: Vec, trees: Vec, -} - -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) { - self.trees.push(ChristmasTree { - area: Grid::with_shape(w, h, b'.'), - presents, - }) - } + shapes: Vec, } #[aoc_generator(day12)] @@ -116,88 +79,113 @@ fn parse(input: &str) -> PresentsProblem { let presents = present_counts .iter() .enumerate() - .flat_map(|(i, count)| repeat_n(shapes[i].make(), *count)) + .flat_map(|(i, count)| repeat_n(Present { shape: i }, *count)) .collect(); 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, }); } - PresentsProblem { shapes, trees } + PresentsProblem { trees, shapes } } // place b on a and test if any # overlap any non-. -fn overlaps(a: &Grid, b: &Grid, p: &Coord2d) -> bool { - b.find_all(&b'#') - .any(|c| a.get(&(c + p)).is_some_and(|c| *c != b'.')) -} +// fn overlaps(a: &Grid, b: &Grid, p: &Coord2d) -> bool { +// b.find_all(&true) +// .any(|c| a.get(&(c + p)).is_some_and(|c| *c)) +// } -fn place_shape( - mut t: ChristmasTree, - idx: usize, - pos: Coord2d, - variant: usize, -) -> Option { - let variant = &t.presents[idx].shape.variants[variant]; +impl PresentsProblem { + fn place_shape( + &self, + mut t: ChristmasTree, + idx: usize, + pos: Coord2d, + variant: usize, + ) -> Option { + let variant = &self.shapes[t.presents[idx].shape].variants[variant]; - if !overlaps(&t.area, variant, &pos) { - for xy in variant.find_all(&b'#') { - t.area.set(&(pos + &(xy)), b'0' + idx as u8).unwrap(); + for xy in variant.find_all(&true) { + if let Some(was) = t.area.set(&(pos + &(xy)), true) + && was + { + // overlapped + return None; + } } - Some(t) - } else { - None } -} -fn place_next(mut t: ChristmasTree, cur: usize) -> Option { - if cur == t.presents.len() { - return Some(t); - } - let variants = &t.presents[cur].shape.variants; - 'variant: for (i, _v) in variants.iter().enumerate() { - //make sure the longest 'bar' can fit - for y in 0..&t.area.height() - 2 { - for x in 0..&t.area.width() - 2 { - if let Some(new_t) = place_shape( - t.clone(), - cur, - Coord2d { - x: x as i64, - y: y as i64, - }, - i, - ) { - if let Some(solution) = place_next(new_t, cur + 1) { - return Some(solution); + fn place_next( + &self, + t: ChristmasTree, + cur: usize, + cache: &mut HashSet<(usize, Grid)>, // impossible shapes + ) -> Option { + if cur == t.presents.len() { + // done + return Some(t); + } + if cache.contains(&(cur, t.area.clone())) { + // doomed + return None; + } + let variants = &self.shapes[t.presents[cur].shape].variants; + for (i, _v) in variants.iter().enumerate() { + for y in 0..&t.area.height() - 2 { + for x in 0..&t.area.width() - 2 { + if let Some(new_t) = self.place_shape( + 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)] fn part1(input: &PresentsProblem) -> u64 { let input = input.clone(); 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() { let area = (t.area.width() * t.area.height()) as u64; - let occupied_area = t.presents.iter().map(|p| p.shape.popcount()).sum::(); - if occupied_area <= area { - count += 1 + let (occupied_area, present_count) = t + .presents + .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)] -fn part2(input: &PresentsProblem) -> u64 { +fn part2(_input: &PresentsProblem) -> u64 { 0 } @@ -251,12 +239,12 @@ mod tests { #[rstest] #[case(EXAMPLE, 0)] fn part1_example(#[case] input: &str, #[case] expected: u64) { - assert_eq!(part1(&parse(EXAMPLE)), 2); + assert_eq!(part1(&parse(input)), expected); } #[rstest] #[case(EXAMPLE, 0)] fn part2_example(#[case] input: &str, #[case] expected: u64) { - assert_eq!(part2(&parse(EXAMPLE)), 0); + assert_eq!(part2(&parse(input)), expected); } }