From 8b7799d71a3fafbe3fb430d5d8da20eae8834ba9 Mon Sep 17 00:00:00 2001 From: Tobias Marschner Date: Sun, 31 Mar 2024 15:54:58 +0200 Subject: [PATCH] Solution for 2022/day17-part2, now computing in time --- 2022/day17-part2/src/main.rs | 143 +++++++++++++++++++++++++++++++---- 1 file changed, 129 insertions(+), 14 deletions(-) diff --git a/2022/day17-part2/src/main.rs b/2022/day17-part2/src/main.rs index f2eb2e7..fb37a80 100644 --- a/2022/day17-part2/src/main.rs +++ b/2022/day17-part2/src/main.rs @@ -128,6 +128,7 @@ enum Tile { } // Grows upwards, i.e. the "back" is the "top" of the stack. +#[derive(Debug, Clone)] struct Cave { // The actual data - a double-ended queue / ring-buffer of lines. data: VecDeque<[Tile; 7]>, @@ -183,8 +184,9 @@ impl Cave { println!("|"); } if self.floor == 0 { - println!("+-------+\n"); + println!("+-------+"); } + println!("\n"); } // Checks if a given simulated coordinate refers to a settled rock. @@ -236,7 +238,9 @@ impl Cave { // // Once located, anything below the lower of those two lines can be removed. // Once cut off the `floor` of the cave has to be incremented accordingly. - fn collect_garbage(&mut self) { + // + // Returns true if garbage was collected, false otherwise. + fn collect_garbage(&mut self) -> bool { // Zip up a reverse iterator with another reverse-iterator that skips the topmost line // to iterate over all pairs of lines. let dy = self @@ -260,10 +264,75 @@ impl Cave { drop(self.data.drain(0..dy)); // Don't forget to adjust the floor. self.floor += dy; + // Garbage was found and removed. + true + } else { + // No garbage was found. + false } } } +// Store the *entire* state of the system in a struct. +// This includes the entire cave, its floor number and +// the current indices into the shape and direction iterators. +#[derive(Debug)] +struct SystemState { + cave: Cave, + rock_idx: usize, + rocks_in_cave: usize, + shape_idx: usize, + dir_idx: usize, +} + +impl SystemState { + // Copy over the current state of the system and adjust the cave-data, deleting any empty + // lines. + fn new(cave: &Cave, rock_idx: usize, shape_idx: usize, dir_idx: usize) -> SystemState { + // Clone the cave for inclusion in the SystemState. + let mut cave = cave.clone(); + // To ensure consistency, cut off all empty lines at the top of the cave. + let y = cave + .data + .iter() + .enumerate() + .rev() + .find(|(_, line)| line.iter().any(|e| e == &Tile::Rock)) + .map(|(y, _)| y + 1) + .unwrap(); + cave.data.truncate(y); + // Collect statistics on the cave for faster comparison. + let rock_count: usize = cave + .data + .iter() + .map(|l| l.iter().filter(|e| e == &&Tile::Rock).count()) + .sum(); + // Create the new system state and return it. + SystemState { + cave, + rock_idx, + shape_idx, + dir_idx, + rocks_in_cave: rock_count, + } + } + + fn is_equal(&self, other: &Self) -> bool { + // To improve performance, check the easy parameters first. + self.rocks_in_cave == other.rocks_in_cave + && self.shape_idx == other.shape_idx + && self.dir_idx == other.dir_idx + // Then, check the actual cave layout. For every line in both caves ... + && self + .cave + .data + .iter() + .zip(other.cave.data.iter()) + // ... ensure every tile in each line is identical. + .all(|(al, bl)| al.iter().zip(bl.iter()).all(|(ae, be)| ae == be)) + } +} + fn main() { // Use command line arguments to specify the input filename. let args: Vec = std::env::args().collect(); @@ -284,6 +353,7 @@ fn main() { '>' => Some(FallingDirection::Right), _ => None, }) + .enumerate() .cycle(); // Also create an infinitely-looping iterator for the rock-types. @@ -295,6 +365,7 @@ fn main() { RockShape::O, ] .iter() + .enumerate() .cycle(); // The cave where all the rocks will settle. @@ -303,30 +374,39 @@ fn main() { floor: 0, }; + // The total collection of distinct states. + // We have to find the loop in the system. + let mut states: Vec = Vec::new(); + + let mut current_dir_idx: usize; + let mut current_shape_idx: usize; + + // We want to fast-forward *once*. + let mut fast_forwarded = false; + // Simulate ONE TRILLION rocks. - for i in 0..1_000_000_000_000usize { + let mut i = 0usize; + const N: usize = 1_000_000_000_000usize; + loop { + // Grab the next shape. + let (shape_idx, shape) = rock_shapes.next().unwrap(); + current_shape_idx = shape_idx; + // Create the next falling rock. let mut fr = Some(FallingRock { - shape: *rock_shapes.next().unwrap(), + shape: *shape, // Always two spaces from the left wall. x: 2, // Always three lines of free space. y: cave.past_the_top() + 3, }); - // Every 1k rocks attempt to clean up garbage. - if i % 1000 == 0 { - cave.collect_garbage(); - } - - if i % 1_000_000 == 0 { - println!("Remaining: {}", 1_000_000_000_000 - i); - } - // Keep moving l/r and down until the rock settles. loop { + // Grab the next direction. + let (dir_idx, dir) = input_directions.next().unwrap(); + current_dir_idx = dir_idx; // Move left / right. - let dir = input_directions.next().unwrap(); fr = fr.unwrap().attempt_move(&mut cave, dir); // Next, move down. @@ -336,6 +416,41 @@ fn main() { break; } } + + // Attempt to collect garbage every cycle and + // store the system state if garbage has been collected. + // Only bother with fast-forwarding if we haven't forwarded already. + if cave.collect_garbage() && !fast_forwarded { + // Create the new SystemState. + let s = SystemState::new(&cave, i, current_shape_idx, current_dir_idx); + // Compare it against all old states. + let res = states + .iter() + .rev() + .find(|e| e.is_equal(&s)); + // Found the cycle? Excellent. Then fast-forward as much as we can. + if let Some(res_elem) = res { + // We know the indices and cave-makeup from then and now are exactly identicaly. + // Only the rock_idx and floor-value are different. + let rock_delta = s.rock_idx - res_elem.rock_idx; + let floor_delta = s.cave.floor - res_elem.cave.floor; + // Determine by how many rocks we can fast-forward to get as close to N as possible. + let cycles_to_ff = (N - i) / rock_delta; + // Then, actually fast-forward by that number of cycles. + i += cycles_to_ff * rock_delta; + cave.floor += cycles_to_ff * floor_delta; + // Only fast-forward once. + fast_forwarded = true; + } + states.push(s); + // println!("No. of states: {}", states.len()); + } + + // Iterate the loop. + i += 1; + if i >= N { + break; + } } println!(