Rust Day 14 - Performance improvements

Use [usize; 2] instead of (usize, usize)
Use HashSet instead of HashMap
Use !.contains instead of .get(...).is_none()
Compute lowest_stone when Cave is created, instead of for every sand
This commit is contained in:
Tobias Berger 2022-12-14 13:43:23 +01:00
parent 53f9a710e0
commit caf113ce3d
Signed by: toby
GPG key ID: 2D05EFAB764D6A88
2 changed files with 46 additions and 72 deletions

View file

@ -3,118 +3,95 @@
#![feature(generators, generator_trait)] #![feature(generators, generator_trait)]
const INPUT: &str = include_str!("input.txt"); const INPUT: &str = include_str!("input.txt");
use std::collections::HashMap; use std::collections::HashSet;
mod part_1; mod part_1;
use part_1::part_1; use part_1::part_1;
mod part_2; mod part_2;
use part_2::part_2; use part_2::part_2;
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)] #[derive(Debug, Clone)]
enum Material {
Stone,
Sand,
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub(crate) struct Cave { pub(crate) struct Cave {
pub(crate) field: HashMap<(usize, usize), Material>, pub(crate) field: HashSet<[usize; 2]>,
lowest_stone: usize,
} }
impl Cave { impl Cave {
fn lowest_stone(&self) -> usize { fn new(field: HashSet<[usize; 2]>) -> Cave {
self.field let lowest_stone = field
.iter() .iter()
.filter_map(|(position, material)| { .map(|position| position[1])
if matches!(material, Material::Stone) {
Some(position.1)
} else {
None
}
})
.max() .max()
.unwrap_or_default() .unwrap_or_default();
Cave {
field,
lowest_stone,
}
} }
/// Inserts a field of sand into this [`Cave`], then returns whether it landed. /// Inserts a field of sand into this [`Cave`], then returns whether it landed.
pub(crate) fn insert_sand_void<const X: usize>(&mut self) -> Option<(usize, usize)> { pub(crate) fn insert_sand_void<const X: usize>(&mut self) -> Option<[usize; 2]> {
let lowest_position = self.lowest_stone(); let mut current_coordinate = [X, 0];
let mut current_coordinate = (X, 0);
loop { loop {
if current_coordinate.1 >= lowest_position { if current_coordinate[1] >= self.lowest_stone {
return None; return None;
} }
let coordinate_below_straight = (current_coordinate.0, current_coordinate.1 + 1); let coordinate_below_straight = [current_coordinate[0], current_coordinate[1] + 1];
let field_below_straight = self.field.get(&coordinate_below_straight); if !self.field.contains(&coordinate_below_straight) {
if field_below_straight.is_none() {
current_coordinate = coordinate_below_straight; current_coordinate = coordinate_below_straight;
continue; continue;
} }
let coordinate_below_left = (current_coordinate.0 - 1, current_coordinate.1 + 1); let coordinate_below_left = [current_coordinate[0] - 1, current_coordinate[1] + 1];
let field_below_left = self.field.get(&coordinate_below_left); if !self.field.contains(&coordinate_below_left) {
if field_below_left.is_none() {
current_coordinate = coordinate_below_left; current_coordinate = coordinate_below_left;
continue; continue;
} }
let coordinate_below_right = (current_coordinate.0 + 1, current_coordinate.1 + 1); let coordinate_below_right = [current_coordinate[0] + 1, current_coordinate[1] + 1];
let field_below_right = self.field.get(&coordinate_below_right); if !self.field.contains(&coordinate_below_right) {
if field_below_right.is_none() {
current_coordinate = coordinate_below_right; current_coordinate = coordinate_below_right;
continue; continue;
} }
self.field.insert(current_coordinate, Material::Sand); self.field.insert(current_coordinate);
return Some(current_coordinate); return Some(current_coordinate);
} }
} }
/// Inserts a field of sand into this [`Cave`], then returns where it landed. /// Inserts a field of sand into this [`Cave`], then returns where it landed.
pub(crate) fn insert_sand_floor<const X: usize>(&mut self) -> (usize, usize) { pub(crate) fn insert_sand_floor<const X: usize>(&mut self) -> [usize; 2] {
let lowest_position = self.lowest_stone() + 2; let lowest_position = self.lowest_stone + 2;
let mut current_coordinate = (X, 0); let mut current_coordinate = [X, 0];
loop { loop {
let y_below = current_coordinate.1 + 1; let y_below = current_coordinate[1] + 1;
if y_below < lowest_position { if y_below < lowest_position {
let coordinate_below_straight = (current_coordinate.0, y_below); let coordinate_below_straight = [current_coordinate[0], y_below];
let field_below_straight = self.field.get(&coordinate_below_straight); if !self.field.contains(&coordinate_below_straight) {
if field_below_straight.is_none() {
current_coordinate = coordinate_below_straight; current_coordinate = coordinate_below_straight;
continue; continue;
} }
let coordinate_below_left = (current_coordinate.0 - 1, y_below); let coordinate_below_left = [current_coordinate[0] - 1, y_below];
let field_below_left = self.field.get(&coordinate_below_left); if !self.field.contains(&coordinate_below_left) {
if field_below_left.is_none() {
current_coordinate = coordinate_below_left; current_coordinate = coordinate_below_left;
continue; continue;
} }
let coordinate_below_right = (current_coordinate.0 + 1, y_below); let coordinate_below_right = [current_coordinate[0] + 1, y_below];
let field_below_right = self.field.get(&coordinate_below_right); if !self.field.contains(&coordinate_below_right) {
if field_below_right.is_none() {
current_coordinate = coordinate_below_right; current_coordinate = coordinate_below_right;
continue; continue;
} }
self.field assert!(self.field.insert(current_coordinate));
.try_insert(current_coordinate, Material::Sand)
.unwrap_or_else(|err| {
panic!("Tried to insert Sand at occupied position {current_coordinate:?} - {err}")
});
return current_coordinate; return current_coordinate;
} else { } else {
self.field assert!(self.field.insert(current_coordinate));
.try_insert(current_coordinate, Material::Sand)
.unwrap_or_else(|err| {
panic!("Tried to insert Sand at occupied position {current_coordinate:?} - {err}")
});
return current_coordinate; return current_coordinate;
} }
} }
@ -123,15 +100,12 @@ impl Cave {
pub(crate) type Input = Cave; pub(crate) type Input = Cave;
pub(crate) fn interpolate( pub(crate) fn interpolate(from: [usize; 2], to: [usize; 2]) -> impl Iterator<Item = [usize; 2]> {
from: (usize, usize), let min_x = from[0].min(to[0]);
to: (usize, usize), let max_x = from[0].max(to[0]);
) -> impl Iterator<Item = (usize, usize)> { let min_y = from[1].min(to[1]);
let min_x = from.0.min(to.0); let max_y = from[1].max(to[1]);
let max_x = from.0.max(to.0); (min_x..=max_x).flat_map(move |x| (min_y..=max_y).map(move |y| [x, y]))
let min_y = from.1.min(to.1);
let max_y = from.1.max(to.1);
(min_x..=max_x).flat_map(move |x| (min_y..=max_y).map(move |y| (x, y)))
} }
fn parse_input(input: &'static str) -> Input { fn parse_input(input: &'static str) -> Input {
@ -140,14 +114,14 @@ fn parse_input(input: &'static str) -> Input {
let coordinates = position_string.split(',').collect::<Vec<_>>(); let coordinates = position_string.split(',').collect::<Vec<_>>();
assert_eq!(coordinates.len(), 2); assert_eq!(coordinates.len(), 2);
( [
coordinates[0].parse::<usize>().unwrap(), coordinates[0].parse::<usize>().unwrap(),
coordinates[1].parse::<usize>().unwrap(), coordinates[1].parse::<usize>().unwrap(),
) ]
}) })
}); });
let mut stone_tiles = HashMap::new(); let mut stone_tiles = HashSet::new();
for lines in result { for lines in result {
let mut coordinates = lines; let mut coordinates = lines;
@ -155,14 +129,14 @@ fn parse_input(input: &'static str) -> Input {
for destination in coordinates { for destination in coordinates {
for coordinate in interpolate(current_point, destination) { for coordinate in interpolate(current_point, destination) {
stone_tiles.insert(coordinate, Material::Stone); stone_tiles.insert(coordinate);
} }
current_point = destination; current_point = destination;
} }
} }
Cave { field: stone_tiles } Cave::new(stone_tiles)
} }
pub fn main() { pub fn main() {

View file

@ -1,7 +1,7 @@
pub(crate) fn part_2(mut input: crate::Input) -> u64 { pub(crate) fn part_2(mut input: crate::Input) -> u64 {
// Increase by 1 because I break as soon as (500, 0) is placed, so it doesn't get counted // Increase by 1 because I break as soon as (500, 0) is placed, so it doesn't get counted
let mut sand_count = 1; let mut sand_count = 1;
while let inserted_at = input.insert_sand_floor::<500>() && inserted_at.1 != 0 { while let inserted_at = input.insert_sand_floor::<500>() && inserted_at[1] != 0 {
sand_count += 1; sand_count += 1;
} }