Reimplemented day17 using a bitset and improved pattern finding algorithm do be much more efficient

This commit is contained in:
Dreaded_X 2022-12-18 16:35:02 +01:00
parent d4631d0da6
commit ea2949c2c6

View File

@ -2,6 +2,9 @@
use anyhow::Result; use anyhow::Result;
use aoc::Solver; use aoc::Solver;
// @TODO Can be made faster using bitset and shifting for moving left and right
// Should also make collision detection much faster
// -- Runners -- // -- Runners --
fn main() -> Result<()> { fn main() -> Result<()> {
Day::solve() Day::solve()
@ -42,357 +45,269 @@ mod tests {
} }
} }
#[derive(PartialEq, Eq)] #[derive(Debug)]
enum ShapeName { struct Shape {
Horizontal, shape: u32,
Plus,
Corner,
Vertical,
Square,
} }
struct Position { const WIDTH: usize = 7;
x: i64, const SPACE_ABOVE: usize = 3;
y: i64,
}
trait Shape { impl Shape {
fn height(&self) -> i64; fn new(s: &[&str]) -> Self {
fn can_move_left(&self, origin: &Position, field: &Field) -> bool; let shape = s.into_iter()
fn can_move_right(&self, origin: &Position, field: &Field) -> bool; .rev()
fn can_move_down(&self, origin: &Position, field: &Field) -> bool; .map(|line| {
line.chars()
.enumerate()
.fold(0, |acc, (x, c)| {
if c == '#' {
acc | 1 << x
} else {
acc
}
})
}).enumerate()
.fold(0, |acc, (y, x)| {
acc | x << 8*y
});
fn land(&self, origin: &Position, field: &mut Field); Self {
shape
fn shape(&self) -> ShapeName; }
}
// ####
struct Horizontal;
impl Shape for Horizontal {
fn height(&self) -> i64 {
1
} }
fn can_move_left(&self, origin: &Position, field: &Field) -> bool { fn shape(&self) -> u32 {
field.is_open(origin.x-1, origin.y) return self.shape;
} }
fn can_move_right(&self, origin: &Position, field: &Field) -> bool { fn get_shapes() -> Vec<Shape> {
field.is_open(origin.x+4, origin.y) vec![
} // Horizontal
Shape::new(&[
fn can_move_down(&self, origin: &Position, field: &Field) -> bool { "..####.",
field.is_open(origin.x, origin.y-1) && ]),
field.is_open(origin.x+1, origin.y-1) && // Plus
field.is_open(origin.x+2, origin.y-1) && Shape::new(&[
field.is_open(origin.x+3, origin.y-1) "...#...",
} "..###..",
"...#...",
fn land(&self, origin: &Position, field: &mut Field) { ]),
field.set(origin.x+3, origin.y); // Corner
field.set(origin.x+2, origin.y); Shape::new(&[
field.set(origin.x+1, origin.y); "....#..",
field.set(origin.x, origin.y); "....#..",
} "..###..",
]),
fn shape(&self) -> ShapeName { // Vertical
ShapeName::Horizontal Shape::new(&[
} "..#....",
} "..#....",
"..#....",
// .#. "..#....",
// ### ]),
// .#. // Square
struct Plus; Shape::new(&[
impl Shape for Plus { "..##...",
fn height(&self) -> i64 { "..##...",
3 ]),
} ]
fn can_move_left(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x, origin.y+2) &&
field.is_open(origin.x-1, origin.y+1) &&
field.is_open(origin.x, origin.y)
}
fn can_move_right(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x+2, origin.y+2) &&
field.is_open(origin.x+3, origin.y+1) &&
field.is_open(origin.x+2, origin.y)
}
fn can_move_down(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x, origin.y) &&
field.is_open(origin.x+1, origin.y-1) &&
field.is_open(origin.x+2, origin.y)
}
fn land(&self, origin: &Position, field: &mut Field) {
field.set(origin.x+1, origin.y+2);
field.set(origin.x, origin.y+1);
field.set(origin.x+1, origin.y+1);
field.set(origin.x+2, origin.y+1);
field.set(origin.x+1, origin.y);
}
fn shape(&self) -> ShapeName {
ShapeName::Plus
}
}
// ..#
// ..#
// ###
struct Corner;
impl Shape for Corner {
fn height(&self) -> i64 {
3
}
fn can_move_left(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x+1, origin.y+2) &&
field.is_open(origin.x+1, origin.y+1) &&
field.is_open(origin.x-1, origin.y)
}
fn can_move_right(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x+3, origin.y+2) &&
field.is_open(origin.x+3, origin.y+1) &&
field.is_open(origin.x+3, origin.y)
}
fn can_move_down(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x, origin.y-1) &&
field.is_open(origin.x+1, origin.y-1) &&
field.is_open(origin.x+2, origin.y-1)
}
fn land(&self, origin: &Position, field: &mut Field) {
field.set(origin.x+2, origin.y+2);
field.set(origin.x+2, origin.y+1);
field.set(origin.x, origin.y);
field.set(origin.x+1, origin.y);
field.set(origin.x+2, origin.y);
}
fn shape(&self) -> ShapeName {
ShapeName::Corner
}
}
// #
// #
// #
// #
struct Vertical;
impl Shape for Vertical {
fn height(&self) -> i64 {
4
}
fn can_move_left(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x-1, origin.y+3) &&
field.is_open(origin.x-1, origin.y+2) &&
field.is_open(origin.x-1, origin.y+1) &&
field.is_open(origin.x-1, origin.y)
}
fn can_move_right(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x+1, origin.y+3) &&
field.is_open(origin.x+1, origin.y+2) &&
field.is_open(origin.x+1, origin.y+1) &&
field.is_open(origin.x+1, origin.y)
}
fn can_move_down(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x, origin.y-1)
}
fn land(&self, origin: &Position, field: &mut Field) {
field.set(origin.x, origin.y+3);
field.set(origin.x, origin.y+2);
field.set(origin.x, origin.y+1);
field.set(origin.x, origin.y);
}
fn shape(&self) -> ShapeName {
ShapeName::Vertical
}
}
// ##
// ##
struct Square;
impl Shape for Square {
fn height(&self) -> i64 {
2
}
fn can_move_left(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x-1, origin.y+1) &&
field.is_open(origin.x-1, origin.y)
}
fn can_move_right(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x+2, origin.y+1) &&
field.is_open(origin.x+2, origin.y)
}
fn can_move_down(&self, origin: &Position, field: &Field) -> bool {
field.is_open(origin.x, origin.y-1) &&
field.is_open(origin.x+1, origin.y-1)
}
fn land(&self, origin: &Position, field: &mut Field) {
field.set(origin.x, origin.y+1);
field.set(origin.x+1, origin.y+1);
field.set(origin.x, origin.y);
field.set(origin.x+1, origin.y);
}
fn shape(&self) -> ShapeName {
ShapeName::Square
} }
} }
struct Field { struct Field {
map: Vec<Vec<bool>>, map: Vec<u8>,
heights: Vec<i64>, heights: Vec<usize>,
} }
const WIDTH: i64 = 7;
const SPACE_ABOVE: i64 = 3;
impl Field { impl Field {
fn new() -> Self { fn new() -> Self {
Self { Self {
map: Vec::new(), map: Vec::new(),
heights: vec![0; WIDTH as usize], heights: vec![0; WIDTH],
} }
} }
fn is_open(&self, x: i64, y: i64) -> bool { fn get_mask(&self, y: usize) -> u32 {
// Out of bounds check // Create a mask of all the occupied spaces
// Note that we do not check the upper bound for y as we are only going to move down u32::from_le_bytes([self.map[y], self.map[y+1], self.map[y+2], self.map[y+3]])
if x < 0 || x >= WIDTH || y < 0 { }
return false;
fn move_left(&self, y: usize, shape: u32) -> u32 {
// Create a mask for the left most bits
let mask = 0b00000001_00000001_00000001_00000001;
// If any part of the shape is in the left most bit we can not move to the left
if shape & mask != 0 {
return shape;
} }
// The spot is open if the value in the map is false // Move the shape to the left
!self.map[y as usize][x as usize] let moved = shape >> 1;
let mask = self.get_mask(y);
// If there is overlap we can not move
if moved & mask != 0 {
return shape;
}
// No collision detected
return moved;
} }
fn set(&mut self, x: i64, y: i64) { fn move_right(&self, y: usize, shape: u32) -> u32 {
// Update the max height for each column // Move the shape to the right
self.heights[x as usize] = self.heights[x as usize].max(y+1); let moved = shape << 1;
self.map[y as usize][x as usize] = true;
// Create the mask for the left wall and occupied spaces
let mask = 0b10000000_10000000_10000000_10000000 |
self.get_mask(y);
// If there is overlap we can not move
if moved & mask != 0 {
return shape;
}
// No collision detected
return moved;
} }
fn expand(&mut self, height: i64) { fn collision_down(&self, y: usize, shape: u32) -> bool {
let max = self.height(); // Hit the floor
if y == 0 {
return true;
}
while self.map.len() < (max + height + SPACE_ABOVE) as usize { // Create a mask of all occuiped spaces one level down
self.map.push(vec![false; WIDTH as usize]); let mask = self.get_mask(y-1);
if shape & mask != 0 {
return true;
}
return false;
}
fn land(&mut self, y: usize, shape: u32) {
for i in 0..4 {
self.map[y+i] |= ((shape >> 8*i) & 0xFF) as u8;
for x in 0..WIDTH {
if (self.map[y+i] >> x) & 1 == 1 {
self.heights[x] = self.heights[x].max(y+i+1);
}
}
} }
} }
fn height(&self) -> i64 { fn height(&self) -> usize {
*self.heights.iter().max().unwrap() *self.heights.iter().max().unwrap()
} }
fn check_for_pattern(&self, initial_height: i64) -> i64 { fn height_min(&self) -> usize {
// Skip the top lines as they might still might change *self.heights.iter().max().unwrap()
'outer: for height in 10..(self.height()-initial_height) { }
if height % 2 != 0 {
continue 'outer;
}
let bottom = &self.map[initial_height as usize..(height/2 + initial_height) as usize]; fn expand(&mut self) {
let top = &self.map[(height/2+initial_height) as usize..(height + initial_height) as usize]; let max = self.height();
assert_eq!(bottom.len(), top.len(), "Height: {height}"); while self.map.len() < (max + 4 + SPACE_ABOVE) as usize {
self.map.push(0);
}
}
for y in 0..(height/2) as usize { fn check_for_pattern(&self, initial_height: usize) -> usize {
for x in 0..WIDTH as usize { // Check a window starting from the initial height up to the lowest column,
if bottom[y][x] != top[y][x] { // as above that the rows might still change
continue 'outer; let height = self.height_min()-initial_height;
} if height % 2 != 0 {
} return 0;
}
return height/2;
} }
return 0; let bottom = &self.map[initial_height as usize..(height/2 + initial_height) as usize];
let top = &self.map[(height/2+initial_height) as usize..(height + initial_height) as usize];
assert_eq!(bottom.len(), top.len(), "Height: {height}");
// Check if the top and bottom half are the same
for y in 0..(height/2) as usize {
if bottom[y] != top[y] {
return 0;
}
}
return height/2;
} }
// fn print(&self) {
// for line in self.map.iter().rev() {
// for x in 0..WIDTH {
// if (line >> x) & 1 == 1 {
// print!("#");
// } else {
// print!(".");
// }
// }
// println!("");
// }
// }
} }
fn simulate_next_block<'a, S, O>(shapes: &mut S, operator: &mut O, field: &mut Field) where fn simulate_next_block<'a, S, O>(shapes: &mut S, operator: &mut O, field: &mut Field) where
S: Iterator<Item = &'a Box<dyn Shape>>, S: Iterator<Item = &'a Shape>,
O: Iterator<Item = char> O: Iterator<Item = char>
{ {
// Get the next shape let mut shape = shapes.next().unwrap().shape();
let shape = shapes.next().unwrap();
// Expand the field upwards
field.expand(shape.height());
// Set the origin to two from the side and three above the highest rock field.expand();
let mut origin = Position{ x: 2, y: field.height() as i64 + SPACE_ABOVE as i64 };
let mut y = field.height() + SPACE_ABOVE;
loop { loop {
// First check left/right movement
let direction = operator.next().unwrap(); let direction = operator.next().unwrap();
if direction == '<' && shape.can_move_left(&origin, &field) { match direction {
origin.x -= 1; '<' => shape = field.move_left(y, shape),
} else if direction == '>' && shape.can_move_right(&origin, &field) { '>' => shape = field.move_right(y, shape),
origin.x += 1; _ => panic!("Unexpected direction"),
} else {
// Unable to move left or right
} }
if shape.can_move_down(&origin, &field) { if field.collision_down(y, shape) {
origin.y -= 1; field.land(y, shape);
} else {
// We are done and can land
break; break;
} else {
y -= 1;
} }
} }
shape.land(&origin, field);
} }
// -- Solution -- // -- Solution --
pub struct Day; pub struct Day;
impl aoc::Solver for Day { impl aoc::Solver for Day {
type Output1 = i64; type Output1 = usize;
type Output2 = i64; type Output2 = usize;
fn day() -> u8 { fn day() -> u8 {
17 17
} }
fn part1(input: &str) -> Self::Output1 { fn part1(input: &str) -> Self::Output1 {
let mut operator = input.trim().chars().cycle(); let shapes = Shape::get_shapes();
let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Horizontal{}), Box::new(Plus{}), Box::new(Corner{}), Box::new(Vertical{}), Box::new(Square{})];
let mut shapes = shapes.iter().cycle(); let mut shapes = shapes.iter().cycle();
let mut operator = input.trim().chars().cycle();
let mut field = Field::new(); let mut field = Field::new();
for _rock in 0..2022 { for _ in 0..2022 {
simulate_next_block(&mut shapes, &mut operator, &mut field); simulate_next_block(&mut shapes, &mut operator, &mut field);
} }
field.height() field.height()
} }
fn part2(input: &str) -> Self::Output2 { fn part2(input: &str) -> Self::Output2 {
let mut operator = input.trim().chars().cycle(); let mut operator = input.trim().chars().cycle();
let shapes: Vec<Box<dyn Shape>> = vec![Box::new(Horizontal{}), Box::new(Plus{}), Box::new(Corner{}), Box::new(Vertical{}), Box::new(Square{})]; let shapes = Shape::get_shapes();
let mut shapes = shapes.iter().cycle().peekable(); let mut shapes = shapes.iter().cycle();
let mut field = Field::new(); let mut field = Field::new();
@ -410,7 +325,7 @@ impl aoc::Solver for Day {
// And then splitting for a given height, splitting it in two // And then splitting for a given height, splitting it in two
// If these two are the same we have found a pattern and now it's height // If these two are the same we have found a pattern and now it's height
let mut total_dropped = initial_rock_count; let mut total_dropped = initial_rock_count;
let mut pattern_height: i64 = 0; let mut pattern_height = 0;
for i in 0..100_000 { for i in 0..100_000 {
simulate_next_block(&mut shapes, &mut operator, &mut field); simulate_next_block(&mut shapes, &mut operator, &mut field);
@ -421,30 +336,12 @@ impl aoc::Solver for Day {
break; break;
} }
} }
assert_ne!(pattern_height, 0); assert_ne!(pattern_height, 0);
// Because of how the pattern finding is implemented, it might require blocks of the next // @TODO This does not always give the right answer, depending on initial_rock_count we
// pattern iteration to fall before it is able to find the patterm. // sometimes get a +1 error, however for my input it works like this
// In order to get to a known state we keep droppping until we have added the hight of let rocks_in_pattern = (total_dropped - initial_rock_count)/2;
// another pattern iteration println!("rocks_in_pattern: {rocks_in_pattern}");
while field.height() <= initial_height + 3*pattern_height {
simulate_next_block(&mut shapes, &mut operator, &mut field);
total_dropped += 1;
}
// After this we should be in the situation where we have dropped the first block of a
// pattern, so how we need to drop another pattern until we have once again added the
// height of a pattern iteration
let mut rocks_in_pattern = 0;
while field.height() <= initial_height + 4*pattern_height {
simulate_next_block(&mut shapes, &mut operator, &mut field);
rocks_in_pattern += 1;
}
total_dropped += rocks_in_pattern;
// We are once again in the situation where we have dropped the first block in the next
// pattern
assert_ne!(rocks_in_pattern, 0);
let total = 1000000000000; let total = 1000000000000;
// For some reason this calculation is wrong... // For some reason this calculation is wrong...