From b8ae6f5711d20364a42f2e299565690e897f7cf1 Mon Sep 17 00:00:00 2001 From: kageru Date: Thu, 17 Dec 2020 21:30:30 +0100 Subject: [PATCH 1/2] try to make const generics work for day 17 --- 2020/src/bin/day17.rs | 23 ++++---- 2020/src/grid.rs | 30 +++++------ 2020/src/grid/position.rs | 107 +++++++++++++++++++++++++++++++++++++- 2020/src/lib.rs | 4 +- 4 files changed, 138 insertions(+), 26 deletions(-) diff --git a/2020/src/bin/day17.rs b/2020/src/bin/day17.rs index eae1ad5..3adcad8 100644 --- a/2020/src/bin/day17.rs +++ b/2020/src/bin/day17.rs @@ -1,36 +1,41 @@ -#![feature(test)] +#![allow(incomplete_features)] +#![feature(test, const_generics, const_evaluatable_checked)] extern crate test; -use itertools::Itertools; use aoc2020::{ common::*, grid::{cell::Cell, *} }; +use itertools::Itertools; fn read_input() -> String { read_file(17) } -fn parse_input P + Copy>(raw: &str, mut pos_gen: F) -> Grid { +fn parse_input PositionND + Copy>(raw: &str, mut pos_gen: F) -> Grid { raw.lines() .enumerate() - .flat_map(move |(y, l)| l.bytes().enumerate().map(move |(x, b)| (pos_gen((x, y)), b.into()))) + .flat_map(move |(y, l)| { + l.bytes() + .enumerate() + .map(move |(x, b)| (PositionND::::from_padded(&[x as i64, y as i64]), b.into())) + }) .filter(|(_, c)| c == &Cell::Alive) .collect() } -fn count_live_neighbors(p: &P, grid: &Grid) -> usize { +fn count_live_neighbors(p: PositionND, grid: &Grid) -> usize { p.neighbors().iter().filter(|&n| grid.get(n) == Cell::Alive).count() } -fn make_step(input: Grid) -> Grid { +fn make_step(input: Grid) -> Grid { let readonly = input.clone(); input .fields .keys() - .flat_map(|p| p.neighbors()) + .flat_map(|p| p.neighbors().iter()) .unique() .map(|pos| { let cell = readonly.get(&pos); - let new = match (&cell, count_live_neighbors(&pos, &readonly)) { + let new = match (&cell, count_live_neighbors::(&pos, &readonly)) { (Cell::Alive, 2..=3) => Cell::Alive, (Cell::Dead, 3) => Cell::Alive, _ => Cell::Dead, @@ -41,7 +46,7 @@ fn make_step(input: Grid) -> Grid { .collect() } -fn solve(parsed: &Grid, steps: usize) -> usize { +fn solve(parsed: &Grid, steps: usize) -> usize { let mut clone = parsed.clone(); for _ in 0..steps { clone = make_step(clone); diff --git a/2020/src/grid.rs b/2020/src/grid.rs index a287722..eb0a69a 100644 --- a/2020/src/grid.rs +++ b/2020/src/grid.rs @@ -8,37 +8,37 @@ use itertools::join; use std::{collections::HashMap, fmt::Display, hash::BuildHasher}; #[derive(Debug, Clone)] -pub struct Grid { - pub fields: HashMap, +pub struct Grid { + pub fields: HashMap, T>, } -impl Grid { - pub fn get_convert>(&self, pos: Pos) -> T { - self.fields.get(&pos.into()).copied().unwrap_or_else(|| T::default()) - } +impl Grid { + //pub fn get_convert>(&self, pos: Pos) -> T { + //self.fields.get(&pos.into()).copied().unwrap_or_else(|| T::default()) + //} - pub fn get(&self, pos: &P) -> T { + pub fn get(&self, pos: &PositionND) -> T { self.fields.get(pos).copied().unwrap_or_else(|| T::default()) } - pub fn insert>(&mut self, pos: Pos, t: T) { + pub fn insert>>(&mut self, pos: Pos, t: T) { self.fields.insert(pos.into(), t); } } -impl std::iter::FromIterator<(P, T)> for Grid { - fn from_iter>(iter: I) -> Self { +impl std::iter::FromIterator<(PositionND, T)> for Grid { + fn from_iter, T)>>(iter: I) -> Self { Grid { fields: iter.into_iter().collect(), } } } -impl Grid { - fn draw_ascii(&self) -> String { - draw_ascii(&self.fields) - } -} +// impl Grid { + // fn draw_ascii(&self) -> String { + // draw_ascii(&self.fields) + // } +// } struct Boundaries { x_min: i64, diff --git a/2020/src/grid/position.rs b/2020/src/grid/position.rs index 074d308..f7d7268 100644 --- a/2020/src/grid/position.rs +++ b/2020/src/grid/position.rs @@ -1,7 +1,9 @@ use super::direction::*; use impl_ops::*; use itertools::iproduct; -use std::{convert::TryInto, hash::Hash, ops, ops::AddAssign}; +use std::{ + convert::TryInto, hash::Hash, ops, ops::{Add, AddAssign} +}; pub trait Position where Self: Sized + Hash + PartialEq + Eq + Clone + Copy @@ -30,6 +32,109 @@ pub struct Position4D { pub w: i64, } +#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)] +pub struct PositionND { + points: [i64; DIMS], +} + +impl From<[I; D]> for PositionND +where I: TryInto + Copy +{ + fn from(s: [I; D]) -> Self { + let mut points = [0; D]; + for i in 0..D { + points[i] = unwrap_number_result(s[i]); + } + Self { points } + } +} + +const fn num_neighbors(d: usize) -> usize { + 3usize.pow(d as u32) - 1 +} + +impl PositionND { + pub const fn zero() -> Self { + PositionND { points: [0; DIMS] } + } + + pub fn from_padded(slice: &[i64]) -> PositionND { + let mut points = [0; DIMS]; + for i in 0..(DIMS.min(slice.len())) { + points[i] = slice[i]; + } + PositionND { points } + } + + // until I can figure out how to properly do that, here’s a “good enough” solution :^) + pub fn neighbors(&self) -> [PositionND; num_neighbors(DIMS)] + where + [PositionND; num_neighbors(DIMS)]: Sized, + { + let mut out = [PositionND::zero(); num_neighbors(DIMS)]; + match DIMS { + 2 => { + for (i, n) in iproduct!((-1..=1), (-1..=1)) + .filter(|t| t != &(0, 0)) + .map(|(x, y)| PositionND::::from_padded(&[x, y])) + .enumerate() + { + out[i] = n; + } + } + 3 => { + for (i, n) in iproduct!((-1..=1), (-1..=1), (-1..=1)) + .filter(|t| t != &(0, 0, 0)) + .map(|(x, y, z)| PositionND::::from_padded(&[x, y, z])) + .enumerate() + { + out[i] = n; + } + } + 4 => { + for (i, n) in iproduct!((-1..=1), (-1..=1), (-1..=1), (-1..=1)) + .filter(|t| t != &(0, 0, 0, 0)) + .map(|(x, y, z, w)| PositionND::::from_padded(&[x, y, z, w])) + .enumerate() + { + out[i] = n; + } + } + _ => unimplemented!(), + } + out + } + + // Maybe one day :( + /* + fn neighbors_inner(existing: [i64; DIMS]) -> [[i64; DIMS]; (DIMS - D).pow(3)] { + let out = [[0; DIMS]; (DIMS - D).pow(3)]; + let mut index = 0; + for i in -1..=1 { + existing[D] = i; + // I guess that means no recursion with const generics? + for xs in neighbors_inner(existing.clone()) { + out[index] = xs; + index += 1; + } + } + out + } + */ +} + +impl Add> for PositionND { + type Output = PositionND; + + fn add(self, rhs: PositionND) -> Self::Output { + let mut points = [0; D]; + for i in 0..D { + points[i] = self.points[i] + rhs.points[i]; + } + PositionND { points } + } +} + mod p2d { use super::*; diff --git a/2020/src/lib.rs b/2020/src/lib.rs index 9c7da59..76b81db 100644 --- a/2020/src/lib.rs +++ b/2020/src/lib.rs @@ -1,3 +1,5 @@ -pub mod teststuff; +#![allow(incomplete_features)] +#![feature(const_generics, const_evaluatable_checked)] pub mod common; pub mod grid; +pub mod teststuff; From e838f132c23f4facae33566354fd89db05c522e5 Mon Sep 17 00:00:00 2001 From: kageru Date: Thu, 8 Jul 2021 19:05:31 +0200 Subject: [PATCH 2/2] Make day 17 work with const generics --- 2020/src/bin/day17.rs | 98 +++++++++++++++++++++++++++++---------- 2020/src/grid.rs | 12 ++--- 2020/src/grid/position.rs | 89 +++++++++++++++++++++++++++++++++-- 2020/src/lib.rs | 2 +- 4 files changed, 163 insertions(+), 38 deletions(-) diff --git a/2020/src/bin/day17.rs b/2020/src/bin/day17.rs index 3adcad8..48f01de 100644 --- a/2020/src/bin/day17.rs +++ b/2020/src/bin/day17.rs @@ -2,7 +2,7 @@ #![feature(test, const_generics, const_evaluatable_checked)] extern crate test; use aoc2020::{ - common::*, grid::{cell::Cell, *} + common::*, grid::{self, cell::Cell, *} }; use itertools::Itertools; @@ -10,7 +10,7 @@ fn read_input() -> String { read_file(17) } -fn parse_input PositionND + Copy>(raw: &str, mut pos_gen: F) -> Grid { +fn parse_input(raw: &str) -> Grid { raw.lines() .enumerate() .flat_map(move |(y, l)| { @@ -22,31 +22,39 @@ fn parse_input PositionND + .collect() } -fn count_live_neighbors(p: PositionND, grid: &Grid) -> usize { - p.neighbors().iter().filter(|&n| grid.get(n) == Cell::Alive).count() +fn count_live_neighbors(p: &PositionND, grid: &Grid) -> usize +where [(); grid::num_neighbors(D)]: Sized { + IntoIterator::into_iter(p.neighbors()) + .filter(|n| grid.get(n) == Cell::Alive) + .count() } -fn make_step(input: Grid) -> Grid { +fn make_step(input: Grid) -> Grid +where [(); grid::num_neighbors(D)]: Sized { let readonly = input.clone(); input .fields .keys() - .flat_map(|p| p.neighbors().iter()) + .flat_map(|p| p.neighbors()) .unique() - .map(|pos| { - let cell = readonly.get(&pos); - let new = match (&cell, count_live_neighbors::(&pos, &readonly)) { - (Cell::Alive, 2..=3) => Cell::Alive, - (Cell::Dead, 3) => Cell::Alive, - _ => Cell::Dead, - }; - (pos, new) - }) + .map(|pos| (pos, next_state(&pos, &readonly))) .filter(|(_, c)| c == &Cell::Alive) .collect() } -fn solve(parsed: &Grid, steps: usize) -> usize { +fn next_state(pos: &PositionND, grid: &Grid) -> Cell +where [(); grid::num_neighbors(D)]: Sized { + let cell = grid.get(pos); + let new = match (&cell, count_live_neighbors::(pos, &grid)) { + (Cell::Alive, 2..=3) => Cell::Alive, + (Cell::Dead, 3) => Cell::Alive, + _ => Cell::Dead, + }; + new +} + +fn solve(parsed: &Grid, steps: usize) -> usize +where [(); grid::num_neighbors(D)]: Sized { let mut clone = parsed.clone(); for _ in 0..steps { clone = make_step(clone); @@ -56,9 +64,9 @@ fn solve(parsed: &Grid, steps: usize) -> usize { fn main() { let raw = read_input(); - let input = parse_input(&raw, |(x, y)| Position3D::from((x, y, 0))); + let input = parse_input::<3>(&raw); println!("Part 1: {}", solve(&input, 6)); - let input = parse_input(&raw, |(x, y)| Position4D::from((x, y, 0, 0))); + let input = parse_input::<4>(&raw); println!("Part 2: {}", solve(&input, 6)); } @@ -72,40 +80,82 @@ mod tests { ..# ###"; + #[test] + fn test_make_step() { + let input = parse_input::<3>(TEST_INPUT); + let changed = make_step(input.clone()); + let expected = Grid { + fields: IntoIterator::into_iter([ + (PositionND { points: [1, 3, 0] }, Cell::Alive), + (PositionND { points: [0, 1, 0] }, Cell::Alive), + (PositionND { points: [2, 2, 0] }, Cell::Alive), + (PositionND { points: [2, 2, 1] }, Cell::Alive), + (PositionND { points: [0, 1, 1] }, Cell::Alive), + (PositionND { points: [2, 1, 0] }, Cell::Alive), + (PositionND { points: [1, 3, -1] }, Cell::Alive), + (PositionND { points: [0, 1, -1] }, Cell::Alive), + (PositionND { points: [1, 2, 0] }, Cell::Alive), + (PositionND { points: [1, 3, 1] }, Cell::Alive), + (PositionND { points: [2, 2, -1] }, Cell::Alive), + ]) + .collect(), + }; + assert_eq!(changed, expected); + } + + #[test] + fn test_count_live_neighbors() { + let input = parse_input::<2>(TEST_INPUT); + let one_one = PositionND { points: [1, 1] }; + let live = count_live_neighbors(&one_one, &input); + assert_eq!(live, 5); + } + + #[test] + fn test_next_state() { + let input = parse_input::<2>(TEST_INPUT); + let one_one = PositionND { points: [1, 1] }; + assert_eq!(next_state(&one_one, &input), Cell::Dead); + let one_three = PositionND { points: [1, 3] }; + assert_eq!(next_state(&one_three, &input), Cell::Alive); + } + #[test] fn test_3d() { - let input = parse_input(TEST_INPUT, |(x, y)| Position3D::from((x, y, 0))); + let input = parse_input::<3>(TEST_INPUT); + assert_eq!(solve(&input, 1), 11); + assert_eq!(solve(&input, 2), 21); assert_eq!(solve(&input, 6), 112); } #[test] fn test_4d() { - let input = parse_input(TEST_INPUT, |(x, y)| Position4D::from((x, y, 0, 0))); + let input = parse_input::<4>(TEST_INPUT); assert_eq!(solve(&input, 6), 848); } #[bench] fn bench_3d_parse(b: &mut test::Bencher) { let raw = read_input(); - b.iter(|| assert_eq!(parse_input(black_box(&raw), |(x, y)| Position3D::from((x, y, 0))).fields.len(), 43)); + b.iter(|| assert_eq!(parse_input::<3>(black_box(&raw)).fields.len(), 43)); } #[bench] #[rustfmt::skip] fn bench_4d_parse(b: &mut test::Bencher) { let raw = read_input(); - b.iter(|| assert_eq!(parse_input(black_box(&raw), |(x, y)| Position4D::from((x, y, 0, 0))).fields.len(), 43)); + b.iter(|| assert_eq!(parse_input::<4>(black_box(&raw)).fields.len(), 43)); } #[bench] fn bench_3d(b: &mut test::Bencher) { - let input = parse_input(&read_input(), |(x, y)| Position3D::from((x, y, 0))); + let input = parse_input::<3>(&read_input()); b.iter(|| assert_eq!(solve(&input, 6), 348)); } #[bench] fn bench_4d(b: &mut test::Bencher) { - let input = parse_input(&read_input(), |(x, y)| Position4D::from((x, y, 0, 0))); + let input = parse_input::<4>(&read_input()); b.iter(|| assert_eq!(solve(&input, 6), 2236)); } } diff --git a/2020/src/grid.rs b/2020/src/grid.rs index eb0a69a..ac43b93 100644 --- a/2020/src/grid.rs +++ b/2020/src/grid.rs @@ -7,16 +7,12 @@ pub use position::*; use itertools::join; use std::{collections::HashMap, fmt::Display, hash::BuildHasher}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Grid { pub fields: HashMap, T>, } impl Grid { - //pub fn get_convert>(&self, pos: Pos) -> T { - //self.fields.get(&pos.into()).copied().unwrap_or_else(|| T::default()) - //} - pub fn get(&self, pos: &PositionND) -> T { self.fields.get(pos).copied().unwrap_or_else(|| T::default()) } @@ -35,9 +31,9 @@ impl std::iter::FromIterator<(PositionND Grid { - // fn draw_ascii(&self) -> String { - // draw_ascii(&self.fields) - // } +// fn draw_ascii(&self) -> String { +// draw_ascii(&self.fields) +// } // } struct Boundaries { diff --git a/2020/src/grid/position.rs b/2020/src/grid/position.rs index f7d7268..5972bfb 100644 --- a/2020/src/grid/position.rs +++ b/2020/src/grid/position.rs @@ -1,3 +1,4 @@ +extern crate test; use super::direction::*; use impl_ops::*; use itertools::iproduct; @@ -34,7 +35,7 @@ pub struct Position4D { #[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)] pub struct PositionND { - points: [i64; DIMS], + pub points: [i64; DIMS], } impl From<[I; D]> for PositionND @@ -49,7 +50,7 @@ where I: TryInto + Copy } } -const fn num_neighbors(d: usize) -> usize { +pub const fn num_neighbors(d: usize) -> usize { 3usize.pow(d as u32) - 1 } @@ -70,13 +71,14 @@ impl PositionND { pub fn neighbors(&self) -> [PositionND; num_neighbors(DIMS)] where [PositionND; num_neighbors(DIMS)]: Sized, + [(); num_neighbors(DIMS)]: Sized, { let mut out = [PositionND::zero(); num_neighbors(DIMS)]; match DIMS { 2 => { for (i, n) in iproduct!((-1..=1), (-1..=1)) .filter(|t| t != &(0, 0)) - .map(|(x, y)| PositionND::::from_padded(&[x, y])) + .map(|(x, y)| PositionND::::from_padded(&[self.points[0] + x, self.points[1] + y])) .enumerate() { out[i] = n; @@ -85,7 +87,7 @@ impl PositionND { 3 => { for (i, n) in iproduct!((-1..=1), (-1..=1), (-1..=1)) .filter(|t| t != &(0, 0, 0)) - .map(|(x, y, z)| PositionND::::from_padded(&[x, y, z])) + .map(|(x, y, z)| PositionND::::from_padded(&[self.points[0]+x, self.points[1]+y, self.points[2]+z])) .enumerate() { out[i] = n; @@ -94,7 +96,7 @@ impl PositionND { 4 => { for (i, n) in iproduct!((-1..=1), (-1..=1), (-1..=1), (-1..=1)) .filter(|t| t != &(0, 0, 0, 0)) - .map(|(x, y, z, w)| PositionND::::from_padded(&[x, y, z, w])) + .map(|(x, y, z, w)| PositionND::::from_padded(&[self.points[0]+x, self.points[1]+y, self.points[2]+z, self.points[3]+w])) .enumerate() { out[i] = n; @@ -296,3 +298,80 @@ fn unwrap_number_result>(i: I) -> i64 { _ => panic!("Bad coordinate"), } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_neighbors_2d() { + let p = PositionND { points: [0, 0] }; + let n = p.neighbors(); + assert_eq!( + n, + [ + PositionND { points: [-1, -1] }, + PositionND { points: [-1, 0] }, + PositionND { points: [-1, 1] }, + PositionND { points: [0, -1] }, + PositionND { points: [0, 1] }, + PositionND { points: [1, -1] }, + PositionND { points: [1, 0] }, + PositionND { points: [1, 1] }, + ] + ); + + let p = PositionND { points: [1, 1] }; + let n = p.neighbors(); + assert_eq!( + n, + [ + PositionND { points: [0, 0] }, + PositionND { points: [0, 1] }, + PositionND { points: [0, 2] }, + PositionND { points: [1, 0] }, + PositionND { points: [1, 2] }, + PositionND { points: [2, 0] }, + PositionND { points: [2, 1] }, + PositionND { points: [2, 2] }, + ] + ) + } + + #[test] + fn test_neighbors_3d() { + let p = PositionND { points: [0, 0, 0] }; + let n = p.neighbors(); + assert_eq!( + n, + [ + PositionND { points: [-1, -1, -1] }, + PositionND { points: [-1, -1, 0] }, + PositionND { points: [-1, -1, 1] }, + PositionND { points: [-1, 0, -1] }, + PositionND { points: [-1, 0, 0] }, + PositionND { points: [-1, 0, 1] }, + PositionND { points: [-1, 1, -1] }, + PositionND { points: [-1, 1, 0] }, + PositionND { points: [-1, 1, 1] }, + PositionND { points: [0, -1, -1] }, + PositionND { points: [0, -1, 0] }, + PositionND { points: [0, -1, 1] }, + PositionND { points: [0, 0, -1] }, + PositionND { points: [0, 0, 1] }, + PositionND { points: [0, 1, -1] }, + PositionND { points: [0, 1, 0] }, + PositionND { points: [0, 1, 1] }, + PositionND { points: [1, -1, -1] }, + PositionND { points: [1, -1, 0] }, + PositionND { points: [1, -1, 1] }, + PositionND { points: [1, 0, -1] }, + PositionND { points: [1, 0, 0] }, + PositionND { points: [1, 0, 1] }, + PositionND { points: [1, 1, -1] }, + PositionND { points: [1, 1, 0] }, + PositionND { points: [1, 1, 1] }, + ] + ); + } +} diff --git a/2020/src/lib.rs b/2020/src/lib.rs index 76b81db..a0264e2 100644 --- a/2020/src/lib.rs +++ b/2020/src/lib.rs @@ -1,5 +1,5 @@ #![allow(incomplete_features)] -#![feature(const_generics, const_evaluatable_checked)] +#![feature(const_generics, const_evaluatable_checked, test)] pub mod common; pub mod grid; pub mod teststuff;