From b6eb9a17371bc1dd9361fdef14fc25060c652632 Mon Sep 17 00:00:00 2001 From: Maxime Augier Date: Tue, 26 Dec 2023 12:09:40 +0100 Subject: [PATCH] Some utils --- myutils/Cargo.lock | 65 ++++++++++++++++ myutils/Cargo.toml | 9 +++ myutils/src/lib.rs | 1 + myutils/src/matrix.rs | 177 ++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 252 insertions(+) create mode 100644 myutils/Cargo.lock create mode 100644 myutils/Cargo.toml create mode 100644 myutils/src/lib.rs create mode 100644 myutils/src/matrix.rs diff --git a/myutils/Cargo.lock b/myutils/Cargo.lock new file mode 100644 index 0000000..4c0769c --- /dev/null +++ b/myutils/Cargo.lock @@ -0,0 +1,65 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +version = 3 + +[[package]] +name = "myutils" +version = "0.1.0" +dependencies = [ + "thiserror", +] + +[[package]] +name = "proc-macro2" +version = "1.0.71" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75cb1540fadbd5b8fbccc4dddad2734eba435053f725621c070711a14bb5f4b8" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "syn" +version = "2.0.43" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee659fb5f3d355364e1f3e5bc10fb82068efbf824a1e9d1c9504244a6469ad53" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "thiserror" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83a48fd946b02c0a526b2e9481c8e2a17755e47039164a86c4070446e3a4614d" +dependencies = [ + "thiserror-impl", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.52" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7fbe9b594d6568a6a1443250a7e67d80b74e1e96f6d1715e1e21cc1888291d3" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "unicode-ident" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" diff --git a/myutils/Cargo.toml b/myutils/Cargo.toml new file mode 100644 index 0000000..7f938fb --- /dev/null +++ b/myutils/Cargo.toml @@ -0,0 +1,9 @@ +[package] +name = "myutils" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +thiserror = "1.0.52" diff --git a/myutils/src/lib.rs b/myutils/src/lib.rs new file mode 100644 index 0000000..3bb3506 --- /dev/null +++ b/myutils/src/lib.rs @@ -0,0 +1 @@ +pub mod matrix; diff --git a/myutils/src/matrix.rs b/myutils/src/matrix.rs new file mode 100644 index 0000000..e0380b3 --- /dev/null +++ b/myutils/src/matrix.rs @@ -0,0 +1,177 @@ +//! A large number of logical games, by virtue of existing on paper, use 2-dimensional structures. +//! This module implement packed matrices, without the overhead of supporting multiple dimensions. + +use std::ops::{Index, IndexMut}; + +use thiserror::Error; + +/// A Matrix of dynamic size, with elements in `T`. +/// Indexing exposes rows as slices. Individual elements of matrix `m` +/// can be accessed with `m[x][y]`. +#[derive(Clone,Debug,PartialEq,Eq)] +pub struct Matrix { + stride: usize, + vec: Vec, +} + +#[derive(PartialEq, Eq, Debug,Error)] +#[error("incorrect shape")] +pub struct ShapeError; + +impl Matrix { + + /// Create a new matrix from a vector of elements in row-major order. + /// Will fail if the length of `vec` doesn't match the requested shape. + pub fn new(vec: Vec, shape: (usize,usize)) -> Result { + if vec.len() != shape.0 * shape.1 { return Err(ShapeError) } + Ok(Self { vec, stride: shape.1 }) + } + + /// Total number of elements in the matrix. Equal to `shape.0 * shape.1` + pub fn len(&self) -> usize { + self.vec.len() + } + + /// Shape of the matrix + pub fn shape(&self) -> (usize, usize) { + (self.vec.len() / self.stride, self.stride) + } + + /// Creates a new matrix of the same shape by applying a closure to every element + pub fn map(&self, f: F) -> Matrix + where F: FnMut(&T) -> U + { + Matrix { vec: self.vec.iter().map(f).collect(), stride: self.stride } + } + + /// Iterates over the matrix rows + pub fn lines(&self) -> impl Iterator + '_ { + (0..self.vec.len()) + .step_by(self.stride) + .map(|i| &self.vec[i..][..self.stride]) + } + + /// Iterate over all the coordinate pairs in row-major order + pub fn indices(&self) -> impl Iterator { + let (h,w) = self.shape(); + (0..h).flat_map(move |x| (0..w).map(move |y| (x,y))) + } + + /// Lists all the neighbors of the given location, truncating at the edge. + pub fn neighbors(&self, pos: (usize, usize)) -> Vec<(usize,usize)> { + let (x,y) = pos; + let (h, w) = self.shape(); + let mut neighs = Vec::with_capacity(9); + + let mut row = |x| { + if y > 0 { neighs.push((x, y-1)) }; + neighs.push((x, y)); + if y+1 < w { neighs.push((x, y+1))}; + }; + + if x > 0 { row(x-1) }; + row(x); + if x+1 < h { row(x+1) }; + neighs + } + + /// Create a new matrix by applying in parallel an operation to every pair of elements from + /// two source matrices of identical shape. + pub fn zip_with(&self, other: &Matrix, f: F) -> Result, ShapeError> + where F: FnMut((&T, &U)) -> V + { + if self.shape() != other.shape() { + return Err(ShapeError) + } + + Ok(Matrix { + stride: self.stride, + vec: self.vec.iter().zip(&other.vec).map(f).collect(), + }) + } + +} + +impl Index for Matrix { + type Output = [T]; + + fn index(&self, index: usize) -> &Self::Output { + &self.vec[index * self.stride..][..self.stride] + } + +} + +impl IndexMut for Matrix { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.vec[index * self.stride..][..self.stride] + } +} + +macro_rules! umat { + [$e:expr; $shape:expr] => { + $crate::util::matrix::Matrix::new(vec![$e; $shape.0 * $shape.1], $shape).unwrap() + }; +} +pub(crate) use umat; + +#[allow(unused_macros)] +macro_rules! mat { + [ $( $( $e:expr ),+ );* ] => {{ + let mut v = vec![]; + let (mut h, mut t) = (0,0); + $( + $( + v.push($e); t += 1; + )+ + h += 1; + )* + $crate::util::matrix::Matrix::new(v, (h, t/h)).unwrap() + }} +} + +#[allow(unused_imports)] +pub(crate) use mat; + +#[cfg(test)] +mod test { + use crate::util::matrix::ShapeError; + + use super::Matrix; + + #[test] + fn shape() { + assert_eq!(ShapeError, Matrix::new(vec![1,2,3], (2,2)).unwrap_err()) + } + + #[test] + fn lines() { + let m = Matrix::new(vec![1,2,3,4,5,6], (3,2)).unwrap(); + let m2: Vec<_> = m.lines().collect(); + assert_eq!(&m2, &[&[1,2], &[3,4], &[5,6]]); + } + + #[test] + fn indices() { + let m = Matrix::new(vec![(); 6], (3,2)).unwrap(); + let idxs: Vec<_> = m.indices().collect(); + assert_eq!(vec![(0,0),(0,1),(1,0),(1,1),(2,0),(2,1)], idxs); + } + + #[test] + fn access() { + let m = Matrix::new(vec![1,2,3,4], (2,2)).unwrap(); + assert_eq!(m[0][0], 1); + assert_eq!(m[0][1], 2); + assert_eq!(m[1][0], 3); + assert_eq!(m[1][1], 4); + } + + #[test] + fn neighbors() { + let m = umat![(); (4,4)]; + assert_eq!(m.neighbors((0,0)), vec![(0,0),(0,1),(1,0),(1,1)]); + assert_eq!(m.neighbors((0,2)), vec![(0,1),(0,2),(0,3), (1,1), (1,2), (1,3)]); + assert_eq!(m.neighbors((1,2)), vec![(0,1),(0,2),(0,3), (1,1), (1,2), (1,3), (2,1), (2,2), (2,3)]); + assert_eq!(m.neighbors((3,3)), vec![(2,2),(2,3),(3,2),(3,3)]); + } +}