diff options
Diffstat (limited to 'src/tests')
-rw-r--r-- | src/tests/Cargo.lock | 9 | ||||
-rw-r--r-- | src/tests/Cargo.toml | 1 | ||||
-rw-r--r-- | src/tests/aoc-2022-14/Cargo.toml | 17 | ||||
-rw-r--r-- | src/tests/aoc-2022-14/benches/main.rs | 34 | ||||
-rw-r--r-- | src/tests/aoc-2022-14/src/lib.rs | 170 | ||||
-rw-r--r-- | src/tests/aoc-2022-14/src/types.pr.rs | 12 |
6 files changed, 243 insertions, 0 deletions
diff --git a/src/tests/Cargo.lock b/src/tests/Cargo.lock index 244aab4..8c4d551 100644 --- a/src/tests/Cargo.lock +++ b/src/tests/Cargo.lock @@ -39,6 +39,15 @@ dependencies = [ ] [[package]] +name = "aoc-2022-14" +version = "0.1.0" +dependencies = [ + "criterion", + "primrose-library", + "rand", +] + +[[package]] name = "atty" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/src/tests/Cargo.toml b/src/tests/Cargo.toml index c405635..a8d75c8 100644 --- a/src/tests/Cargo.toml +++ b/src/tests/Cargo.toml @@ -4,6 +4,7 @@ members = [ "aoc-2021-09", "aoc-2022-08", "aoc-2022-09", + "aoc-2022-14", "example_sets", "example_stack", "prime_sieve" diff --git a/src/tests/aoc-2022-14/Cargo.toml b/src/tests/aoc-2022-14/Cargo.toml new file mode 100644 index 0000000..b8c00f5 --- /dev/null +++ b/src/tests/aoc-2022-14/Cargo.toml @@ -0,0 +1,17 @@ +[package] +name = "aoc-2022-14" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +rand = { workspace = true } +primrose-library = { path = "../../crates/library" } + +[dev-dependencies] +criterion = { workspace = true } + +[[bench]] +name = "main" +harness = false diff --git a/src/tests/aoc-2022-14/benches/main.rs b/src/tests/aoc-2022-14/benches/main.rs new file mode 100644 index 0000000..5ac0f39 --- /dev/null +++ b/src/tests/aoc-2022-14/benches/main.rs @@ -0,0 +1,34 @@ +use aoc_2022_14::tests::*; +use criterion::{criterion_group, criterion_main, BatchSize, BenchmarkId, Criterion}; +use rand::{rngs::StdRng, SeedableRng}; + +fn run_benches(c: &mut Criterion) { + c.bench_with_input( + BenchmarkId::new("part1", "small"), + &SMALL_INPUT, + |b, &inp| b.iter_batched_ref(|| parse_input(inp), |w| w.part1(), BatchSize::SmallInput), + ); + c.bench_with_input( + BenchmarkId::new("part1", "large"), + &LARGE_INPUT, + |b, &inp| b.iter_batched_ref(|| parse_input(inp), |w| w.part1(), BatchSize::SmallInput), + ); + + c.bench_with_input( + BenchmarkId::new("part2", "small"), + &SMALL_INPUT, + |b, &inp| b.iter_batched_ref(|| parse_input(inp), |w| w.part2(), BatchSize::SmallInput), + ); + c.bench_with_input( + BenchmarkId::new("part2", "large"), + &LARGE_INPUT, + |b, &inp| b.iter_batched_ref(|| parse_input(inp), |w| w.part2(), BatchSize::SmallInput), + ); +} + +criterion_group!( + name = benches; + config = Criterion::default().sample_size(10); + targets = run_benches +); +criterion_main!(benches); diff --git a/src/tests/aoc-2022-14/src/lib.rs b/src/tests/aoc-2022-14/src/lib.rs new file mode 100644 index 0000000..3494e2e --- /dev/null +++ b/src/tests/aoc-2022-14/src/lib.rs @@ -0,0 +1,170 @@ +#![feature(type_alias_impl_trait)] + +use primrose_library::traits::*; +use std::{ + cmp::{max, min}, + ops::Add, +}; + +mod types; +use types::*; + +pub struct World { + structures: Vec<Structure>, + sand_grains: Set<Pos>, + floor_y: isize, +} + +impl World { + pub fn new(structures: Vec<Structure>) -> Self { + let floor_y = structures + .iter() + .map(|s| s.points.iter().map(|p| p.1).max().unwrap()) + .max() + .unwrap() + + 2; + + Self { + structures, + floor_y, + sand_grains: Default::default(), + } + } + + pub fn part1(&mut self) -> usize { + (0..).take_while(|_| !self.step()).count() + } + + pub fn part2(&mut self) -> usize { + self.structures.push(Structure::new({ + let mut l: List<Pos> = Default::default(); + l.insert(Pos(isize::MIN, self.floor_y)); + l.insert(Pos(isize::MAX, self.floor_y)); + l + })); + (0..).take_while(|_| !self.step()).count() + } + + fn step(&mut self) -> bool { + let mut pos = Pos(500, 0); + if self.sand_grains.contains(&pos) { + return true; + } + loop { + if pos.1 > self.floor_y { + return true; + } + pos = if !self.inhabited(pos + Pos(0, 1)) { + pos + Pos(0, 1) + } else if !self.inhabited(pos + Pos(-1, 1)) { + pos + Pos(-1, 1) + } else if !self.inhabited(pos + Pos(1, 1)) { + pos + Pos(1, 1) + } else { + self.sand_grains.insert(pos); + return false; + }; + } + } + + fn inhabited(&self, pos: Pos) -> bool { + self.structures.iter().any(|s| s.contains(pos)) || self.sand_grains.contains(&pos) + } +} + +pub struct Structure { + points: List<Pos>, + bbox_min: Pos, + bbox_max: Pos, +} + +impl Structure { + pub fn new(points: List<Pos>) -> Self { + let bbox_min = points.iter().fold(Pos(isize::MAX, isize::MAX), |acc, pos| { + Pos(min(acc.0, pos.0), min(acc.1, pos.1)) + }); + let bbox_max = points.iter().fold(Pos(isize::MAX, isize::MAX), |acc, pos| { + Pos(max(acc.0, pos.0), max(acc.1, pos.1)) + }); + + Structure { + points, + bbox_max, + bbox_min, + } + } + + fn contains(&self, pos: Pos) -> bool { + if pos.0 < self.bbox_min.0 + || pos.0 > self.bbox_max.0 + || pos.1 < self.bbox_min.1 + || pos.1 > self.bbox_max.1 + { + return false; + } + for (a, b) in self.points.iter().zip(self.points.iter().skip(1)) { + if a.0 == b.0 { + if pos.0 == a.0 && pos.1 >= min(a.1, b.1) && pos.1 <= max(a.1, b.1) { + return true; + } + } else { + // a.1 == b.1 + if pos.1 == a.1 && pos.0 >= min(a.0, b.0) && pos.0 <= max(a.0, b.0) { + return true; + } + } + } + return false; + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +pub struct Pos(isize, isize); + +impl Add for Pos { + type Output = Pos; + + fn add(self, rhs: Self) -> Self::Output { + Pos(self.0 + rhs.0, self.1 + rhs.1) + } +} + +pub mod tests { + use super::*; + + pub fn parse_input(input: &str) -> World { + World::new(input.lines().map(parse_structure).collect()) + } + + pub fn parse_structure(l: &str) -> Structure { + Structure::new(l.split(" -> ").map(|s| parse_pos(s)).collect()) + } + + pub fn parse_pos(s: &str) -> Pos { + let (x, y) = s.split_once(',').unwrap(); + Pos(x.parse().unwrap(), y.parse().unwrap()) + } + + #[test] + fn small() { + assert_eq!(parse_input(SMALL_INPUT).part1(), 24, "part 1"); + assert_eq!(parse_input(SMALL_INPUT).part2(), 93, "part 2"); + } + + #[test] + fn large() { + assert_eq!(parse_input(LARGE_INPUT).part1(), 0, "part 1"); + assert_eq!(parse_input(LARGE_INPUT).part2(), 26360, "part 2"); + } + + pub const SMALL_INPUT: &str = "498,4 -> 498,6 -> 496,6 +503,4 -> 502,4 -> 502,9 -> 494,9"; + + pub const LARGE_INPUT: &str = "515,60 -> 515,52 -> 515,60 -> 517,60 -> 517,55 -> 517,60 -> 519,60 -> 519,50 -> 519,60 -> 521,60 -> 521,56 -> 521,60 -> 523,60 -> 523,53 -> 523,60 -> 525,60 -> 525,53 -> 525,60 +515,60 -> 515,52 -> 515,60 -> 517,60 -> 517,55 -> 517,60 -> 519,60 -> 519,50 -> 519,60 -> 521,60 -> 521,56 -> 521,60 -> 523,60 -> 523,53 -> 523,60 -> 525,60 -> 525,53 -> 525,60 +502,32 -> 507,32 +522,47 -> 522,43 -> 522,47 -> 524,47 -> 524,41 -> 524,47 -> 526,47 -> 526,39 -> 526,47 -> 528,47 -> 528,39 -> 528,47 +516,32 -> 521,32 +501,161 -> 501,152 -> 501,161 -> 503,161 -> 503,157 -> 503,161 -> 505,161 -> 505,155 -> 505,161 -> 507,161 -> 507,154 -> 507,161 -> 509,161 -> 509,155 -> 509,161 +512,30 -> 517,30"; +} diff --git a/src/tests/aoc-2022-14/src/types.pr.rs b/src/tests/aoc-2022-14/src/types.pr.rs new file mode 100644 index 0000000..9d36060 --- /dev/null +++ b/src/tests/aoc-2022-14/src/types.pr.rs @@ -0,0 +1,12 @@ +/*SPEC* +property order-preserved<T> { + \c <: (Container, Indexable) -> (forall \x -> ((equal? (op-last ((op-insert c) x))) x)) +} + +property unique<T> { + \c <: (Container) -> ((for-all-elems c) \a -> ((unique-count? a) c)) +} + +type List<T> = {c impl (Container, Indexable) | (order-preserved c)} +type Set<T> = {c impl (Container) | (unique c)} + *ENDSPEC*/ |