From 53ca8d0043ae4ed12971d480fadb27fe50a0119a Mon Sep 17 00:00:00 2001 From: Bert Peters Date: Sun, 19 Dec 2021 18:16:56 +0100 Subject: [PATCH] Implement day 19 part 1 By brute force and lots of it --- 2021/src/common.rs | 3 +- 2021/src/day19.rs | 256 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 256 insertions(+), 3 deletions(-) diff --git a/2021/src/common.rs b/2021/src/common.rs index 6751a8f..487b8eb 100644 --- a/2021/src/common.rs +++ b/2021/src/common.rs @@ -82,8 +82,9 @@ pub fn ordered(a: O, b: O) -> (O, O) { } } -pub fn read_input(input: &mut dyn Read, parser: P) -> O +pub fn read_input(mut input: I, parser: P) -> O where + I: Read, P: for<'a> FnOnce(&'a [u8]) -> IResult<&'a [u8], O>, { let mut buffer = Vec::new(); diff --git a/2021/src/day19.rs b/2021/src/day19.rs index 113ba49..fe359a3 100644 --- a/2021/src/day19.rs +++ b/2021/src/day19.rs @@ -1,9 +1,261 @@ +use std::collections::HashSet; use std::io::Read; +use std::ops::Add; +use std::ops::Sub; -pub fn part1(_input: &mut dyn Read) -> String { - todo!() +use nom::bytes::complete::tag; +use nom::character::complete::newline; +use nom::combinator::map; +use nom::multi::many1; +use nom::multi::separated_list1; +use nom::sequence::delimited; +use nom::sequence::preceded; +use nom::sequence::terminated; +use nom::sequence::tuple; +use nom::IResult; + +use crate::common::read_input; + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Default)] +struct Point3(pub [i32; 3]); + +impl Sub for Point3 { + type Output = Self; + + fn sub(self, rhs: Self) -> Self::Output { + Point3([ + self.0[0] - rhs.0[0], + self.0[1] - rhs.0[1], + self.0[2] - rhs.0[2], + ]) + } +} + +impl Add for Point3 { + type Output = Self; + + fn add(self, rhs: Self) -> Self::Output { + Point3([ + self.0[0] + rhs.0[0], + self.0[1] + rhs.0[1], + self.0[2] + rhs.0[2], + ]) + } +} + +struct Rotations<'a> { + points: &'a [Point3], + axes: [usize; 3], + rotation_index: usize, +} + +impl<'a> Rotations<'a> { + const ROTATIONS: [[i32; 3]; 8] = [ + [1, 1, 1], + [1, 1, -1], + [1, -1, 1], + [1, -1, -1], + [-1, 1, 1], + [-1, 1, -1], + [-1, -1, 1], + [-1, -1, -1], + ]; + + pub fn new(points: &'a [Point3]) -> Self { + Self { + points, + axes: [0, 1, 2], + rotation_index: 0, + } + } +} + +impl Iterator for Rotations<'_> { + type Item = Vec; + + fn next(&mut self) -> Option { + if self.rotation_index >= Self::ROTATIONS.len() { + if !next_permutation(&mut self.axes) { + return None; + } + + self.rotation_index = 0; + } + + let axes = &self.axes; + let rot = &Self::ROTATIONS[self.rotation_index]; + + let result = self + .points + .iter() + .map(|Point3(coords)| { + Point3([ + coords[axes[0]] * rot[0], + coords[axes[1]] * rot[1], + coords[axes[2]] * rot[2], + ]) + }) + .collect(); + + self.rotation_index += 1; + + Some(result) + } +} + +fn parse_point(input: &[u8]) -> IResult<&[u8], Point3> { + use nom::character::complete::char; + use nom::character::complete::i32; + + map( + tuple((i32, preceded(char(','), i32), preceded(char(','), i32))), + |(x, y, z)| Point3([x, y, z]), + )(input) +} + +fn parse_input(input: &[u8]) -> IResult<&[u8], Vec>> { + use nom::character::complete::i32; + let parse_header = delimited(tag("--- scanner "), i32, tag(" ---\n")); + + let parse_scanner = preceded(parse_header, many1(terminated(parse_point, newline))); + separated_list1(newline, parse_scanner)(input) +} + +fn try_overlap(correct: &[(Point3, HashSet)], candidate: &[Point3]) -> Option> { + let mut relative = HashSet::new(); + for rot in Rotations::new(candidate) { + for &start in &rot { + relative.clear(); + + relative.extend(rot.iter().map(|&other| other - start)); + + if let Some((base, _)) = correct.iter().find(|(_, correct_relative)| { + correct_relative.intersection(&relative).count() >= 12 + }) { + // Found a solution, build the correct output + let translated = relative.drain().map(|point| point + *base).collect(); + + return Some(translated); + } + } + } + + None +} + +pub fn part1(input: &mut dyn Read) -> String { + let mut scanners = read_input(input, parse_input); + + let mut points: HashSet<_> = scanners[0].iter().copied().collect(); + + let mut todo = vec![std::mem::take(&mut scanners[0])]; + println!("Scanners: {}", scanners.len()); + + while let Some(matched) = todo.pop() { + if scanners.iter().all(Vec::is_empty) { + break; + } + + let relative: Vec<(Point3, HashSet)> = matched + .iter() + .map(|&base| (base, matched.iter().map(|&other| (other - base)).collect())) + .collect(); + + for candidate in &mut scanners { + if candidate.is_empty() { + continue; + } + + if let Some(result) = try_overlap(&relative, candidate) { + points.extend(result.iter().copied()); + todo.push(result); + candidate.clear(); + } + } + } + + scanners.retain(|s| !s.is_empty()); + + points.len().to_string() } pub fn part2(_input: &mut dyn Read) -> String { todo!() } + +pub fn next_permutation(list: &mut [T]) -> bool { + // Based on: https://en.cppreference.com/w/cpp/algorithm/next_permutation + if list.len() <= 1 { + return false; + } + + if let Some((i, val1)) = list + .windows(2) + .enumerate() + .rev() + .find_map(|(i, window)| (window[0] < window[1]).then(|| (i, &window[0]))) + { + let it2 = list + .iter() + .enumerate() + .skip(i + 1) + .rev() + .find_map(|(idx, val2)| (val1 < val2).then(|| idx)) + .expect("Unreachable, ascending pair exists"); + + list.swap(i, it2); + list[(i + 1)..].reverse(); + true + } else { + list.reverse(); + false + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use crate::test_implementation; + + const SAMPLE: &[u8] = include_bytes!("samples/19.txt"); + + #[test] + fn test_next_permutation() { + let mut list = [1, 2, 3]; + + assert!(next_permutation(&mut list)); + assert_eq!(list, [1, 3, 2]); + + assert!(next_permutation(&mut list)); + assert_eq!(list, [2, 1, 3]); + + assert!(next_permutation(&mut list)); + assert_eq!(list, [2, 3, 1]); + + assert!(next_permutation(&mut list)); + assert_eq!(list, [3, 1, 2]); + + assert!(next_permutation(&mut list)); + assert_eq!(list, [3, 2, 1]); + + // Note the negation! + assert!(!next_permutation(&mut list)); + assert_eq!(list, [1, 2, 3]); + + // 24 is a bit too much to write out, but we can check the number + let mut list2 = [1, 2, 3, 4]; + for _ in 1..24 { + assert!(next_permutation(&mut list2)); + } + + // Should be back to the start + assert!(!next_permutation(&mut list2)); + assert_eq!(list2, [1, 2, 3, 4]); + } + + #[test] + fn sample_part1() { + test_implementation(part1, SAMPLE, 79); + } +}