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.
|
// Grows upwards, i.e. the "back" is the "top" of the stack.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
struct Cave {
|
struct Cave {
|
||||||
// The actual data - a double-ended queue / ring-buffer of lines.
|
// The actual data - a double-ended queue / ring-buffer of lines.
|
||||||
data: VecDeque<[Tile; 7]>,
|
data: VecDeque<[Tile; 7]>,
|
||||||
@ -183,8 +184,9 @@ impl Cave {
|
|||||||
println!("|");
|
println!("|");
|
||||||
}
|
}
|
||||||
if self.floor == 0 {
|
if self.floor == 0 {
|
||||||
println!("+-------+\n");
|
println!("+-------+");
|
||||||
}
|
}
|
||||||
|
println!("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checks if a given simulated coordinate refers to a settled rock.
|
// 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 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.
|
// 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
|
// Zip up a reverse iterator with another reverse-iterator that skips the topmost line
|
||||||
// to iterate over all pairs of lines.
|
// to iterate over all pairs of lines.
|
||||||
let dy = self
|
let dy = self
|
||||||
@ -260,10 +264,75 @@ impl Cave {
|
|||||||
drop(self.data.drain(0..dy));
|
drop(self.data.drain(0..dy));
|
||||||
// Don't forget to adjust the floor.
|
// Don't forget to adjust the floor.
|
||||||
self.floor += dy;
|
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() {
|
fn main() {
|
||||||
// Use command line arguments to specify the input filename.
|
// Use command line arguments to specify the input filename.
|
||||||
let args: Vec<String> = std::env::args().collect();
|
let args: Vec<String> = std::env::args().collect();
|
||||||
@ -284,6 +353,7 @@ fn main() {
|
|||||||
'>' => Some(FallingDirection::Right),
|
'>' => Some(FallingDirection::Right),
|
||||||
_ => None,
|
_ => None,
|
||||||
})
|
})
|
||||||
|
.enumerate()
|
||||||
.cycle();
|
.cycle();
|
||||||
|
|
||||||
// Also create an infinitely-looping iterator for the rock-types.
|
// Also create an infinitely-looping iterator for the rock-types.
|
||||||
@ -295,6 +365,7 @@ fn main() {
|
|||||||
RockShape::O,
|
RockShape::O,
|
||||||
]
|
]
|
||||||
.iter()
|
.iter()
|
||||||
|
.enumerate()
|
||||||
.cycle();
|
.cycle();
|
||||||
|
|
||||||
// The cave where all the rocks will settle.
|
// The cave where all the rocks will settle.
|
||||||
@ -303,30 +374,39 @@ fn main() {
|
|||||||
floor: 0,
|
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.
|
// 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.
|
// Create the next falling rock.
|
||||||
let mut fr = Some(FallingRock {
|
let mut fr = Some(FallingRock {
|
||||||
shape: *rock_shapes.next().unwrap(),
|
shape: *shape,
|
||||||
// Always two spaces from the left wall.
|
// Always two spaces from the left wall.
|
||||||
x: 2,
|
x: 2,
|
||||||
// Always three lines of free space.
|
// Always three lines of free space.
|
||||||
y: cave.past_the_top() + 3,
|
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.
|
// Keep moving l/r and down until the rock settles.
|
||||||
loop {
|
loop {
|
||||||
|
// Grab the next direction.
|
||||||
|
let (dir_idx, dir) = input_directions.next().unwrap();
|
||||||
|
current_dir_idx = dir_idx;
|
||||||
// Move left / right.
|
// Move left / right.
|
||||||
let dir = input_directions.next().unwrap();
|
|
||||||
fr = fr.unwrap().attempt_move(&mut cave, dir);
|
fr = fr.unwrap().attempt_move(&mut cave, dir);
|
||||||
|
|
||||||
// Next, move down.
|
// Next, move down.
|
||||||
@ -336,6 +416,41 @@ fn main() {
|
|||||||
break;
|
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!(
|
println!(
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user