diff --git a/14/Cargo.lock b/14/Cargo.lock index 511b137..aa838b8 100644 --- a/14/Cargo.lock +++ b/14/Cargo.lock @@ -13,7 +13,7 @@ name = "day14" version = "0.1.0" dependencies = [ "itertools", - "num", + "ndarray", ] [[package]] @@ -32,28 +32,26 @@ dependencies = [ ] [[package]] -name = "num" -version = "0.4.1" +name = "matrixmultiply" +version = "0.3.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b05180d69e3da0e530ba2a1dae5110317e49e3b7f3d41be227dc5f92e49ee7af" +checksum = "7574c1cf36da4798ab73da5b215bbf444f50718207754cb522201d78d1cd0ff2" dependencies = [ - "num-bigint", - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", + "autocfg", + "rawpointer", ] [[package]] -name = "num-bigint" -version = "0.4.4" +name = "ndarray" +version = "0.15.6" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "608e7659b5c3d7cba262d894801b9ec9d00de989e8a82bd4bef91d08da45cdc0" +checksum = "adb12d4e967ec485a5f71c6311fe28158e9d6f4bc4a447b474184d0f91a8fa32" dependencies = [ - "autocfg", + "matrixmultiply", + "num-complex", "num-integer", "num-traits", + "rawpointer", ] [[package]] @@ -75,29 +73,6 @@ dependencies = [ "num-traits", ] -[[package]] -name = "num-iter" -version = "0.1.43" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.4.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0" -dependencies = [ - "autocfg", - "num-bigint", - "num-integer", - "num-traits", -] - [[package]] name = "num-traits" version = "0.2.17" @@ -106,3 +81,9 @@ checksum = "39e3200413f237f41ab11ad6d161bc7239c84dcb631773ccd7de3dfe4b5c267c" dependencies = [ "autocfg", ] + +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" diff --git a/14/Cargo.toml b/14/Cargo.toml index 3c1ab53..50fe052 100644 --- a/14/Cargo.toml +++ b/14/Cargo.toml @@ -7,4 +7,4 @@ edition = "2021" [dependencies] itertools = "0.12.0" -num = "0.4.1" +ndarray = { version = "0.15.6" } diff --git a/14/src/main.rs b/14/src/main.rs index 8fd4665..45c7375 100644 --- a/14/src/main.rs +++ b/14/src/main.rs @@ -1,6 +1,7 @@ -use itertools::Either; +use ndarray::*; use std::collections::HashMap; -use std::fmt::{Display, Write}; + +use std::fmt::Display; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; use std::time::Instant; @@ -30,7 +31,7 @@ fn main() { #[derive(Debug)] struct Platform { - rows: Vec>, + matrix: Array2, width: usize, height: usize, } @@ -41,7 +42,11 @@ impl From> for Platform { let width = rows[0].len(); let height = rows.len(); Self { - rows, + matrix: Array2::from_shape_vec( + (height, width), + rows.iter().flat_map(|row| row.iter().map(|c| *c)).collect(), + ) + .unwrap(), width, height, } @@ -62,93 +67,66 @@ struct West; trait Direction { const REVERSED: bool; const AXIS: Axis; + const ROTATIONS: usize; } impl Direction for North { const REVERSED: bool = false; const AXIS: Axis = Axis::Columns; + const ROTATIONS: usize = 0; } impl Direction for South { const REVERSED: bool = true; const AXIS: Axis = Axis::Columns; + const ROTATIONS: usize = 2; // 180 } impl Direction for East { const REVERSED: bool = true; const AXIS: Axis = Axis::Rows; + const ROTATIONS: usize = 1; // CCW } impl Direction for West { const REVERSED: bool = false; const AXIS: Axis = Axis::Rows; + const ROTATIONS: usize = 3; // CW } impl<'a> Platform { - fn col(&self, col: usize) -> Vec { - self.rows.iter().map(|row| row[col]).collect() - } - fn replace_col(&mut self, idx: usize, col: Vec) { - for row_idx in 0..col.len() { - self.rows[row_idx][idx] = col[row_idx]; - } - } fn roll(&mut self) { - match T::AXIS { - Axis::Columns => { - for idx in 0..self.width { - let new = self.roll_one::(idx); - self.replace_col(idx, new); - } + // Get a view into the rotated matrix with the target direction to the north + let mut view = self.matrix.view_mut(); + + match T::ROTATIONS % 4 { + 0 => {} + 1 => { + view.invert_axis(Axis(1)); + view.swap_axes(0, 1); } - Axis::Rows => { - for idx in 0..self.height { - let new = self.roll_one::(idx); - self.rows[idx].copy_from_slice(new.as_slice()); + 2 => { + view.invert_axis(Axis(0)); + view.invert_axis(Axis(1)); + } + 3 => { + view.swap_axes(0, 1); + view.invert_axis(Axis(1)); + } + _ => unreachable!(), + } + Self::roll_rocks(view); + } + fn roll_rocks(mut view: ArrayBase, Dim<[usize; 2]>>) { + let axis_len = view.len_of(Axis(1)); + for mut col in view.columns_mut() { + for inner_idx in 1..axis_len { + if col[inner_idx] == 'O' { + let lower_limit = (0..inner_idx).rev().find(|i| col[*i] == '#').unwrap_or(0); + if let Some(empty_pos) = (lower_limit..inner_idx).find(|i| col[*i] == '.') { + col.swap(inner_idx, empty_pos); + } } } } } - fn roll_rock_at(old: &mut Vec, inner_idx: usize) { - // Find the first # rock or the edge of the map, we can't look beyond this for a resting position - if T::REVERSED { - // The furthest possible resting position is the first # below us, or the edge of the map - let upper_limit = (inner_idx..old.len() - 1) - .find(|c| old[*c] == '#') - .unwrap_or(old.len()); - - // We will roll to the position of the furthest '.' before the upper limit - if let Some(empty_pos) = (inner_idx..upper_limit).rev().find(|i| old[*i] == '.') { - old.swap(inner_idx, empty_pos); - } - } else { - // the furthest possible resting position is the first # above us, or the edge - let lower_limit = (0..inner_idx).rev().find(|c| old[*c] == '#').unwrap_or(0); - - // We will roll to the position of the furthest '.' before the lower limit - if let Some(empty_pos) = (lower_limit..inner_idx).find(|i| old[*i] == '.') { - old.swap(inner_idx, empty_pos); - } - } - } - - fn roll_one(&mut self, idx: usize) -> Vec { - let mut old = match T::AXIS { - Axis::Columns => self.col(idx), - Axis::Rows => self.rows[idx].clone(), - }; - let idx_iter: Either<_, _> = match T::REVERSED { - true => Either::Left((0..old.len()).rev()), - false => Either::Right(0..old.len()), - }; - let end_idx = if T::REVERSED { old.len() } else { 0 }; - for inner_idx in idx_iter { - match old[inner_idx] { - '.' | '#' => continue, - 'O' if inner_idx == end_idx => continue, - 'O' => Self::roll_rock_at::(&mut old, inner_idx), - _ => panic!("invalid character"), - } - } - old - } fn score(&self) -> u64 { match T::AXIS { Axis::Columns => self.score_columns::(), @@ -166,10 +144,12 @@ impl<'a> Platform { } } fn score_columns(&self) -> u64 { - (0..self.height) - .map(|row| { - self.rows[row].iter().filter(|c| **c == 'O').count() - * self.row_or_column_score::(row) + self.matrix + .rows() + .into_iter() + .enumerate() + .map(|(idx, row)| { + row.iter().filter(|c| **c == 'O').count() * self.row_or_column_score::(idx) }) .sum::() as u64 } @@ -182,13 +162,13 @@ impl<'a> Platform { // find the first loop, return the iteration count when we first saw it and when we saw it again fn find_loop(&mut self) -> (usize, usize) { - let mut first_seen: HashMap>, usize> = HashMap::new(); - first_seen.insert(self.rows.clone(), 0); + let mut first_seen = HashMap::new(); + first_seen.insert(self.matrix.clone(), 0); let mut i = 0; loop { self.roll_cycle(); i += 1; - if let Some(first_idx) = first_seen.insert(self.rows.clone(), i) { + if let Some(first_idx) = first_seen.insert(self.matrix.clone(), i) { return (first_idx, i); } } @@ -200,13 +180,7 @@ impl<'a> Platform { impl Display for Platform { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for row in &self.rows { - for c in row { - f.write_char(*c)?; - } - writeln!(f)?; - } - writeln!(f) + self.matrix.fmt(f) } }