diff --git a/2023/src/common.rs b/2023/src/common.rs index a291dce..f93b425 100644 --- a/2023/src/common.rs +++ b/2023/src/common.rs @@ -300,3 +300,98 @@ impl Display for Grid<'_> { write!(f, "{}", String::from_utf8_lossy(self.data)) } } + +// TODO: merge OwnedGrid and Grid impls so I don't go insane +pub struct OwnedGrid { + width: usize, + data: Vec, +} + +impl OwnedGrid { + pub fn new(data: Vec) -> anyhow::Result { + let width = 1 + data + .iter() + .position(|&c| c == b'\n') + .context("Failed to find end of line in grid")?; + + anyhow::ensure!( + data.len() % width == 0, + "Grid should divide equally into rows" + ); + + Ok(Self { width, data }) + } + + pub fn height(&self) -> usize { + self.data.len() / self.width + } + + pub fn width(&self) -> usize { + self.width - 1 + } + + pub fn rows(&self) -> impl Iterator { + let width = self.width(); + self.data + .chunks_exact(self.width) + .map(move |row| &row[..width]) + } + + pub fn rows_mut(&mut self) -> impl Iterator { + let width = self.width(); + self.data + .chunks_exact_mut(self.width) + .map(move |row| &mut row[..width]) + } + + pub fn find(&self, c: u8) -> Option<(usize, usize)> { + let pos = self.data.iter().position(|&d| d == c)?; + + Some((pos % self.width, pos / self.width)) + } +} + +impl Index for OwnedGrid { + type Output = [u8]; + + fn index(&self, y: usize) -> &Self::Output { + let offset = y * self.width; + &self.data[offset..(offset + self.width())] + } +} + +impl IndexMut for OwnedGrid { + fn index_mut(&mut self, y: usize) -> &mut Self::Output { + let offset = y * self.width; + let width = self.width; + &mut self.data[offset..(offset + width)] + } +} + +impl Display for OwnedGrid { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", String::from_utf8_lossy(&self.data)) + } +} + +impl PartialEq for OwnedGrid { + fn eq(&self, other: &OwnedGrid) -> bool { + // No need to compare width as width is a function of data + self.data == other.data + } +} + +// Custom Clone impl so we don't allocate in `clone_from` +impl Clone for OwnedGrid { + fn clone(&self) -> Self { + Self { + width: self.width.clone(), + data: self.data.clone(), + } + } + + fn clone_from(&mut self, source: &Self) { + self.width = source.width; + self.data.clone_from(&source.data); + } +} diff --git a/2023/src/day14.rs b/2023/src/day14.rs index 0211712..7d2b797 100644 --- a/2023/src/day14.rs +++ b/2023/src/day14.rs @@ -1,4 +1,5 @@ use crate::common::Grid; +use crate::common::OwnedGrid; pub fn part1(input: &[u8]) -> anyhow::Result { let grid = Grid::new(input)?; @@ -22,8 +23,131 @@ pub fn part1(input: &[u8]) -> anyhow::Result { Ok(load.to_string()) } -pub fn part2(_input: &[u8]) -> anyhow::Result { - anyhow::bail!("Not implemented") +fn advance(grid: &mut OwnedGrid, stack_heights: &mut [usize]) { + // Tilt north + stack_heights.fill(0); + for y in 0..grid.height() { + for (x, stack_height) in stack_heights.iter_mut().enumerate() { + let c = grid[y][x]; + match c { + b'#' => *stack_height = y + 1, + b'O' => { + grid[y][x] = b'.'; + grid[*stack_height][x] = b'O'; + + *stack_height += 1; + } + _ => continue, + } + } + } + + // Tilt west + for row in grid.rows_mut() { + let mut stack_height = 0; + for x in 0..row.len() { + let c = row[x]; + match c { + b'#' => stack_height = x + 1, + b'O' => { + row[x] = b'.'; + row[stack_height] = b'O'; + + stack_height += 1; + } + _ => continue, + } + } + } + + // Tilt south + stack_heights.fill(grid.height() - 1); + for y in (0..grid.height()).rev() { + for (x, stack_height) in stack_heights.iter_mut().enumerate() { + let c = grid[y][x]; + match c { + b'#' => *stack_height = y.saturating_sub(1), + b'O' => { + grid[y][x] = b'.'; + grid[*stack_height][x] = b'O'; + + // Saturating because possible underflow + *stack_height = stack_height.saturating_sub(1); + } + _ => continue, + } + } + } + + // Tilt east + for row in grid.rows_mut() { + let mut stack_height = row.len() - 1; + for x in (0..row.len()).rev() { + let c = row[x]; + match c { + b'#' => stack_height = x.saturating_sub(1), + b'O' => { + row[x] = b'.'; + row[stack_height] = b'O'; + + stack_height = stack_height.saturating_sub(1); + } + _ => continue, + } + } + } +} + +fn find_cycle( + it: impl Iterator, + hare: &mut OwnedGrid, + stack_heights: &mut [usize], +) -> Option { + let mut tortoise = hare.clone(); + let mut last_sync = 0; + + for cycle in it { + advance(hare, stack_heights); + + if tortoise == *hare { + return Some(cycle - last_sync); + } else if cycle.count_ones() == 1 { + // New power of two, sync up the tortoise and the hare + tortoise.clone_from(&hare); + last_sync = cycle; + } + } + None +} + +pub fn part2(input: &[u8]) -> anyhow::Result { + const GOAL: usize = 1000000000; + let mut hare = OwnedGrid::new(input.to_owned())?; + let mut stack_heights = vec![0; hare.width()]; + + let mut it = 1..=GOAL; + + // If there is a cycle found, skip ahead + if let Some(len) = find_cycle(&mut it, &mut hare, &mut stack_heights) { + let remaining = it.size_hint().0; + let steps = remaining % len; + + for _ in 0..steps { + advance(&mut hare, &mut stack_heights); + } + } + + let height = hare.height(); + let load = hare + .rows() + .enumerate() + .flat_map(|(y, row)| { + row.iter() + .filter_map(move |&c| (c == b'O').then_some(height - y)) + }) + .sum::(); + + Ok(load.to_string()) } #[cfg(test)] @@ -36,4 +160,9 @@ mod tests { fn sample_part1() { assert_eq!("136", part1(SAMPLE).unwrap()); } + + #[test] + fn sample_part2() { + assert_eq!("64", part2(SAMPLE).unwrap()); + } }