Make day 17 work with const generics

This commit is contained in:
kageru 2021-07-08 19:05:31 +02:00
parent b8ae6f5711
commit e838f132c2
Signed by: kageru
GPG Key ID: 8282A2BEA4ADA3D2
4 changed files with 163 additions and 38 deletions

View File

@ -2,7 +2,7 @@
#![feature(test, const_generics, const_evaluatable_checked)] #![feature(test, const_generics, const_evaluatable_checked)]
extern crate test; extern crate test;
use aoc2020::{ use aoc2020::{
common::*, grid::{cell::Cell, *} common::*, grid::{self, cell::Cell, *}
}; };
use itertools::Itertools; use itertools::Itertools;
@ -10,7 +10,7 @@ fn read_input() -> String {
read_file(17) read_file(17)
} }
fn parse_input<const DIMS: usize, F: FnMut((usize, usize)) -> PositionND<DIMS> + Copy>(raw: &str, mut pos_gen: F) -> Grid<DIMS, Cell> { fn parse_input<const DIMS: usize>(raw: &str) -> Grid<DIMS, Cell> {
raw.lines() raw.lines()
.enumerate() .enumerate()
.flat_map(move |(y, l)| { .flat_map(move |(y, l)| {
@ -22,31 +22,39 @@ fn parse_input<const DIMS: usize, F: FnMut((usize, usize)) -> PositionND<DIMS> +
.collect() .collect()
} }
fn count_live_neighbors<const D: usize>(p: PositionND<D>, grid: &Grid<D, Cell>) -> usize { fn count_live_neighbors<const D: usize>(p: &PositionND<D>, grid: &Grid<D, Cell>) -> usize
p.neighbors().iter().filter(|&n| grid.get(n) == Cell::Alive).count() where [(); grid::num_neighbors(D)]: Sized {
IntoIterator::into_iter(p.neighbors())
.filter(|n| grid.get(n) == Cell::Alive)
.count()
} }
fn make_step<const D: usize>(input: Grid<D, Cell>) -> Grid<D, Cell> { fn make_step<const D: usize>(input: Grid<D, Cell>) -> Grid<D, Cell>
where [(); grid::num_neighbors(D)]: Sized {
let readonly = input.clone(); let readonly = input.clone();
input input
.fields .fields
.keys() .keys()
.flat_map(|p| p.neighbors().iter()) .flat_map(|p| p.neighbors())
.unique() .unique()
.map(|pos| { .map(|pos| (pos, next_state(&pos, &readonly)))
let cell = readonly.get(&pos);
let new = match (&cell, count_live_neighbors::<D>(&pos, &readonly)) {
(Cell::Alive, 2..=3) => Cell::Alive,
(Cell::Dead, 3) => Cell::Alive,
_ => Cell::Dead,
};
(pos, new)
})
.filter(|(_, c)| c == &Cell::Alive) .filter(|(_, c)| c == &Cell::Alive)
.collect() .collect()
} }
fn solve<const D: usize>(parsed: &Grid<D, Cell>, steps: usize) -> usize { fn next_state<const D: usize>(pos: &PositionND<D>, grid: &Grid<D, Cell>) -> Cell
where [(); grid::num_neighbors(D)]: Sized {
let cell = grid.get(pos);
let new = match (&cell, count_live_neighbors::<D>(pos, &grid)) {
(Cell::Alive, 2..=3) => Cell::Alive,
(Cell::Dead, 3) => Cell::Alive,
_ => Cell::Dead,
};
new
}
fn solve<const D: usize>(parsed: &Grid<D, Cell>, steps: usize) -> usize
where [(); grid::num_neighbors(D)]: Sized {
let mut clone = parsed.clone(); let mut clone = parsed.clone();
for _ in 0..steps { for _ in 0..steps {
clone = make_step(clone); clone = make_step(clone);
@ -56,9 +64,9 @@ fn solve<const D: usize>(parsed: &Grid<D, Cell>, steps: usize) -> usize {
fn main() { fn main() {
let raw = read_input(); 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)); 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)); 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] #[test]
fn test_3d() { 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); assert_eq!(solve(&input, 6), 112);
} }
#[test] #[test]
fn test_4d() { 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); assert_eq!(solve(&input, 6), 848);
} }
#[bench] #[bench]
fn bench_3d_parse(b: &mut test::Bencher) { fn bench_3d_parse(b: &mut test::Bencher) {
let raw = read_input(); 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] #[bench]
#[rustfmt::skip] #[rustfmt::skip]
fn bench_4d_parse(b: &mut test::Bencher) { fn bench_4d_parse(b: &mut test::Bencher) {
let raw = read_input(); 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] #[bench]
fn bench_3d(b: &mut test::Bencher) { 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)); b.iter(|| assert_eq!(solve(&input, 6), 348));
} }
#[bench] #[bench]
fn bench_4d(b: &mut test::Bencher) { 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)); b.iter(|| assert_eq!(solve(&input, 6), 2236));
} }
} }

View File

