use rayon::prelude::*;
pub fn trinum(x: usize) -> usize {
x * (x + 1) / 2
}
pub fn checked_trinum(x: usize) -> Option<usize> {
x.checked_add(1)
.and_then(|t| t.checked_mul(x))
.map(|t| t / 2)
}
#[derive(Clone, Debug)]
pub struct Triangular<T> {
dim: usize,
linear: Box<[T]>,
}
impl<T> Triangular<T> {
pub fn new<F>(dim: usize, mut contents: F) -> Triangular<T>
where
F: FnMut((usize, usize)) -> T,
{
let mut linear = Vec::with_capacity(trinum(dim));
for row in 0..dim {
for col in 0..=row {
linear.push(contents((row, col)));
}
}
let linear = linear.into_boxed_slice();
Triangular { dim, linear }
}
pub fn par_new<F>(dim: usize, contents: F) -> Triangular<T>
where
F: Sync + Fn((usize, usize)) -> T,
T: Send,
{
let contents = &contents;
let linear = (0..dim)
.into_par_iter()
.flat_map(|row| {
(0..=row)
.into_par_iter()
.map(move |col| (contents)((row, col)))
})
.collect::<Vec<_>>()
.into_boxed_slice();
Triangular { dim, linear }
}
pub fn dim(&self) -> usize {
self.dim
}
fn linear_index(&self, (row, col): (usize, usize)) -> usize {
trinum(row) + col
}
fn checked_linear_index(&self, (row, col): (usize, usize)) -> Result<usize, &'static str> {
if !(row < self.dim) {
return Err("first index out of bounds");
}
if !(col <= row) {
return Err("second index larger than first index");
}
Ok(self.linear_index((row, col)))
}
pub unsafe fn get_unchecked(&self, (row, col): (usize, usize)) -> &T {
let idx = self.linear_index((row, col));
unsafe { self.linear.get_unchecked(idx) }
}
pub unsafe fn get_unchecked_mut(&mut self, (row, col): (usize, usize)) -> &mut T {
let idx = self.linear_index((row, col));
unsafe { self.linear.get_unchecked_mut(idx) }
}
}
impl<T> std::ops::Index<(usize, usize)> for Triangular<T> {
type Output = T;
fn index(&self, (row, col): (usize, usize)) -> &T {
let idx = match self.checked_linear_index((row, col)) {
Ok(x) => x,
Err(x) => panic!("invalid indices for triangular matrix: {x}"),
};
unsafe { self.linear.get_unchecked(idx) }
}
}
impl<T> std::ops::IndexMut<(usize, usize)> for Triangular<T> {
fn index_mut(&mut self, (row, col): (usize, usize)) -> &mut T {
let idx = match self.checked_linear_index((row, col)) {
Ok(x) => x,
Err(x) => panic!("invalid indices when mutably indexing triangular matrix: {x}"),
};
unsafe { self.linear.get_unchecked_mut(idx) }
}
}
#[cfg(test)]
mod tests {
use super::{checked_trinum, trinum, Triangular};
#[test]
fn test_trinum() {
assert_eq!(checked_trinum(0), Some(0));
assert_eq!(checked_trinum(1), Some(1));
assert_eq!(checked_trinum(2), Some(3));
assert_eq!(checked_trinum(3), Some(6));
assert_eq!(checked_trinum(4), Some(10));
assert_eq!(checked_trinum(5), Some(15));
assert_eq!(checked_trinum(6), Some(21));
assert_eq!(checked_trinum(7), Some(28));
assert_eq!(checked_trinum(8), Some(36));
assert_eq!(checked_trinum(100), Some(5050));
assert_eq!(checked_trinum(usize::MAX / 16), None);
for i in 0..100 {
assert_eq!(Some(trinum(i)), checked_trinum(i));
}
}
#[test]
fn test_triangular() {
let calc = |(i, j)| (10 * i + j) as i16;
let mut m = Triangular::<i16>::new(5, calc);
assert_eq!(m.dim(), 5);
for i in 0..5 {
for j in 0..=i {
assert_eq!(m[(i, j)], calc((i, j)));
}
}
m[(0, 0)] = -1;
m[(3, 0)] = -2;
m[(4, 3)] = -3;
m[(4, 4)] = -4;
assert_eq!(m[(0, 0)], -1);
assert_eq!(m[(3, 0)], -2);
assert_eq!(m[(4, 3)], -3);
assert_eq!(m[(4, 4)], -4);
}
#[test]
#[should_panic]
fn test_triangular_index_too_large() {
let m = Triangular::<()>::new(3, |_| ());
m[(3, 0)];
}
#[test]
#[should_panic]
fn test_triangular_index_wrongly_ordered() {
let m = Triangular::<()>::new(3, |_| ());
m[(1, 2)];
}
}