From 23ac043d8e0daf5b264733f69a88f4e1461f0ca9 Mon Sep 17 00:00:00 2001 From: Malte Tammena Date: Sun, 18 Dec 2022 07:33:34 +0100 Subject: [PATCH] Solving --- Cargo.lock | 340 ++++++++++++++++++++++++++++++++++++++++++++ day14/Cargo.toml | 3 + day14/src/cave.rs | 302 ++++++++++++++++++++------------------- day14/src/main.rs | 14 +- day15/Cargo.toml | 1 + day15/input | 23 +++ day15/src/main.rs | 164 ++++++++++++++++++++- day16/Cargo.toml | 3 + day16/input.example | 4 + day16/src/main.rs | 192 ++++++++++++++++++++++++- day17/src/main.rs | 247 +++++++++++++++++++++++--------- 11 files changed, 1074 insertions(+), 219 deletions(-) create mode 100644 day15/input create mode 100644 day16/input.example diff --git a/Cargo.lock b/Cargo.lock index 7ee1150..9880594 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,12 +2,55 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "ahash" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bf6ccdb167abbf410dcb915cabd428929d7f6a04980b54a11f26a39f1c7f7107" +dependencies = [ + "cfg-if", + "once_cell", + "version_check", +] + +[[package]] +name = "aho-corasick" +version = "0.7.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac" +dependencies = [ + "memchr", +] + [[package]] name = "arrays" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dbdbb2877d26e0647c6b8125802b2ecf2dc2a28d864dde41e6f9b9a54da08fe" +[[package]] +name = "async-trait" +version = "0.1.59" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "31e6e93155431f3931513b243d371981bb2770112b370c82745a1d19d2f99364" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "async_once" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ce4f10ea3abcd6617873bae9f91d1c5332b4a778bd9ce34d0cd517474c1de82" + +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + [[package]] name = "beef" version = "0.5.2" @@ -20,6 +63,58 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec_simd" +version = "0.20.5" +source = "git+https://github.com/Imberflur/bitvec_simd?branch=master#b2c1df36d8fc015b0c2494305f3be70451e4f763" +dependencies = [ + "smallvec", + "wide", +] + +[[package]] +name = "bytemuck" +version = "1.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaa3a8d9a1ca92e282c96a32d6511b695d7d994d1d102ba85d279f9b2756947f" + +[[package]] +name = "cached" +version = "0.40.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72b4147cd94d5fbdc2ab71b11d50a2f45493625576b3bb70257f59eedea69f3d" +dependencies = [ + "async-trait", + "async_once", + "cached_proc_macro", + "cached_proc_macro_types", + "futures", + "hashbrown 0.12.3", + "instant", + "lazy_static", + "once_cell", + "thiserror", + "tokio", +] + +[[package]] +name = "cached_proc_macro" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "751f7f4e7a091545e7f6c65bacc404eaee7e87bfb1f9ece234a1caa173dc16f2" +dependencies = [ + "cached_proc_macro_types", + "darling", + "quote", + "syn", +] + +[[package]] +name = "cached_proc_macro_types" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a4f925191b4367301851c6d99b09890311d74b0d43f274c0b34c86d308a3663" + [[package]] name = "cc" version = "1.0.77" @@ -76,6 +171,41 @@ version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "882e392738ed515520f38708166e9efec85ee154f80bf1fc64510e2fc54bf481" +[[package]] +name = "darling" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.13.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "day01" version = "0.1.0" @@ -175,14 +305,27 @@ dependencies = [ [[package]] name = "day14" version = "0.1.0" +dependencies = [ + "bitvec_simd", + "hashbrown 0.13.1", + "termion", +] [[package]] name = "day15" version = "0.1.0" +dependencies = [ + "regex", +] [[package]] name = "day16" version = "0.1.0" +dependencies = [ + "bitvec_simd", + "cached", + "regex", +] [[package]] name = "day17" @@ -243,6 +386,67 @@ version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" +[[package]] +name = "futures" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38390104763dc37a5145a53c29c63c1290b5d316d6086ec32c293f6736051bb0" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-sink", + "futures-task", + "futures-util", +] + +[[package]] +name = "futures-channel" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "52ba265a92256105f45b719605a571ffe2d1f0fea3807304b522c1d778f79eed" +dependencies = [ + "futures-core", + "futures-sink", +] + +[[package]] +name = "futures-core" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04909a7a7e4633ae6c4a9ab280aeb86da1236243a77b694a49eacd659a4bd3ac" + +[[package]] +name = "futures-io" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "00f5fb52a06bdcadeb54e8d3671f8888a39697dcb0b81b23b55174030427f4eb" + +[[package]] +name = "futures-sink" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39c15cf1a4aa79df40f1bb462fb39676d0ad9e366c2a33b590d7c66f4f81fcf9" + +[[package]] +name = "futures-task" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2ffb393ac5d9a6eaa9d3fdf37ae2776656b706e200c8e16b1bdb227f5198e6ea" + +[[package]] +name = "futures-util" +version = "0.3.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "197676987abd2f9cadff84926f410af1c183608d36641465df73ae8211dc65d6" +dependencies = [ + "futures-core", + "futures-sink", + "futures-task", + "pin-project-lite", + "pin-utils", +] + [[package]] name = "getrandom" version = "0.2.8" @@ -254,6 +458,21 @@ dependencies = [ "wasi", ] +[[package]] +name = "hashbrown" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" + +[[package]] +name = "hashbrown" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ff8ae62cd3a9102e5637afc8452c55acf3844001bd5374e0b0bd7b6616c038" +dependencies = [ + "ahash", +] + [[package]] name = "heck" version = "0.4.0" @@ -269,6 +488,21 @@ dependencies = [ "libc", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + [[package]] name = "io-lifetimes" version = "1.0.3" @@ -361,6 +595,12 @@ dependencies = [ "syn", ] +[[package]] +name = "memchr" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" + [[package]] name = "numtoa" version = "0.1.0" @@ -379,6 +619,18 @@ version = "6.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee" +[[package]] +name = "pin-project-lite" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116" + +[[package]] +name = "pin-utils" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" + [[package]] name = "ppv-lite86" version = "0.2.17" @@ -475,6 +727,17 @@ dependencies = [ "redox_syscall", ] +[[package]] +name = "regex" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e076559ef8e241f2ae3479e36f97bd5741c0330689e217ad51ce2c76808b868a" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + [[package]] name = "regex-syntax" version = "0.6.28" @@ -495,6 +758,30 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "safe_arch" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "serde" +version = "1.0.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e326c9ec8042f1b5da33252c8a37e9ffbd2c9bef0155215b6e6c80c790e05f91" + +[[package]] +name = "smallvec" +version = "1.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0" +dependencies = [ + "serde", +] + [[package]] name = "strsim" version = "0.10.0" @@ -543,6 +830,49 @@ dependencies = [ "redox_termios", ] +[[package]] +name = "thiserror" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "10deb33631e3c9018b9baf9dcbbc4f737320d2b576bac10f6aefa048fa407e3e" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982d17546b47146b28f7c22e3d08465f6b8903d0ea13c1660d9d84a6e7adcdbb" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "tokio" +version = "1.23.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eab6d665857cc6ca78d6e80303a02cea7a7851e85dfbd77cbdc09bd129f1ef46" +dependencies = [ + "autocfg", + "pin-project-lite", + "tokio-macros", + "windows-sys", +] + +[[package]] +name = "tokio-macros" +version = "1.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d266c00fde287f55d3f1c3e96c500c362a2b8c695076ec180f27918820bc6df8" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "unicode-ident" version = "1.0.5" @@ -561,6 +891,16 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" +[[package]] +name = "wide" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae41ecad2489a1655c8ef8489444b0b113c0a0c795944a3572a0931cf7d2525c" +dependencies = [ + "bytemuck", + "safe_arch", +] + [[package]] name = "winapi" version = "0.3.9" diff --git a/day14/Cargo.toml b/day14/Cargo.toml index 9b41072..2f9e2e4 100644 --- a/day14/Cargo.toml +++ b/day14/Cargo.toml @@ -8,3 +8,6 @@ build = true app = true [dependencies] +bitvec_simd = { git = "https://github.com/Imberflur/bitvec_simd", branch = "master" } +hashbrown = { version = "0.13.1", features = ["nightly"] } +termion = "2.0.1" diff --git a/day14/src/cave.rs b/day14/src/cave.rs index b24deff..df9c5ca 100644 --- a/day14/src/cave.rs +++ b/day14/src/cave.rs @@ -1,9 +1,14 @@ -use std::ops::{Index, IndexMut, Sub}; +use std::ops::Sub; + +use bitvec_simd::BitVec; +use hashbrown::HashMap; +use termion::color; const SOURCE: Point = Point::new(500, 0); -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub enum Material { + #[default] Air, Stone, Sand, @@ -12,40 +17,24 @@ pub enum Material { pub enum InsertResult { Ok, Overflow, + ReachedTop, } pub enum GravityApplyResult { Moved, Fixed, + Outside, + ReachedTop, } -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub struct Point([u16; 2]); impl Point { - const MAX: Self = Point::new(u16::MAX, u16::MAX); - const MIN: Self = Point::new(u16::MIN, u16::MIN); - pub const fn new(x: u16, y: u16) -> Self { Self::from([x, y]) } - pub const fn maximize(&self, other: &Self) -> Self { - let sx = self.0[0]; - let sy = self.0[1]; - let ox = other.0[0]; - let oy = other.0[1]; - Point::new(sx.max(ox), sy.max(oy)) - } - - pub const fn minimize(&self, other: &Self) -> Self { - let sx = self.0[0]; - let sy = self.0[1]; - let ox = other.0[0]; - let oy = other.0[1]; - Point::new(sx.min(ox), sy.min(oy)) - } - pub const fn x(&self) -> u16 { self.0[0] } @@ -54,14 +43,6 @@ impl Point { self.0[1] } - pub fn x_mut(&mut self) -> &mut u16 { - &mut self.0[0] - } - - pub fn y_mut(&mut self) -> &mut u16 { - &mut self.0[1] - } - fn line_to(&self, other: &Point) -> Box> { let sx = self.0[0]; let sy = self.0[1]; @@ -73,136 +54,143 @@ impl Point { Box::from((sy.min(oy)..=sy.max(oy)).map(move |new_y| Point::new(sx, new_y))) } } + + const fn down(&self) -> Point { + Point::new(self.x(), self.y() + 1) + } + + const fn down_left(&self) -> Point { + Point::new(self.x() - 1, self.y() + 1) + } + + const fn down_right(&self) -> Point { + Point::new(self.x() + 1, self.y() + 1) + } } -pub struct Cave { - min: Point, - max: Point, - grid: Vec, +pub struct Cave { + grid: HashMap, + max_stone_y: u16, } -impl Cave { - pub fn from_span(min: Point, max: Point) -> Self { - let width = (max.x() - min.x()) as usize + 1; - let height = (max.y() - min.y()) as usize + 1; - let grid: Vec = ::std::iter::repeat(Material::Air) - .take(width * height) - .collect(); - Cave { min, max, grid } - } - - pub fn get(&self, index: Point) -> &Material { - let normalized = index - self.min; - let idx = normalized.x() as usize + normalized.y() as usize * self.width() as usize; - &self.grid[idx] - } - - fn get_mut(&mut self, index: Point) -> &mut Material { - let normalized = index - self.min; - let idx = normalized.x() as usize + normalized.y() as usize * self.width() as usize; - &mut self.grid[idx] - } - - fn width(&self) -> u16 { - self.max.x() - self.min.x() + 1 +impl Cave { + pub fn get(&self, index: &Point) -> Option<&Material> { + self.grid.get(index) } pub fn insert_sand(&mut self) -> InsertResult { let mut pos = SOURCE; - while self.is_in_bounds(pos) { + loop { match self.apply_gravity(&mut pos) { GravityApplyResult::Moved => {} GravityApplyResult::Fixed => { - self[pos] = Material::Sand; - return InsertResult::Ok; + self.insert(pos, Material::Sand); + break InsertResult::Ok; } + GravityApplyResult::Outside => break InsertResult::Overflow, + GravityApplyResult::ReachedTop => break InsertResult::ReachedTop, } } - InsertResult::Overflow } - fn is_in_bounds(&self, pos: Point) -> bool { - self.min.x() <= pos.x() - && self.min.y() <= pos.y() - && self.max.x() >= pos.x() - && self.max.y() >= pos.y() + pub fn count_sand_using_scanline(&mut self) -> usize { + let mut y = SOURCE.y(); + let mut min_x = SOURCE.x(); + let mut max_x = SOURCE.x(); + let mut bits = BitVec::zeros(SOURCE.x() as usize); + let mut total = 0; + bits.set(SOURCE.x() as usize, true); + while !bits.is_empty() { + if y == self.max_stone_y + 2 { + break; + } + let mut last_left_bit = false; + for x in min_x..=max_x { + let mat = self.get(&Point::new(x, y)).copied().unwrap_or_default(); + let set_bit = bits.get(x as usize).unwrap_or_default() + || last_left_bit + || bits.get(x as usize + 1).unwrap_or_default(); + last_left_bit = bits.get(x as usize).unwrap_or_default(); + if mat == Material::Stone { + bits.set(x as usize, false); + } else if set_bit && mat == Material::Air { + bits.set(x as usize, true); + } + } + min_x -= 1; + max_x += 1; + y += 1; + total += bits.count_ones(); + } + total } fn apply_gravity(&self, pos: &mut Point) -> GravityApplyResult { - if pos.y() == self.max.y() { - *pos.y_mut() += 1; + if HAS_FLOOR && pos.y() == self.max_stone_y + 1 { + GravityApplyResult::Fixed + } else if !HAS_FLOOR && pos.y() >= self.max_stone_y { + *pos = pos.down(); + GravityApplyResult::Outside + } else if self.is_air(&pos.down()) { + *pos = pos.down(); GravityApplyResult::Moved - } else if let Material::Air = &self[Point::new(pos.x(), pos.y().saturating_add(1))] { - *pos.y_mut() += 1; + } else if self.is_air(&pos.down_left()) { + *pos = pos.down_left(); GravityApplyResult::Moved - } else if let Material::Air = - &self[Point::new(pos.x().saturating_sub(1), pos.y().saturating_add(1))] - { - *pos.y_mut() += 1; - *pos.x_mut() -= 1; - GravityApplyResult::Moved - } else if let Material::Air = - &self[Point::new(pos.x().saturating_add(1), pos.y().saturating_add(1))] - { - *pos.y_mut() += 1; - *pos.x_mut() += 1; + } else if self.is_air(&pos.down_right()) { + *pos = pos.down_right(); GravityApplyResult::Moved + } else if *pos == SOURCE { + GravityApplyResult::ReachedTop } else { GravityApplyResult::Fixed } } - fn height(&self) -> u16 { - self.max.y() - self.min.y() + 1 - } -} - -impl FromIterator> for Cave { - fn from_iter>>(iter: T) -> Self { - let (stone_points, min, max) = iter.into_iter().fold( - (vec![], Point::MAX, Point::MIN), - |(mut stone_points, mut min, mut max), points| { - let mut last: Option = None; - for point in points { - min = point.minimize(&min); - max = point.maximize(&max); - match last { - Some(last) => stone_points.extend(last.line_to(&point)), - // Push the first point manually - None => stone_points.push(point.clone()), - } - last = Some(point); - } - (stone_points, min, max) - }, - ); - let min = min.minimize(&SOURCE); - let max = max.maximize(&SOURCE); - let min = Point::new(min.x().saturating_sub(1), min.y().saturating_sub(1)); - let max = Point::new(max.x().saturating_add(1), max.y().saturating_add(1)); - let mut cave = Cave::from_span(min, max); - for point in stone_points { - cave[point] = Material::Stone; + fn is_air(&self, pos: &Point) -> bool { + match self.get(pos) { + Some(Material::Air) | None => true, + _ => false, + } + } + + pub fn new() -> Self { + Self { + grid: HashMap::new(), + max_stone_y: 0, + } + } + + fn insert(&mut self, pos: Point, material: Material) -> Option { + if material == Material::Stone { + self.max_stone_y = self.max_stone_y.max(pos.y()); + } + self.grid.insert(pos, material) + } +} + +impl FromIterator> for Cave { + fn from_iter>>(iter: T) -> Self { + let stone_points = iter.into_iter().fold(vec![], |mut stone_points, points| { + let mut last: Option = None; + for point in points { + match last { + Some(last) => stone_points.extend(last.line_to(&point)), + // Push the first point manually + None => stone_points.push(point.clone()), + } + last = Some(point); + } + stone_points + }); + let mut cave = Cave::new(); + for point in stone_points { + cave.insert(point, Material::Stone); } - println!("{cave}"); cave } } -impl Index for Cave { - type Output = Material; - - fn index(&self, index: Point) -> &Self::Output { - self.get(index) - } -} - -impl IndexMut for Cave { - fn index_mut(&mut self, index: Point) -> &mut Self::Output { - self.get_mut(index) - } -} - impl const From<[u16; 2]> for Point { fn from(raw: [u16; 2]) -> Self { Self(raw) @@ -217,26 +205,54 @@ impl const Sub for Point { } } -impl std::fmt::Display for Cave { +impl std::fmt::Display for Cave { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for y in self.min.y()..=self.max.y() { - for x in self.min.x()..=self.max.x() { - if Point::new(x, y) == SOURCE { - write!(f, "@")?; + let (min_x, max_x, min_y, max_y) = self.grid.keys().fold( + (u16::MAX, u16::MIN, u16::MAX, u16::MIN), + |(min_x, max_x, min_y, max_y), pos| { + ( + min_x.min(pos.x()), + max_x.max(pos.x()), + min_y.min(pos.y()), + max_y.max(pos.y()), + ) + }, + ); + let min_x = min_x.min(SOURCE.x()); + let max_x = max_x.max(SOURCE.x()); + let min_y = min_y.min(SOURCE.y()); + let max_y = max_y.max(SOURCE.y()); + for y in min_y..=max_y { + for x in min_x..=max_x { + let pos = Point::new(x, y); + let material = self.get(&pos); + if pos == SOURCE { + write!(f, "{}", color::Fg(color::Green))? } else { - write!( - f, - "{}", - match self[Point::new(x, y)] { - Material::Air => ".", - Material::Sand => "o", - Material::Stone => "#", + match material { + Some(Material::Air) | None => { + write!(f, "{}", color::Fg(color::Rgb(128, 128, 128))) } - )?; - } + Some(Material::Stone) => write!(f, "{}", color::Fg(color::LightRed)), + Some(Material::Sand) => write!(f, "{}", color::Fg(color::Yellow)), + }? + }; + write!( + f, + "{}", + if pos == SOURCE { + "@" + } else { + match material { + Some(Material::Air) | None => ".", + Some(Material::Stone) => "#", + Some(Material::Sand) => "o", + } + } + )?; } writeln!(f)?; } - writeln!(f) + writeln!(f, "{}", color::Fg(color::Reset)) } } diff --git a/day14/src/main.rs b/day14/src/main.rs index f7204be..d688027 100644 --- a/day14/src/main.rs +++ b/day14/src/main.rs @@ -30,21 +30,27 @@ fn parse_line>(line: S) -> Vec { } fn solve_first_puzzle(reader: R) -> usize { - let mut cave: Cave = reader + let mut cave: Cave = reader .lines() .map(Result::unwrap) .map(|line| parse_line(&line)) .collect(); let mut counter = 0; while let InsertResult::Ok = cave.insert_sand() { - println!("{cave}"); counter += 1; } + println!("{cave}"); counter } fn solve_second_puzzle(reader: R) -> usize { - todo!() + let mut cave: Cave = reader + .lines() + .map(Result::unwrap) + .map(|line| parse_line(&line)) + .collect(); + println!("{cave}"); + cave.count_sand_using_scanline() } fn main() { @@ -81,6 +87,6 @@ mod tests { #[test] fn example_on_second() { let reader = Cursor::new(include_str!("../input.test")); - assert_eq!(solve_second_puzzle(reader), 140); + assert_eq!(solve_second_puzzle(reader), 93); } } diff --git a/day15/Cargo.toml b/day15/Cargo.toml index 1f5694e..7b05eab 100644 --- a/day15/Cargo.toml +++ b/day15/Cargo.toml @@ -8,3 +8,4 @@ build = true app = true [dependencies] +regex = "1.7.0" diff --git a/day15/input b/day15/input new file mode 100644 index 0000000..f7e996c --- /dev/null +++ b/day15/input @@ -0,0 +1,23 @@ +Sensor at x=325337, y=2568863: closest beacon is at x=-518661, y=2000000 +Sensor at x=3988825, y=837820: closest beacon is at x=4305648, y=2127118 +Sensor at x=1611311, y=2053174: closest beacon is at x=2827226, y=1579510 +Sensor at x=101890, y=3940049: closest beacon is at x=955472, y=3457514 +Sensor at x=3962702, y=2558425: closest beacon is at x=4226981, y=2604726 +Sensor at x=2957890, y=2160813: closest beacon is at x=2827226, y=1579510 +Sensor at x=3907456, y=3325610: closest beacon is at x=3696221, y=3226373 +Sensor at x=3354177, y=3435919: closest beacon is at x=3696221, y=3226373 +Sensor at x=3997379, y=3071868: closest beacon is at x=3696221, y=3226373 +Sensor at x=145143, y=1714962: closest beacon is at x=-518661, y=2000000 +Sensor at x=611563, y=3148864: closest beacon is at x=955472, y=3457514 +Sensor at x=3080405, y=3904777: closest beacon is at x=3696221, y=3226373 +Sensor at x=644383, y=10732: closest beacon is at x=364635, y=-294577 +Sensor at x=3229566, y=1694167: closest beacon is at x=2827226, y=1579510 +Sensor at x=1600637, y=3984884: closest beacon is at x=955472, y=3457514 +Sensor at x=2959765, y=2820860: closest beacon is at x=2491502, y=2897876 +Sensor at x=2235330, y=3427797: closest beacon is at x=2491502, y=2897876 +Sensor at x=2428996, y=210881: closest beacon is at x=2827226, y=1579510 +Sensor at x=369661, y=687805: closest beacon is at x=364635, y=-294577 +Sensor at x=3558476, y=2123614: closest beacon is at x=4305648, y=2127118 +Sensor at x=3551529, y=2825104: closest beacon is at x=3696221, y=3226373 +Sensor at x=64895, y=3577: closest beacon is at x=364635, y=-294577 +Sensor at x=3079531, y=1538659: closest beacon is at x=2827226, y=1579510 diff --git a/day15/src/main.rs b/day15/src/main.rs index e7a11a9..3db0013 100644 --- a/day15/src/main.rs +++ b/day15/src/main.rs @@ -1,3 +1,163 @@ -fn main() { - println!("Hello, world!"); +use regex::Regex; +use std::{ + fs::File, + io::{BufRead, BufReader}, +}; + +const TUNING_FACTOR: i64 = 4_000_000; + +#[derive(Debug, Default, PartialEq, Eq)] +enum SolvePuzzle { + First, + #[default] + Second, +} + +#[derive(Debug, Default)] +struct Sensor { + x: i64, + y: i64, + manhattan_radius: u64, +} + +#[derive(Debug, Default)] +struct Beacon { + x: i64, + y: i64, +} + +impl Sensor { + pub const fn min_x(&self) -> i64 { + self.x - self.manhattan_radius as i64 + } + + pub const fn max_x(&self) -> i64 { + self.x + self.manhattan_radius as i64 + } + + fn is_in_range(&self, x: i64, y: i64) -> bool { + x.abs_diff(self.x) + y.abs_diff(self.y) <= self.manhattan_radius + } +} + +fn parse_input(reader: R) -> (Vec, Vec) { + let regex = + Regex::new(r"^Sensor at x=(-?\d+), y=(-?\d+): closest beacon is at x=(-?\d+), y=(-?\d+)$") + .unwrap(); + reader + .lines() + .map(Result::unwrap) + .map(|line| { + let captures = regex.captures(&line).expect("Regex matches line"); + let sensor_x: i64 = captures + .get(1) + .expect("Sensor X position") + .as_str() + .parse() + .expect("Input not a number"); + let sensor_y: i64 = captures + .get(2) + .expect("Sensor Y position") + .as_str() + .parse() + .expect("Input not a number"); + let beacon_x: i64 = captures + .get(3) + .expect("Beacon X position") + .as_str() + .parse() + .expect("Input not a number"); + let beacon_y: i64 = captures + .get(4) + .expect("Beacon Y position") + .as_str() + .parse() + .expect("Input not a number"); + let manhattan_radius = sensor_x.abs_diff(beacon_x) + sensor_y.abs_diff(beacon_y); + ( + Sensor { + x: sensor_x, + y: sensor_y, + manhattan_radius, + }, + Beacon { + x: beacon_x, + y: beacon_y, + }, + ) + }) + .unzip() +} + +fn solve_first_puzzle(reader: R) -> usize { + let (sensors, beacons) = parse_input(reader); + let min_x = sensors.iter().map(Sensor::min_x).min().unwrap_or_default(); + let max_x = sensors.iter().map(Sensor::max_x).max().unwrap_or_default(); + let mut occupied_counter = 0; + let y = LINE as i64; + for x in min_x..=max_x { + if sensors.iter().any(|sensor| sensor.is_in_range(x, y)) + && !beacons.iter().any(|beacon| beacon.x == x && beacon.y == y) + { + occupied_counter += 1; + } + } + occupied_counter +} + +fn solve_second_puzzle(reader: R) -> i64 { + let (sensors, _beacons) = parse_input(reader); + for y in 0_i64..=MAX_Y { + let mut x = 0; + while x <= MAX_X { + let sensor = sensors.iter().find(|sensor| sensor.is_in_range(x, y)); + match sensor { + Some(sensor) => { + let dist_y = sensor.y.abs_diff(y) as i64; + let width_at_height = sensor.manhattan_radius as i64 - dist_y; + x = sensor.x + width_at_height + 1; + } + // FOUND! + None => return x * TUNING_FACTOR + y, + } + } + } + panic!("No spot found") +} + +fn main() { + let mut args = ::std::env::args().skip(1); + let file = args.next().unwrap_or_else(|| String::from("./input")); + let solve = args + .next() + .map(|arg| match arg.as_str() { + "first" => SolvePuzzle::First, + "second" => SolvePuzzle::Second, + _ => unreachable!(), + }) + .unwrap_or_default(); + let file = BufReader::new(File::open(file).expect("Opening file")); + match solve { + SolvePuzzle::First => println!("{}", solve_first_puzzle::<2_000_000, _>(file)), + SolvePuzzle::Second => println!("{}", solve_second_puzzle::<4_000_000, 4_000_000, _>(file)), + }; +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::*; + + #[test] + fn example_on_first() { + let reader = Cursor::new(include_str!("../input.test")); + assert_eq!(solve_first_puzzle::<10, _>(reader), 26); + } + + #[test] + fn example_on_second() { + let reader = Cursor::new(include_str!("../input.test")); + assert_eq!(solve_second_puzzle::<20, 20, _>(reader), 56000011); + } } diff --git a/day16/Cargo.toml b/day16/Cargo.toml index 25796d6..468ca5d 100644 --- a/day16/Cargo.toml +++ b/day16/Cargo.toml @@ -8,3 +8,6 @@ build = true app = true [dependencies] +cached = "0.40.0" +regex = "1.7.0" +bitvec_simd = { git = "https://github.com/Imberflur/bitvec_simd", branch = "master" } diff --git a/day16/input.example b/day16/input.example new file mode 100644 index 0000000..7e0cd5b --- /dev/null +++ b/day16/input.example @@ -0,0 +1,4 @@ +Valve AA has flow rate=0; tunnel leads to valve BB +Valve BB has flow rate=3; tunnels lead to valves CC, DD +Valve CC has flow rate=18; tunnel leads to valve DD +Valve DD has flow rate=20; tunnels lead to valves BB, CC diff --git a/day16/src/main.rs b/day16/src/main.rs index e7a11a9..e5706ac 100644 --- a/day16/src/main.rs +++ b/day16/src/main.rs @@ -1,3 +1,191 @@ -fn main() { - println!("Hello, world!"); +use std::{ + collections::HashMap, + fs::File, + io::{BufRead, BufReader}, + ops::Deref, +}; + +use bitvec_simd::BitVec; +use cached::proc_macro::cached; +use regex::Regex; + +#[derive(Debug, Default, PartialEq, Eq)] +enum SolvePuzzle { + First, + #[default] + Second, +} + +#[derive(Debug)] +struct Valve { + name: String, + flow: u32, + leads_to: Vec, +} + +#[derive(Debug)] +struct Valves { + inner: Vec, +} + +impl Valves { + pub fn start_idx(&self) -> usize { + self.inner + .iter() + .enumerate() + .find(|(_idx, valve)| valve.name == "AA") + .expect("Where to start now?") + .0 + } +} + +fn parse_input(reader: R) -> Valves { + let regex = Regex::new( + r"Valve ([A-Z]{2}) has flow rate=(\d+); tunnels? leads? to valves? (([A-Z]{2}(, )?)+)", + ) + .unwrap(); + let raw = reader + .lines() + .map(Result::unwrap) + .map(|line| { + let captures = regex.captures(&line).expect("Matched line"); + let name = captures.get(1).expect("name").as_str().to_string(); + let rate: u32 = captures + .get(2) + .expect("flow rate") + .as_str() + .parse() + .expect("flow rate is a number"); + let leads_to = captures + .get(3) + .expect("leads to") + .as_str() + .split(", ") + .map(String::from) + .collect::>(); + (name, rate, leads_to) + }) + .collect::>(); + let indices: HashMap = raw + .iter() + .enumerate() + .map(|(idx, (name, _, _))| (name.clone(), idx)) + .collect(); + let inner = raw + .into_iter() + .map(|(name, flow, leads_to)| { + let leads_to = leads_to + .into_iter() + .map(|name| *indices.get(&name).expect("Pipe exists")) + .collect(); + Valve { + name, + flow, + leads_to, + } + }) + .collect(); + Valves { inner } +} + +#[cached( + key = "(usize, u32, Vec)", + convert = r#"{ (current_valve, time_left, opened.clone().into_usizes()) }"# +)] +fn solve(valves: &Valves, current_valve: usize, time_left: u32, opened: &mut BitVec) -> u32 { + if time_left == 0 { + return 0; + } + let curr = &valves[current_valve]; + let should_skip_opening = curr + .leads_to + .iter() + .filter(|&&idx| !opened[idx]) + .any(|&idx| { + let other = &valves[idx]; + let gain = other.flow as i32 - curr.flow as i32; + let min_gain_needed = other.flow as i32; + (time_left as i32 - 1) * gain > min_gain_needed + }); + if time_left == 25 { + eprintln!("Checking {}: {:>6?}", curr.name, opened); + } + curr.leads_to + .iter() + .map(|&idx| { + if opened[current_valve] || should_skip_opening { + solve(valves, idx, time_left.saturating_sub(1), opened) + } else { + let if_closed = solve(valves, idx, time_left.saturating_sub(1), opened); + opened.set(current_valve, true); + let mut if_opened = solve(valves, idx, time_left.saturating_sub(2), opened); + opened.set(current_valve, false); + if_opened += (time_left - 1) * curr.flow; + if if_closed >= if_opened { + if_closed + } else { + if_opened + } + } + }) + .max() + .unwrap_or_else(|| { + // Leads nowhere + if opened[current_valve] { + 0 + } else { + (time_left.saturating_sub(1)) * curr.flow + } + }) +} + +fn solve_first_puzzle(reader: R) -> u32 { + let valves = parse_input(reader); + let mut opened = BitVec::zeros(valves.len()); + solve(&valves, valves.start_idx(), 30, &mut opened) +} + +fn main() { + let mut args = ::std::env::args().skip(1); + let file = args.next().unwrap_or_else(|| String::from("./input")); + let solve = args + .next() + .map(|arg| match arg.as_str() { + "first" => SolvePuzzle::First, + "second" => SolvePuzzle::Second, + _ => unreachable!(), + }) + .unwrap_or_default(); + let file = BufReader::new(File::open(file).expect("Opening file")); + match solve { + SolvePuzzle::First => println!("{}", solve_first_puzzle(file)), + _ => todo!(), + }; +} + +impl Deref for Valves { + type Target = Vec; + + fn deref(&self) -> &Self::Target { + &self.inner + } +} + +#[cfg(test)] +mod tests { + use std::io::Cursor; + + use super::*; + + #[test] + fn example_on_first() { + let reader = Cursor::new(include_str!("../input.test")); + assert_eq!(solve_first_puzzle(reader), 1651); + } + + #[test] + fn example_on_second() { + let reader = Cursor::new(include_str!("../input.test")); + // todo!() + } } diff --git a/day17/src/main.rs b/day17/src/main.rs index fa9eb8b..891a78a 100644 --- a/day17/src/main.rs +++ b/day17/src/main.rs @@ -20,14 +20,33 @@ enum Wind { Right, } +#[derive(Debug)] +struct Repetition { + from_history_idx: usize, + to_history_idx: usize, + height_diff: usize, +} + struct Cave { - height_map: [usize; CAVE_WIDTH], wind: Vec, wind_pos: usize, + content: Vec<[bool; CAVE_WIDTH]>, + // A list of rocks, their initial wind_pos and final height + history: Vec<(StrangeRock, usize, usize)>, } impl Cave { pub fn max_height(&self) -> usize { - self.height_map.iter().max().copied().unwrap() + if let Some((rev_idx, _)) = self + .content + .iter() + .rev() + .enumerate() + .find(|(_idx, row)| row.iter().any(|&blocked| blocked)) + { + self.content.len() - rev_idx + } else { + 0 + } } pub fn use_wind(&mut self) -> Wind { @@ -36,58 +55,104 @@ impl Cave { wind } - pub fn drop(&mut self, piece: StrangRock) { - let mut piece_height = self.max_height() + 4; + pub fn drop(&mut self, piece: StrangeRock) { + let initial_wind = self.wind_pos; + let piece_positions = piece.positions(); + let curr_max_height = self.max_height(); + let mut height = curr_max_height + 3; let mut pos = 2_usize; - eprintln!("{piece:>10?} {pos} {piece_height}"); loop { match self.use_wind() { Wind::Left if pos > 0 => { - let mut collision = false; - for (offset_idx, offset) in piece.offsets().iter().enumerate() { - if self.height_map[pos - 1 + offset_idx] >= piece_height + offset { - collision = true; - } - } - if !collision { + let is_impossible = (height <= curr_max_height) + && piece_positions + .iter() + .map(|(x, y)| (x + pos - 1, y + height)) + .any(|(x, y)| self.get(x, y)); + if !is_impossible { pos = pos.saturating_sub(1); } } Wind::Right if pos < CAVE_WIDTH.saturating_sub(piece.width()) => { - let mut collision = false; - for (offset_idx, offset) in piece.offsets().iter().enumerate() { - if self.height_map[pos + 1 + offset_idx] >= piece_height + offset { - collision = true; - } - } - if !collision { + let is_impossible = (height <= curr_max_height) + && piece_positions + .iter() + .map(|(x, y)| (x + pos + 1, y + height)) + .any(|(x, y)| self.get(x, y)); + if !is_impossible { pos += 1; } } _ => {} } - piece_height -= 1; - eprintln!("{piece:>10?} {pos} {piece_height}"); - eprintln!(" - {:>2?} {:>2?}", self.height_map, piece.offsets()); - for (offset_idx, &offset) in piece.offsets().iter().enumerate() { - let height_at_pos = self.height_map[pos + offset_idx]; - let piece_height = piece_height + offset; - eprintln!(" - {offset_idx} {offset}: {height_at_pos} {piece_height}"); - if piece_height == height_at_pos { - for (height_idx, &height) in piece.height_offsets().iter().enumerate() { - let total_height = piece_height + height + 1; - self.height_map[pos + height_idx] = total_height; - } - eprintln!(" == {:>2?} {:>2?}", self.height_map, piece.offsets()); - return; - } + let cant_move_down = height == 0 + || piece_positions + .iter() + .map(|(x, y)| (x + pos, y + height - 1)) + .any(|(x, y)| self.get(x, y)); + if !cant_move_down { + height -= 1; + } + if cant_move_down { + piece_positions + .iter() + .map(|(x, y)| (x + pos, y + height)) + .for_each(|(x, y)| self.block(x, y)); + self.history.push((piece, initial_wind, height)); + return; } } } + + pub fn min_repeat(&self) -> usize { + // Everything has to repeat after visiting every variant with every wind + StrangeRock::TOTAL_VARIANT_COUNT * self.wind.len() + } + + pub fn check_repetition(&self) -> Option { + // println!("IS REPEATING? {:>20}", self.history.len()); + let min_repeat = self.min_repeat(); + if self.history.len() <= min_repeat + 429 { + return None; + } + let to_history_idx = self.history.len() - 1 - 429; + let to = &self.history[to_history_idx]; + (min_repeat..) + .take_while(|&idx_sub| idx_sub < self.history.len()) + .map(|idx_sub| self.history.len() - idx_sub) + .find_map(|from_history_idx| { + let from = &self.history[from_history_idx]; + if to.0 == from.0 && to.1 == from.1 && self.content[to.2] == self.content[from.2] { + Some(Repetition { + from_history_idx, + to_history_idx, + height_diff: to.2 - from.2, + }) + } else { + None + } + }) + } + + pub fn get(&mut self, x: usize, y: usize) -> bool { + debug_assert!(x < CAVE_WIDTH); + while y >= self.content.len() { + self.content.push([false; CAVE_WIDTH]); + } + self.content[y][x] + } + + pub fn block(&mut self, x: usize, y: usize) { + debug_assert!(x < CAVE_WIDTH); + while y >= self.content.len() { + self.content.push([false; CAVE_WIDTH]); + } + self.content[y][x] = true; + } } #[derive(Debug, Clone, Copy, PartialEq, Eq)] -enum StrangRock { +enum StrangeRock { // #### Horizontal, // .#. @@ -108,34 +173,25 @@ enum StrangRock { Square, } -impl StrangRock { - const fn width(&self) -> usize { +impl StrangeRock { + pub const TOTAL_VARIANT_COUNT: usize = 5; + pub const fn positions(&self) -> &[(usize, usize)] { match self { - StrangRock::Horizontal => 4, - StrangRock::Plus => 3, - StrangRock::Corner => 3, - StrangRock::Vertical => 1, - StrangRock::Square => 2, + StrangeRock::Horizontal => &[(0, 0), (1, 0), (2, 0), (3, 0)], + StrangeRock::Plus => &[(1, 0), (0, 1), (1, 1), (2, 1), (1, 2)], + StrangeRock::Corner => &[(0, 0), (1, 0), (2, 0), (2, 1), (2, 2)], + StrangeRock::Vertical => &[(0, 0), (0, 1), (0, 2), (0, 3)], + StrangeRock::Square => &[(0, 0), (1, 0), (0, 1), (1, 1)], } } - fn offsets(&self) -> &[usize] { + pub const fn width(&self) -> usize { match self { - StrangRock::Horizontal => &[0, 0, 0, 0], - StrangRock::Plus => &[1, 0, 1], - StrangRock::Corner => &[0, 0, 0], - StrangRock::Vertical => &[0], - StrangRock::Square => &[0, 0], - } - } - - pub fn height_offsets(&self) -> &[usize] { - match self { - StrangRock::Horizontal => &[0, 0, 0, 0], - StrangRock::Plus => &[1, 2, 1], - StrangRock::Corner => &[0, 0, 2], - StrangRock::Vertical => &[3], - StrangRock::Square => &[1, 1], + StrangeRock::Horizontal => 4, + StrangeRock::Plus => 3, + StrangeRock::Corner => 3, + StrangeRock::Vertical => 1, + StrangeRock::Square => 2, } } } @@ -148,22 +204,74 @@ fn read_input(reader: R) -> Cave { .map(Wind::from) .collect(); let wind_pos = 0; - let height_map = [0; CAVE_WIDTH]; Cave { - height_map, wind, wind_pos, + content: vec![], + history: vec![], } } -fn solve_first_puzzle(reader: R) -> usize { +fn solve_first_puzzle(reader: R) -> usize { let mut cave = read_input(reader); - use StrangRock::*; + println!("{}", cave.min_repeat()); + use StrangeRock::*; let pieces = [Horizontal, Plus, Corner, Vertical, Square]; - for piece in pieces.into_iter().cycle().take(2022) { + let mut piece_idx = 0; + let mut current_idx = 0; + let mut additional_height = 0; + let mut have_skipped = false; + while current_idx < ROCK_COUNT { + let piece = pieces[piece_idx]; cave.drop(piece); + if !have_skipped { + if let Some(repetition) = cave.check_repetition() { + eprintln!("{repetition:?}"); + eprintln!("{:?}", cave.history[repetition.from_history_idx]); + eprintln!("{:?}", cave.history[repetition.to_history_idx]); + eprintln!( + "{:?}", + cave.content[cave.history[repetition.from_history_idx].2] + ); + eprintln!( + "{:?}", + cave.content[cave.history[repetition.to_history_idx].2] + ); + let number_of_rocks_that_repeated = + repetition.to_history_idx - repetition.from_history_idx; + let repeat = (ROCK_COUNT - current_idx) / number_of_rocks_that_repeated; + eprintln!("XXXX"); + println!("{number_of_rocks_that_repeated}"); + for _ in 0..number_of_rocks_that_repeated { + piece_idx = (piece_idx + 1) % pieces.len(); + cave.drop(pieces[piece_idx]); + } + eprintln!("XXXX"); + for x in repetition.from_history_idx..repetition.to_history_idx { + assert_eq!( + cave.content[cave.history[x].2], + cave.content[cave.history[x + number_of_rocks_that_repeated].2], + "X: {} Y: {}, *X: {}, *Y: {}", + x, + x + number_of_rocks_that_repeated, + cave.history[x].2, + cave.history[x + number_of_rocks_that_repeated].2 + ); + } + eprintln!("XXXX"); + current_idx += number_of_rocks_that_repeated * repeat; + eprintln!("Skip {}", number_of_rocks_that_repeated * repeat); + eprintln!("To be simulated: {}", ROCK_COUNT - current_idx); + eprintln!("0..{}", cave.content.len()); + additional_height += repetition.height_diff * repeat; + eprintln!("additional height {}", additional_height); + have_skipped = true; + } + } + piece_idx = (piece_idx + 1) % pieces.len(); + current_idx += 1; } - cave.max_height() + cave.max_height() + additional_height } fn main() { @@ -179,8 +287,8 @@ fn main() { .unwrap_or_default(); let file = BufReader::new(File::open(file).expect("Opening file")); match solve { - SolvePuzzle::First => println!("{}", solve_first_puzzle(file)), - _ => todo!(), + SolvePuzzle::First => println!("{}", solve_first_puzzle::<2022, _>(file)), + SolvePuzzle::Second => println!("{}", solve_first_puzzle::<1_000_000_000_000, _>(file)), }; } @@ -203,12 +311,15 @@ mod tests { #[test] fn example_on_first() { let reader = Cursor::new(include_str!("../input.test")); - assert_eq!(solve_first_puzzle(reader), 3068); + assert_eq!(solve_first_puzzle::<2022, _>(reader), 3068); } #[test] fn example_on_second() { let reader = Cursor::new(include_str!("../input.test")); - // todo!() + assert_eq!( + solve_first_puzzle::<1000000000000, _>(reader), + 1514285714288 + ); } }