Solution for 2022/day17-part2, now computing in time
This commit is contained in:
parent
8bdf937185
commit
8b7799d71a
@ -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<String> = 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<SystemState> = 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!(
|
||||
|
||||
Loading…
Reference in New Issue
Block a user