use core::{
fmt::Debug,
iter::Sum,
ops::{
Add,
AddAssign,
Deref,
Index,
Mul,
MulAssign,
RangeBounds,
Sub,
SubAssign,
},
slice::SliceIndex,
};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
#[repr(transparent)]
pub struct Dim<T, const N: usize>(pub(crate) [T; N]);
impl<T, const N: usize> Dim<T, N> {
#[inline]
#[must_use]
pub fn axis<const IDX: usize>(&self) -> &T {
const {
assert!(IDX < N, "flense: must get an in-bounds dimension");
}
unsafe { self.0.get_unchecked(IDX) }
}
#[inline]
#[must_use]
pub fn get<I>(&self, index: I) -> Option<&I::Output>
where
I: SliceIndex<[T]>,
{
self.0.get(index)
}
#[inline]
#[must_use]
pub unsafe fn get_unchecked<I>(&self, index: I) -> &I::Output
where
I: SliceIndex<[T]>,
{
unsafe { self.0.get_unchecked(index) }
}
}
impl<T, const N: usize> Index<usize> for Dim<T, N> {
type Output = T;
fn index(&self, index: usize) -> &Self::Output {
#[expect(clippy::indexing_slicing, reason = "panicking is allowable in Index")]
&self.0[index]
}
}
impl<'a, T, const N: usize> Dim<T, N>
where
T: 'a + Sum<&'a T>,
{
#[inline]
#[must_use]
pub fn sum(&'a self) -> T {
self.0.iter().sum()
}
}
impl<T, const N: usize> Dim<T, N>
where
T: PartialOrd,
{
#[inline]
#[must_use]
pub fn contains(&self, other: &Self) -> bool {
self.0
.iter()
.zip(other.0.iter())
.all(|(lhs, rhs)| lhs > rhs)
}
#[inline]
#[must_use]
pub fn is_compatible_with(&self, other: &Self) -> bool {
self.0
.iter()
.zip(other.0.iter())
.all(|(lhs, rhs)| lhs <= rhs)
}
}
impl<T, const N: usize> Dim<T, N>
where
T: Copy + PartialOrd + Ord,
{
#[inline]
#[must_use]
pub fn min(&self, other: &Self) -> Self {
let mut result = self.0;
for (lhs, rhs) in result.iter_mut().zip(other.0.iter()) {
*lhs = (*lhs).min(*rhs);
}
Self(result)
}
}
impl<R, const N: usize> Dim<R, N>
where
R: RangeBounds<usize>,
{
#[inline]
#[must_use]
pub fn to_indices(&self, dimensions: Dim<usize, N>) -> Option<(Dim<usize, N>, Dim<usize, N>)> {
use core::ops::Bound::{
Excluded,
Included,
Unbounded,
};
let mut starts = [0; N];
let mut ends = [0; N];
for (((range, len), start_out), end_out) in self
.0
.iter()
.zip(dimensions.0)
.zip(starts.iter_mut())
.zip(ends.iter_mut())
{
*start_out = match range.start_bound() {
Included(start) => *start,
Excluded(start) => start.checked_add(1)?,
Unbounded => 0,
};
*end_out = match range.end_bound() {
Included(end) => end.checked_add(1)?,
Excluded(end) => *end,
Unbounded => len,
};
}
Some((Dim(starts), Dim(ends)))
}
}
impl<T> From<T> for Dim<T, 1> {
#[inline]
fn from(value: T) -> Self {
Self([value])
}
}
impl<T> Deref for Dim<T, 1> {
type Target = T;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0[0]
}
}
impl<T, const N: usize> From<[T; N]> for Dim<T, N> {
#[inline]
fn from(value: [T; N]) -> Self {
Self(value)
}
}
impl<const N: usize> TryFrom<Dim<usize, N>> for Dim<isize, N> {
type Error = <isize as TryFrom<usize>>::Error;
#[inline]
fn try_from(value: Dim<usize, N>) -> Result<Self, Self::Error> {
let mut result = [0; N];
for (elt, value) in result.iter_mut().zip(value.0) {
*elt = value.try_into()?;
}
Ok(Self(result))
}
}
macro_rules! impl_dim_op {
($trait:ident, $method:ident, $op:tt, $traitassign:ident, $methodassign:ident, $opassign:tt) => {
impl<T, const N: usize> $trait for Dim<T, N>
where
T: Copy + $trait<Output = T>,
{
type Output = Self;
#[inline]
fn $method(mut self, rhs: Self) -> Self::Output {
for (lhs, rhs) in self.0.iter_mut().zip(rhs.0) {
*lhs = *lhs $op rhs;
}
self
}
}
impl<T, const N: usize> $traitassign for Dim<T, N>
where
T: $traitassign
{
#[inline]
fn $methodassign(self: &mut Self, rhs: Self) {
for (lhs, rhs) in self.0.iter_mut().zip(rhs.0) {
*lhs $opassign rhs;
}
}
}
}
}
impl_dim_op!(Add, add, +, AddAssign, add_assign, +=);
impl_dim_op!(Sub, sub, -, SubAssign, sub_assign, -=);
impl_dim_op!(Mul, mul, *, MulAssign, mul_assign, *=);