diff --git a/14/src/main.rs b/14/src/main.rs index 69de318..07bfe79 100644 --- a/14/src/main.rs +++ b/14/src/main.rs @@ -1,7 +1,8 @@ +use std::collections::HashMap; use std::fmt::{Display, Write}; use std::fs::File; use std::io::{BufRead, BufReader, Lines}; -use std::time::{Duration, Instant}; +use std::time::Instant; // BOILERPLATE type InputIter = Lines>; @@ -26,16 +27,24 @@ fn main() { // PARSE -struct Platform(Vec>); +#[derive(Debug)] +struct Platform { + rows: Vec>, +} impl From> for Platform { fn from(lines: Lines) -> Self { - Self(lines.map(|line| line.unwrap().chars().collect()).collect()) + Self { + rows: lines.map(|line| line.unwrap().chars().collect()).collect(), + } } } enum Direction { North, + South, + East, + West, } #[derive(Debug, PartialEq)] @@ -47,26 +56,31 @@ enum Axis { impl Direction { fn reverse_direction(&self) -> bool { match self { - Direction::North => false, + Direction::North | Direction::West => false, + Direction::South | Direction::East => true, } } fn axis(&self) -> Axis { match self { - Direction::North => Axis::Columns, + Direction::North | Direction::South => Axis::Columns, + Direction::East | Direction::West => Axis::Rows, } } } impl<'a> Platform { fn width(&self) -> usize { - self.0[0].len() + self.rows[0].len() + } + fn height(&self) -> usize { + self.col(0).len() } fn col(&self, col: usize) -> Vec { - self.0.iter().map(|row| row[col]).collect() + 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.0[row_idx][idx] = col[row_idx]; + self.rows[row_idx][idx] = col[row_idx]; } } fn roll(&mut self, dir: &Direction) { @@ -88,41 +102,133 @@ impl<'a> Platform { 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); - // TODO: implement reverse direction - 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 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) = (lower_limit..col_idx) - .rev() - .filter(|i| col[*i] == '.') - .last() - { - col.swap(col_idx, empty_pos); + if let Some(empty_pos) = + (col_idx..upper_limit).filter(|i| col[*i] == '.').last() + { + col.swap(col_idx, empty_pos); + } } + _ => panic!("invalid character"), + } + } + } 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"), } - _ => panic!("invalid character"), } } self.replace_col(idx, col); } fn score_columns(&self, dir: &Direction) -> u64 { - // TODO: implement reverse direction - (0..self.0.len()) + // TODO: implement reverse direction - this was not required for the problem + (0..self.rows.len()) .map(|row| { - let row_score = self.0.len() - row; - self.0[row].iter().filter(|c| **c == 'O').count() * row_score + let row_score = self.rows.len() - row; + self.rows[row].iter().filter(|c| **c == 'O').count() * row_score }) .sum::() as u64 } - fn roll_rows(&self, dir: &Direction) { - unimplemented!() + 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); + self.roll(&Direction::South); + self.roll(&Direction::East); + } + + // 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 i = 0; + loop { + self.roll_cycle(); + i += 1; + if let Some(first_idx) = first_seen.insert(self.rows.clone(), i) { + return (first_idx, i); + } + } } fn score_rows(&self, dir: &Direction) -> u64 { unimplemented!() @@ -131,7 +237,7 @@ impl<'a> Platform { impl Display for Platform { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - for row in &self.0 { + for row in &self.rows { for c in row { f.write_char(*c)?; } @@ -151,7 +257,17 @@ fn problem1(input: Lines) -> u64 { // PROBLEM 2 solution fn problem2(input: Lines) -> u64 { - 0 + const ITERATIONS: usize = 1000000000; + let mut p = Platform::from(input); + let first_loop = p.find_loop(); + let loop_length = first_loop.1 - first_loop.0; + let cycles_to_skip = ((ITERATIONS - first_loop.0) / loop_length) * loop_length; + let iterations_remaining = ITERATIONS - first_loop.0 - cycles_to_skip; + for _ in 0..iterations_remaining { + p.roll_cycle(); + } + + p.score(&Direction::North) } #[cfg(test)] @@ -179,6 +295,6 @@ O.#..O.#.# #[test] fn problem2_example() { let c = Cursor::new(EXAMPLE); - assert_eq!(problem2(c.lines()), 0); + assert_eq!(problem2(c.lines()), 64); } }