aboutsummaryrefslogtreecommitdiff
path: root/src/tests/aoc-2022-14
diff options
context:
space:
mode:
Diffstat (limited to 'src/tests/aoc-2022-14')
-rw-r--r--src/tests/aoc-2022-14/Cargo.toml17
-rw-r--r--src/tests/aoc-2022-14/benches/main.rs34
-rw-r--r--src/tests/aoc-2022-14/src/lib.rs170
-rw-r--r--src/tests/aoc-2022-14/src/types.pr.rs12
4 files changed, 233 insertions, 0 deletions
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*/