diff --git a/14/Cargo.lock b/14/Cargo.lock index 3ee74ee..511b137 100644 --- a/14/Cargo.lock +++ b/14/Cargo.lock @@ -12,9 +12,25 @@ checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" name = "day14" version = "0.1.0" dependencies = [ + "itertools", "num", ] +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "itertools" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25db6b064527c5d482d0423354fcd07a89a2dfe07b67892e62411946db7f07b0" +dependencies = [ + "either", +] + [[package]] name = "num" version = "0.4.1" diff --git a/14/Cargo.toml b/14/Cargo.toml index ce44a51..3c1ab53 100644 --- a/14/Cargo.toml +++ b/14/Cargo.toml @@ -6,4 +6,5 @@ edition = "2021" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +itertools = "0.12.0" num = "0.4.1" diff --git a/14/src/main.rs b/14/src/main.rs index 07bfe79..7864210 100644 --- a/14/src/main.rs +++ b/14/src/main.rs @@ -1,3 +1,4 @@ +use itertools::Either; use std::collections::HashMap; use std::fmt::{Display, Write}; use std::fs::File; @@ -85,8 +86,67 @@ impl<'a> Platform { } fn roll(&mut self, dir: &Direction) { match dir.axis() { - Axis::Columns => self.roll_columns(&dir), - Axis::Rows => self.roll_rows(&dir), + Axis::Columns => { + for idx in 0..self.width() { + self.roll_one(idx, dir) + } + } + + Axis::Rows => { + for idx in 0..self.height() { + self.roll_one(idx, dir) + } + } + } + } + fn roll_rock_at(old: &mut Vec, inner_idx: usize, dir: &Direction) { + // Find the first # rock or the edge of the map, we can't look beyond this for a resting position + match dir.reverse_direction() { + true => { + let upper_limit = (inner_idx..old.len()) + .find(|c| old[*c] == '#') + .unwrap_or(old.len()); + + if let Some(empty_pos) = (inner_idx..upper_limit).filter(|i| old[*i] == '.').last() + { + old.swap(inner_idx, empty_pos); + } + } + false => { + // Find the first # rock or the edge of the map, we can't look beyond this for a resting position + let lower_limit = (0..inner_idx).rev().find(|c| old[*c] == '#').unwrap_or(0); + + if let Some(empty_pos) = (lower_limit..inner_idx) + .rev() + .filter(|i| old[*i] == '.') + .last() + { + old.swap(inner_idx, empty_pos); + } + } + } + } + fn roll_one(&mut self, idx: usize, dir: &Direction) { + let mut old = match dir.axis() { + Axis::Columns => self.col(idx), + Axis::Rows => self.rows[idx].clone(), + }; + let idx_iter: Either<_, _> = match dir.reverse_direction() { + true => Either::Left((0..old.len()).rev()), + false => Either::Right(0..old.len()), + }; + for inner_idx in idx_iter { + match old[inner_idx] { + '.' | '#' => continue, + 'O' if dir.reverse_direction() && inner_idx == old.len() => continue, + 'O' if !dir.reverse_direction() && inner_idx == 0 => continue, + 'O' => Self::roll_rock_at(&mut old, inner_idx, dir), + _ => panic!("invalid character"), + } + } + match dir.axis() { + Axis::Columns => self.replace_col(idx, old), + Axis::Rows => self.rows[idx] = old, } } fn score(&self, dir: &Direction) -> u64 { @@ -95,121 +155,21 @@ impl<'a> Platform { Axis::Rows => self.score_rows(dir), } } - fn roll_columns(&mut self, dir: &Direction) { - assert_eq!(dir.axis(), Axis::Columns); - - for col in 0..self.width() { - self.roll_column(col, dir); - } - } - // This code could definitely be reused for rows - fn roll_column(&mut self, idx: usize, dir: &Direction) { - let mut col = self.col(idx); + fn row_or_column_score(&self, idx: usize, dir: &Direction) -> usize { if dir.reverse_direction() { - for col_idx in (0..col.len()).rev() { - match col[col_idx] { - '.' | '#' => continue, - 'O' if col_idx == col.len() => continue, - 'O' => { - // Find the first # rock or the edge of the map, we can't look beyond this for a resting position - let upper_limit = (col_idx..col.len()) - .find(|c| col[*c] == '#') - .unwrap_or(col.len()); - - if let Some(empty_pos) = - (col_idx..upper_limit).filter(|i| col[*i] == '.').last() - { - col.swap(col_idx, empty_pos); - } - } - _ => panic!("invalid character"), - } - } + idx + 1 } else { - for col_idx in 0..col.len() { - match col[col_idx] { - '.' | '#' => continue, - 'O' if col_idx == 0 => continue, - 'O' => { - // Find the first # rock or the edge of the map, we can't look beyond this for a resting position - let lower_limit = (0..col_idx).rev().find(|c| col[*c] == '#').unwrap_or(0); - - if let Some(empty_pos) = (lower_limit..col_idx) - .rev() - .filter(|i| col[*i] == '.') - .last() - { - col.swap(col_idx, empty_pos); - } - } - _ => panic!("invalid character"), - } - } + self.rows.len() - idx } - self.replace_col(idx, col); } fn score_columns(&self, dir: &Direction) -> u64 { - // TODO: implement reverse direction - this was not required for the problem - (0..self.rows.len()) + (0..self.height()) .map(|row| { - let row_score = self.rows.len() - row; - self.rows[row].iter().filter(|c| **c == 'O').count() * row_score + self.rows[row].iter().filter(|c| **c == 'O').count() + * self.row_or_column_score(row, dir) }) .sum::() as u64 } - fn roll_rows(&mut self, dir: &Direction) { - assert_eq!(dir.axis(), Axis::Rows); - - for row in 0..self.height() { - self.roll_row(row, dir); - } - } - fn roll_row(&mut self, idx: usize, dir: &Direction) { - let mut row = self.rows[idx].clone(); - if dir.reverse_direction() { - for row_idx in (0..row.len()).rev() { - match row[row_idx] { - '.' | '#' => continue, - 'O' if row_idx == row.len() => continue, - 'O' => { - // Find the first # rock or the edge of the map, we can't look beyond this for a resting position - let upper_limit = (row_idx..row.len()) - .find(|c| row[*c] == '#') - .unwrap_or(row.len()); - - if let Some(empty_pos) = - (row_idx..upper_limit).filter(|i| row[*i] == '.').last() - { - row.swap(row_idx, empty_pos); - } - } - _ => panic!("invalid character"), - } - } - } else { - for row_idx in 0..row.len() { - match row[row_idx] { - '.' | '#' => continue, - 'O' if row_idx == 0 => continue, - 'O' => { - // Find the first # rock or the edge of the map, we can't look beyond this for a resting position - let lower_limit = (0..row_idx).rev().find(|c| row[*c] == '#').unwrap_or(0); - - if let Some(empty_pos) = (lower_limit..row_idx) - .rev() - .filter(|i| row[*i] == '.') - .last() - { - row.swap(row_idx, empty_pos); - } - } - _ => panic!("invalid character"), - } - } - } - self.rows[idx] = row; - } - fn roll_cycle(&mut self) { self.roll(&Direction::North); self.roll(&Direction::West);