@ -7,16 +7,12 @@ pub use position::*;
use itertools::join; use itertools::join;
use std::{collections::HashMap, fmt::Display, hash::BuildHasher}; use std::{collections::HashMap, fmt::Display, hash::BuildHasher};
#[derive(Debug, Clone)] #[derive(Debug, Clone, PartialEq)]
pub struct Grid<const D: usize, T: Display + Default> { pub struct Grid<const D: usize, T: Display + Default> {
pub fields: HashMap<PositionND<D>, T>, pub fields: HashMap<PositionND<D>, T>,
} }
impl<const D: usize, T: Display + Default + Copy> Grid<D, T> { impl<const D: usize, T: Display + Default + Copy> Grid<D, T> {
//pub fn get_convert<Pos: Into<P>>(&self, pos: Pos) -> T {
//self.fields.get(&pos.into()).copied().unwrap_or_else(|| T::default())
//}
pub fn get(&self, pos: &PositionND<D>) -> T { pub fn get(&self, pos: &PositionND<D>) -> T {
self.fields.get(pos).copied().unwrap_or_else(|| T::default()) self.fields.get(pos).copied().unwrap_or_else(|| T::default())
} }
@ -35,9 +31,9 @@ impl<const D: usize, T: Display + Default> std::iter::FromIterator<(PositionND<D
} }
// impl<T: Display + Default + Copy> Grid<Position2D, T> { // impl<T: Display + Default + Copy> Grid<Position2D, T> {
// fn draw_ascii(&self) -> String { // fn draw_ascii(&self) -> String {
// draw_ascii(&self.fields) // draw_ascii(&self.fields)
// } // }
// } // }
struct Boundaries { struct Boundaries {

View File

@ -1,3 +1,4 @@
extern crate test;
use super::direction::*; use super::direction::*;
use impl_ops::*; use impl_ops::*;
use itertools::iproduct; use itertools::iproduct;
@ -34,7 +35,7 @@ pub struct Position4D {
#[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)] #[derive(Hash, PartialEq, Eq, Debug, Clone, Copy)]
pub struct PositionND<const DIMS: usize> { pub struct PositionND<const DIMS: usize> {
points: [i64; DIMS], pub points: [i64; DIMS],
} }
impl<const D: usize, I> From<[I; D]> for PositionND<D> impl<const D: usize, I> From<[I; D]> for PositionND<D>
@ -49,7 +50,7 @@ where I: TryInto<i64> + Copy
} }
} }
const fn num_neighbors(d: usize) -> usize { pub const fn num_neighbors(d: usize) -> usize {
3usize.pow(d as u32) - 1 3usize.pow(d as u32) - 1
} }
@ -70,13 +71,14 @@ impl<const DIMS: usize> PositionND<DIMS> {
pub fn neighbors(&self) -> [PositionND<DIMS>; num_neighbors(DIMS)] pub fn neighbors(&self) -> [PositionND<DIMS>; num_neighbors(DIMS)]
where where
[PositionND<DIMS>; num_neighbors(DIMS)]: Sized, [PositionND<DIMS>; num_neighbors(DIMS)]: Sized,
[(); num_neighbors(DIMS)]: Sized,
{ {
let mut out = [PositionND::zero(); num_neighbors(DIMS)]; let mut out = [PositionND::zero(); num_neighbors(DIMS)];
match DIMS { match DIMS {
2 => { 2 => {
for (i, n) in iproduct!((-1..=1), (-1..=1)) for (i, n) in iproduct!((-1..=1), (-1..=1))
.filter(|t| t != &(0, 0)) .filter(|t| t != &(0, 0))
.map(|(x, y)| PositionND::<DIMS>::from_padded(&[x, y])) .map(|(x, y)| PositionND::<DIMS>::from_padded(&[self.points[0] + x, self.points[1] + y]))
.enumerate() .enumerate()
{ {
out[i] = n; out[i] = n;
@ -85,7 +87,7 @@ impl<const DIMS: usize> PositionND<DIMS> {
3 => { 3 => {
for (i, n) in iproduct!((-1..=1), (-1..=1), (-1..=1)) for (i, n) in iproduct!((-1..=1), (-1..=1), (-1..=1))
.filter(|t| t != &(0, 0, 0)) .filter(|t| t != &(0, 0, 0))
.map(|(x, y, z)| PositionND::<DIMS>::from_padded(&[x, y, z])) .map(|(x, y, z)| PositionND::<DIMS>::from_padded(&[self.points[0]+x, self.points[1]+y, self.points[2]+z]))
.enumerate() .enumerate()
{ {
out[i] = n; out[i] = n;
@ -94,7 +96,7 @@ impl<const DIMS: usize> PositionND<DIMS> {
4 => { 4 => {
for (i, n) in iproduct!((-1..=1), (-1..=1), (-1..=1), (-1..=1)) for (i, n) in iproduct!((-1..=1), (-1..=1), (-1..=1), (-1..=1))
.filter(|t| t != &(0, 0, 0, 0)) .filter(|t| t != &(0, 0, 0, 0))
.map(|(x, y, z, w)| PositionND::<DIMS>::from_padded(&[x, y, z, w])) .map(|(x, y, z, w)| PositionND::<DIMS>::from_padded(&[self.points[0]+x, self.points[1]+y, self.points[2]+z, self.points[3]+w]))
.enumerate() .enumerate()
{ {
out[i] = n; out[i] = n;
@ -296,3 +298,80 @@ fn unwrap_number_result<I: TryInto<i64>>(i: I) -> i64 {
_ => panic!("Bad coordinate"), _ => 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] },
]
);
}
}

View File

@ -1,5 +1,5 @@
#![allow(incomplete_features)] #![allow(incomplete_features)]
#![feature(const_generics, const_evaluatable_checked)] #![feature(const_generics, const_evaluatable_checked, test)]
pub mod common; pub mod common;
pub mod grid; pub mod grid;
pub mod teststuff; pub mod teststuff;