day14: some silly and not super effective performance optimizations
This commit is contained in:
		
							
								
								
									
										170
									
								
								14/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										170
									
								
								14/src/main.rs
									
									
									
									
									
								
							@@ -31,51 +31,57 @@ fn main() {
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct Platform {
 | 
			
		||||
    rows: Vec<Vec<char>>,
 | 
			
		||||
    width: usize,
 | 
			
		||||
    height: usize,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<T: BufRead> From<Lines<T>> for Platform {
 | 
			
		||||
    fn from(lines: Lines<T>) -> Self {
 | 
			
		||||
        let rows: Vec<Vec<char>> = lines.map(|line| line.unwrap().chars().collect()).collect();
 | 
			
		||||
        let width = rows[0].len();
 | 
			
		||||
        let height = rows.len();
 | 
			
		||||
        Self {
 | 
			
		||||
            rows: lines.map(|line| line.unwrap().chars().collect()).collect(),
 | 
			
		||||
            rows,
 | 
			
		||||
            width,
 | 
			
		||||
            height,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
enum Direction {
 | 
			
		||||
    North,
 | 
			
		||||
    South,
 | 
			
		||||
    East,
 | 
			
		||||
    West,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[derive(Debug, PartialEq)]
 | 
			
		||||
enum Axis {
 | 
			
		||||
    Rows,
 | 
			
		||||
    Columns,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Direction {
 | 
			
		||||
    fn reverse_direction(&self) -> bool {
 | 
			
		||||
        match self {
 | 
			
		||||
            Direction::North | Direction::West => false,
 | 
			
		||||
            Direction::South | Direction::East => true,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn axis(&self) -> Axis {
 | 
			
		||||
        match self {
 | 
			
		||||
            Direction::North | Direction::South => Axis::Columns,
 | 
			
		||||
            Direction::East | Direction::West => Axis::Rows,
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
struct North;
 | 
			
		||||
struct South;
 | 
			
		||||
struct East;
 | 
			
		||||
struct West;
 | 
			
		||||
 | 
			
		||||
trait Direction {
 | 
			
		||||
    const REVERSED: bool;
 | 
			
		||||
    const AXIS: Axis;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Direction for North {
 | 
			
		||||
    const REVERSED: bool = false;
 | 
			
		||||
    const AXIS: Axis = Axis::Columns;
 | 
			
		||||
}
 | 
			
		||||
impl Direction for South {
 | 
			
		||||
    const REVERSED: bool = true;
 | 
			
		||||
    const AXIS: Axis = Axis::Columns;
 | 
			
		||||
}
 | 
			
		||||
impl Direction for East {
 | 
			
		||||
    const REVERSED: bool = true;
 | 
			
		||||
    const AXIS: Axis = Axis::Rows;
 | 
			
		||||
}
 | 
			
		||||
impl Direction for West {
 | 
			
		||||
    const REVERSED: bool = false;
 | 
			
		||||
    const AXIS: Axis = Axis::Rows;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Platform {
 | 
			
		||||
    fn width(&self) -> usize {
 | 
			
		||||
        self.rows[0].len()
 | 
			
		||||
    }
 | 
			
		||||
    fn height(&self) -> usize {
 | 
			
		||||
        self.col(0).len()
 | 
			
		||||
    }
 | 
			
		||||
    fn col(&self, col: usize) -> Vec<char> {
 | 
			
		||||
        self.rows.iter().map(|row| row[col]).collect()
 | 
			
		||||
    }
 | 
			
		||||
@@ -84,97 +90,94 @@ impl<'a> Platform {
 | 
			
		||||
            self.rows[row_idx][idx] = col[row_idx];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn roll(&mut self, dir: &Direction) {
 | 
			
		||||
        match dir.axis() {
 | 
			
		||||
    fn roll<T: Direction>(&mut self) {
 | 
			
		||||
        match T::AXIS {
 | 
			
		||||
            Axis::Columns => {
 | 
			
		||||
                for idx in 0..self.width() {
 | 
			
		||||
                    self.roll_one(idx, dir)
 | 
			
		||||
                for idx in 0..self.width {
 | 
			
		||||
                    let new = self.roll_one::<T>(idx);
 | 
			
		||||
                    self.replace_col(idx, new);
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            Axis::Rows => {
 | 
			
		||||
                for idx in 0..self.height() {
 | 
			
		||||
                    self.roll_one(idx, dir)
 | 
			
		||||
                for idx in 0..self.height {
 | 
			
		||||
                    let new = self.roll_one::<T>(idx);
 | 
			
		||||
                    self.rows[idx].copy_from_slice(new.as_slice());
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn roll_rock_at(old: &mut Vec<char>, inner_idx: usize, dir: &Direction) {
 | 
			
		||||
    fn roll_rock_at<T: Direction>(old: &mut Vec<char>, inner_idx: usize) {
 | 
			
		||||
        // Find the first # rock or the edge of the map, we can't look beyond this for a resting position
 | 
			
		||||
        match dir.reverse_direction() {
 | 
			
		||||
            true => {
 | 
			
		||||
                let upper_limit = (inner_idx..old.len())
 | 
			
		||||
                    .find(|c| old[*c] == '#')
 | 
			
		||||
                    .unwrap_or(old.len());
 | 
			
		||||
        if T::REVERSED {
 | 
			
		||||
            // The furthest possible resting position is the first # below us, or the edge of the map
 | 
			
		||||
            let upper_limit = (inner_idx..old.len() - 1)
 | 
			
		||||
                .find(|c| old[*c] == '#')
 | 
			
		||||
                .unwrap_or(old.len());
 | 
			
		||||
 | 
			
		||||
                if let Some(empty_pos) = (inner_idx..upper_limit).filter(|i| old[*i] == '.').last()
 | 
			
		||||
                {
 | 
			
		||||
                    old.swap(inner_idx, empty_pos);
 | 
			
		||||
                }
 | 
			
		||||
            // We will roll to the position of the furthest '.' before the upper limit
 | 
			
		||||
            if let Some(empty_pos) = (inner_idx..upper_limit).rev().find(|i| old[*i] == '.') {
 | 
			
		||||
                old.swap(inner_idx, empty_pos);
 | 
			
		||||
            }
 | 
			
		||||
            false => {
 | 
			
		||||
                // Find the first # rock or the edge of the map, we can't look beyond this for a resting position
 | 
			
		||||
                let lower_limit = (0..inner_idx).rev().find(|c| old[*c] == '#').unwrap_or(0);
 | 
			
		||||
        } else {
 | 
			
		||||
            // the furthest possible resting position is the first # above us, or the edge
 | 
			
		||||
            let lower_limit = (0..inner_idx).rev().find(|c| old[*c] == '#').unwrap_or(0);
 | 
			
		||||
 | 
			
		||||
                if let Some(empty_pos) = (lower_limit..inner_idx)
 | 
			
		||||
                    .rev()
 | 
			
		||||
                    .filter(|i| old[*i] == '.')
 | 
			
		||||
                    .last()
 | 
			
		||||
                {
 | 
			
		||||
                    old.swap(inner_idx, empty_pos);
 | 
			
		||||
                }
 | 
			
		||||
            // We will roll to the position of the furthest '.' before the lower limit
 | 
			
		||||
            if let Some(empty_pos) = (lower_limit..inner_idx).find(|i| old[*i] == '.') {
 | 
			
		||||
                old.swap(inner_idx, empty_pos);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn roll_one(&mut self, idx: usize, dir: &Direction) {
 | 
			
		||||
        let mut old = match dir.axis() {
 | 
			
		||||
 | 
			
		||||
    fn roll_one<T: Direction>(&mut self, idx: usize) -> Vec<char> {
 | 
			
		||||
        let mut old = match T::AXIS {
 | 
			
		||||
            Axis::Columns => self.col(idx),
 | 
			
		||||
            Axis::Rows => self.rows[idx].clone(),
 | 
			
		||||
        };
 | 
			
		||||
        let idx_iter: Either<_, _> = match dir.reverse_direction() {
 | 
			
		||||
        let idx_iter: Either<_, _> = match T::REVERSED {
 | 
			
		||||
            true => Either::Left((0..old.len()).rev()),
 | 
			
		||||
            false => Either::Right(0..old.len()),
 | 
			
		||||
        };
 | 
			
		||||
        let end_idx = if T::REVERSED { old.len() } else { 0 };
 | 
			
		||||
        for inner_idx in idx_iter {
 | 
			
		||||
            match old[inner_idx] {
 | 
			
		||||
                '.' | '#' => continue,
 | 
			
		||||
                'O' if dir.reverse_direction() && inner_idx == old.len() => continue,
 | 
			
		||||
                'O' if !dir.reverse_direction() && inner_idx == 0 => continue,
 | 
			
		||||
                'O' => Self::roll_rock_at(&mut old, inner_idx, dir),
 | 
			
		||||
                'O' if inner_idx == end_idx => continue,
 | 
			
		||||
                'O' => Self::roll_rock_at::<T>(&mut old, inner_idx),
 | 
			
		||||
                _ => panic!("invalid character"),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        match dir.axis() {
 | 
			
		||||
            Axis::Columns => self.replace_col(idx, old),
 | 
			
		||||
            Axis::Rows => self.rows[idx] = old,
 | 
			
		||||
        old
 | 
			
		||||
    }
 | 
			
		||||
    fn score<T: Direction>(&self) -> u64 {
 | 
			
		||||
        match T::AXIS {
 | 
			
		||||
            Axis::Columns => self.score_columns::<T>(),
 | 
			
		||||
            Axis::Rows => self.score_rows::<T>(),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn score(&self, dir: &Direction) -> u64 {
 | 
			
		||||
        match dir.axis() {
 | 
			
		||||
            Axis::Columns => self.score_columns(dir),
 | 
			
		||||
            Axis::Rows => self.score_rows(dir),
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn row_or_column_score(&self, idx: usize, dir: &Direction) -> usize {
 | 
			
		||||
        if dir.reverse_direction() {
 | 
			
		||||
    fn row_or_column_score<T: Direction>(&self, idx: usize) -> usize {
 | 
			
		||||
        if T::REVERSED {
 | 
			
		||||
            idx + 1
 | 
			
		||||
        } else {
 | 
			
		||||
            self.rows.len() - idx
 | 
			
		||||
            match T::AXIS {
 | 
			
		||||
                Axis::Rows => self.width - idx,
 | 
			
		||||
                Axis::Columns => self.height - idx,
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn score_columns(&self, dir: &Direction) -> u64 {
 | 
			
		||||
        (0..self.height())
 | 
			
		||||
    fn score_columns<T: Direction>(&self) -> u64 {
 | 
			
		||||
        (0..self.height)
 | 
			
		||||
            .map(|row| {
 | 
			
		||||
                self.rows[row].iter().filter(|c| **c == 'O').count()
 | 
			
		||||
                    * self.row_or_column_score(row, dir)
 | 
			
		||||
                    * self.row_or_column_score::<T>(row)
 | 
			
		||||
            })
 | 
			
		||||
            .sum::<usize>() as u64
 | 
			
		||||
    }
 | 
			
		||||
    fn roll_cycle(&mut self) {
 | 
			
		||||
        self.roll(&Direction::North);
 | 
			
		||||
        self.roll(&Direction::West);
 | 
			
		||||
        self.roll(&Direction::South);
 | 
			
		||||
        self.roll(&Direction::East);
 | 
			
		||||
        self.roll::<North>();
 | 
			
		||||
        self.roll::<West>();
 | 
			
		||||
        self.roll::<South>();
 | 
			
		||||
        self.roll::<East>();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // find the first loop, return the iteration count when we first saw it and when we saw it again
 | 
			
		||||
@@ -190,7 +193,7 @@ impl<'a> Platform {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn score_rows(&self, dir: &Direction) -> u64 {
 | 
			
		||||
    fn score_rows<T: Direction>(&self) -> u64 {
 | 
			
		||||
        unimplemented!()
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -211,8 +214,8 @@ impl Display for Platform {
 | 
			
		||||
 | 
			
		||||
fn problem1<T: BufRead>(input: Lines<T>) -> u64 {
 | 
			
		||||
    let mut p = Platform::from(input);
 | 
			
		||||
    p.roll(&Direction::North);
 | 
			
		||||
    p.score(&Direction::North)
 | 
			
		||||
    p.roll::<North>();
 | 
			
		||||
    p.score::<North>()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// PROBLEM 2 solution
 | 
			
		||||
@@ -223,11 +226,12 @@ fn problem2<T: BufRead>(input: Lines<T>) -> u64 {
 | 
			
		||||
    let loop_length = first_loop.1 - first_loop.0;
 | 
			
		||||
    let cycles_to_skip = ((ITERATIONS - first_loop.0) / loop_length) * loop_length;
 | 
			
		||||
    let iterations_remaining = ITERATIONS - first_loop.0 - cycles_to_skip;
 | 
			
		||||
 | 
			
		||||
    for _ in 0..iterations_remaining {
 | 
			
		||||
        p.roll_cycle();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    p.score(&Direction::North)
 | 
			
		||||
    p.score::<North>()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
#[cfg(test)]
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user