day14: translation-based minimal copy implementation
This commit is contained in:
		
							
								
								
									
										132
									
								
								14/src/main.rs
									
									
									
									
									
								
							
							
						
						
									
										132
									
								
								14/src/main.rs
									
									
									
									
									
								
							@@ -1,6 +1,7 @@
 | 
			
		||||
use itertools::Either;
 | 
			
		||||
use ndarray::*;
 | 
			
		||||
use std::collections::HashMap;
 | 
			
		||||
use std::fmt::{Display, Write};
 | 
			
		||||
 | 
			
		||||
use std::fmt::Display;
 | 
			
		||||
use std::fs::File;
 | 
			
		||||
use std::io::{BufRead, BufReader, Lines};
 | 
			
		||||
use std::time::Instant;
 | 
			
		||||
@@ -30,7 +31,7 @@ fn main() {
 | 
			
		||||
 | 
			
		||||
#[derive(Debug)]
 | 
			
		||||
struct Platform {
 | 
			
		||||
    rows: Vec<Vec<char>>,
 | 
			
		||||
    matrix: Array2<char>,
 | 
			
		||||
    width: usize,
 | 
			
		||||
    height: usize,
 | 
			
		||||
}
 | 
			
		||||
@@ -41,7 +42,11 @@ impl<T: BufRead> From<Lines<T>> for Platform {
 | 
			
		||||
        let width = rows[0].len();
 | 
			
		||||
        let height = rows.len();
 | 
			
		||||
        Self {
 | 
			
		||||
            rows,
 | 
			
		||||
            matrix: Array2::from_shape_vec(
 | 
			
		||||
                (height, width),
 | 
			
		||||
                rows.iter().flat_map(|row| row.iter().map(|c| *c)).collect(),
 | 
			
		||||
            )
 | 
			
		||||
            .unwrap(),
 | 
			
		||||
            width,
 | 
			
		||||
            height,
 | 
			
		||||
        }
 | 
			
		||||
@@ -62,93 +67,66 @@ struct West;
 | 
			
		||||
trait Direction {
 | 
			
		||||
    const REVERSED: bool;
 | 
			
		||||
    const AXIS: Axis;
 | 
			
		||||
    const ROTATIONS: usize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl Direction for North {
 | 
			
		||||
    const REVERSED: bool = false;
 | 
			
		||||
    const AXIS: Axis = Axis::Columns;
 | 
			
		||||
    const ROTATIONS: usize = 0;
 | 
			
		||||
}
 | 
			
		||||
impl Direction for South {
 | 
			
		||||
    const REVERSED: bool = true;
 | 
			
		||||
    const AXIS: Axis = Axis::Columns;
 | 
			
		||||
    const ROTATIONS: usize = 2; // 180
 | 
			
		||||
}
 | 
			
		||||
impl Direction for East {
 | 
			
		||||
    const REVERSED: bool = true;
 | 
			
		||||
    const AXIS: Axis = Axis::Rows;
 | 
			
		||||
    const ROTATIONS: usize = 1; // CCW
 | 
			
		||||
}
 | 
			
		||||
impl Direction for West {
 | 
			
		||||
    const REVERSED: bool = false;
 | 
			
		||||
    const AXIS: Axis = Axis::Rows;
 | 
			
		||||
    const ROTATIONS: usize = 3; // CW
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
impl<'a> Platform {
 | 
			
		||||
    fn col(&self, col: usize) -> Vec<char> {
 | 
			
		||||
        self.rows.iter().map(|row| row[col]).collect()
 | 
			
		||||
    }
 | 
			
		||||
    fn replace_col(&mut self, idx: usize, col: Vec<char>) {
 | 
			
		||||
        for row_idx in 0..col.len() {
 | 
			
		||||
            self.rows[row_idx][idx] = col[row_idx];
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    fn roll<T: Direction>(&mut self) {
 | 
			
		||||
        match T::AXIS {
 | 
			
		||||
            Axis::Columns => {
 | 
			
		||||
                for idx in 0..self.width {
 | 
			
		||||
                    let new = self.roll_one::<T>(idx);
 | 
			
		||||
                    self.replace_col(idx, new);
 | 
			
		||||
                }
 | 
			
		||||
        // Get a view into the rotated matrix with the target direction to the north
 | 
			
		||||
        let mut view = self.matrix.view_mut();
 | 
			
		||||
 | 
			
		||||
        match T::ROTATIONS % 4 {
 | 
			
		||||
            0 => {}
 | 
			
		||||
            1 => {
 | 
			
		||||
                view.invert_axis(Axis(1));
 | 
			
		||||
                view.swap_axes(0, 1);
 | 
			
		||||
            }
 | 
			
		||||
            Axis::Rows => {
 | 
			
		||||
                for idx in 0..self.height {
 | 
			
		||||
                    let new = self.roll_one::<T>(idx);
 | 
			
		||||
                    self.rows[idx].copy_from_slice(new.as_slice());
 | 
			
		||||
            2 => {
 | 
			
		||||
                view.invert_axis(Axis(0));
 | 
			
		||||
                view.invert_axis(Axis(1));
 | 
			
		||||
            }
 | 
			
		||||
            3 => {
 | 
			
		||||
                view.swap_axes(0, 1);
 | 
			
		||||
                view.invert_axis(Axis(1));
 | 
			
		||||
            }
 | 
			
		||||
            _ => unreachable!(),
 | 
			
		||||
        }
 | 
			
		||||
        Self::roll_rocks(view);
 | 
			
		||||
    }
 | 
			
		||||
    fn roll_rocks(mut view: ArrayBase<ViewRepr<&mut char>, Dim<[usize; 2]>>) {
 | 
			
		||||
        let axis_len = view.len_of(Axis(1));
 | 
			
		||||
        for mut col in view.columns_mut() {
 | 
			
		||||
            for inner_idx in 1..axis_len {
 | 
			
		||||
                if col[inner_idx] == 'O' {
 | 
			
		||||
                    let lower_limit = (0..inner_idx).rev().find(|i| col[*i] == '#').unwrap_or(0);
 | 
			
		||||
                    if let Some(empty_pos) = (lower_limit..inner_idx).find(|i| col[*i] == '.') {
 | 
			
		||||
                        col.swap(inner_idx, empty_pos);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    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
 | 
			
		||||
        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());
 | 
			
		||||
 | 
			
		||||
            // 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);
 | 
			
		||||
            }
 | 
			
		||||
        } 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);
 | 
			
		||||
 | 
			
		||||
            // 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<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 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 inner_idx == end_idx => continue,
 | 
			
		||||
                'O' => Self::roll_rock_at::<T>(&mut old, inner_idx),
 | 
			
		||||
                _ => panic!("invalid character"),
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        old
 | 
			
		||||
    }
 | 
			
		||||
    fn score<T: Direction>(&self) -> u64 {
 | 
			
		||||
        match T::AXIS {
 | 
			
		||||
            Axis::Columns => self.score_columns::<T>(),
 | 
			
		||||
@@ -166,10 +144,12 @@ impl<'a> Platform {
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
    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::<T>(row)
 | 
			
		||||
        self.matrix
 | 
			
		||||
            .rows()
 | 
			
		||||
            .into_iter()
 | 
			
		||||
            .enumerate()
 | 
			
		||||
            .map(|(idx, row)| {
 | 
			
		||||
                row.iter().filter(|c| **c == 'O').count() * self.row_or_column_score::<T>(idx)
 | 
			
		||||
            })
 | 
			
		||||
            .sum::<usize>() as u64
 | 
			
		||||
    }
 | 
			
		||||
@@ -182,13 +162,13 @@ impl<'a> Platform {
 | 
			
		||||
 | 
			
		||||
    // find the first loop, return the iteration count when we first saw it and when we saw it again
 | 
			
		||||
    fn find_loop(&mut self) -> (usize, usize) {
 | 
			
		||||
        let mut first_seen: HashMap<Vec<Vec<char>>, usize> = HashMap::new();
 | 
			
		||||
        first_seen.insert(self.rows.clone(), 0);
 | 
			
		||||
        let mut first_seen = HashMap::new();
 | 
			
		||||
        first_seen.insert(self.matrix.clone(), 0);
 | 
			
		||||
        let mut i = 0;
 | 
			
		||||
        loop {
 | 
			
		||||
            self.roll_cycle();
 | 
			
		||||
            i += 1;
 | 
			
		||||
            if let Some(first_idx) = first_seen.insert(self.rows.clone(), i) {
 | 
			
		||||
            if let Some(first_idx) = first_seen.insert(self.matrix.clone(), i) {
 | 
			
		||||
                return (first_idx, i);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
@@ -200,13 +180,7 @@ impl<'a> Platform {
 | 
			
		||||
 | 
			
		||||
impl Display for Platform {
 | 
			
		||||
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
 | 
			
		||||
        for row in &self.rows {
 | 
			
		||||
            for c in row {
 | 
			
		||||
                f.write_char(*c)?;
 | 
			
		||||
            }
 | 
			
		||||
            writeln!(f)?;
 | 
			
		||||
        }
 | 
			
		||||
        writeln!(f)
 | 
			
		||||
        self.matrix.fmt(f)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user