use ndarray::prelude::*;
use ndarray::{s, Data, DataMut, RemoveAxis};
use noisy_float::types::{N32, N64};
pub trait MaybeNan: Sized {
type NotNan;
fn is_nan(&self) -> bool;
fn try_as_not_nan(&self) -> Option<&Self::NotNan>;
fn from_not_nan(_: Self::NotNan) -> Self;
fn from_not_nan_opt(_: Option<Self::NotNan>) -> Self;
fn from_not_nan_ref_opt(_: Option<&Self::NotNan>) -> &Self;
fn remove_nan_mut(_: ArrayViewMut1<'_, Self>) -> ArrayViewMut1<'_, Self::NotNan>;
}
fn remove_nan_mut<A: MaybeNan>(mut view: ArrayViewMut1<'_, A>) -> ArrayViewMut1<'_, A> {
if view.len() == 0 {
return view.slice_move(s![..0]);
}
let mut i = 0;
let mut j = view.len() - 1;
loop {
while i <= j && !view[i].is_nan() {
i += 1;
}
while j > i && view[j].is_nan() {
j -= 1;
}
if i >= j {
return view.slice_move(s![..i]);
} else {
view.swap(i, j);
i += 1;
j -= 1;
}
}
}
macro_rules! impl_maybenan_for_fxx {
($fxx:ident, $Nxx:ident) => {
impl MaybeNan for $fxx {
type NotNan = $Nxx;
fn is_nan(&self) -> bool {
$fxx::is_nan(*self)
}
fn try_as_not_nan(&self) -> Option<&$Nxx> {
$Nxx::try_borrowed(self)
}
fn from_not_nan(value: $Nxx) -> $fxx {
value.raw()
}
fn from_not_nan_opt(value: Option<$Nxx>) -> $fxx {
match value {
None => ::std::$fxx::NAN,
Some(num) => num.raw(),
}
}
fn from_not_nan_ref_opt(value: Option<&$Nxx>) -> &$fxx {
match value {
None => &::std::$fxx::NAN,
Some(num) => num.as_ref(),
}
}
fn remove_nan_mut(view: ArrayViewMut1<'_, $fxx>) -> ArrayViewMut1<'_, $Nxx> {
let not_nan = remove_nan_mut(view);
unsafe {
ArrayViewMut1::from_shape_ptr(not_nan.dim(), not_nan.as_ptr() as *mut $Nxx)
}
}
}
};
}
impl_maybenan_for_fxx!(f32, N32);
impl_maybenan_for_fxx!(f64, N64);
macro_rules! impl_maybenan_for_opt_never_nan {
($ty:ty) => {
impl MaybeNan for Option<$ty> {
type NotNan = NotNone<$ty>;
fn is_nan(&self) -> bool {
self.is_none()
}
fn try_as_not_nan(&self) -> Option<&NotNone<$ty>> {
if self.is_none() {
None
} else {
Some(unsafe { &*(self as *const Option<$ty> as *const NotNone<$ty>) })
}
}
fn from_not_nan(value: NotNone<$ty>) -> Option<$ty> {
value.into_inner()
}
fn from_not_nan_opt(value: Option<NotNone<$ty>>) -> Option<$ty> {
value.and_then(|v| v.into_inner())
}
fn from_not_nan_ref_opt(value: Option<&NotNone<$ty>>) -> &Option<$ty> {
match value {
None => &None,
Some(num) => unsafe { &*(num as *const NotNone<$ty> as *const Option<$ty>) },
}
}
fn remove_nan_mut(view: ArrayViewMut1<'_, Self>) -> ArrayViewMut1<'_, Self::NotNan> {
let not_nan = remove_nan_mut(view);
unsafe {
ArrayViewMut1::from_shape_ptr(
not_nan.dim(),
not_nan.as_ptr() as *mut NotNone<$ty>,
)
}
}
}
};
}
impl_maybenan_for_opt_never_nan!(u8);
impl_maybenan_for_opt_never_nan!(u16);
impl_maybenan_for_opt_never_nan!(u32);
impl_maybenan_for_opt_never_nan!(u64);
impl_maybenan_for_opt_never_nan!(u128);
impl_maybenan_for_opt_never_nan!(i8);
impl_maybenan_for_opt_never_nan!(i16);
impl_maybenan_for_opt_never_nan!(i32);
impl_maybenan_for_opt_never_nan!(i64);
impl_maybenan_for_opt_never_nan!(i128);
impl_maybenan_for_opt_never_nan!(N32);
impl_maybenan_for_opt_never_nan!(N64);
#[derive(Clone, Copy, Debug)]
#[repr(transparent)]
pub struct NotNone<T>(Option<T>);
impl<T> NotNone<T> {
pub fn new(value: T) -> NotNone<T> {
NotNone(Some(value))
}
pub fn try_new(value: Option<T>) -> Option<NotNone<T>> {
if value.is_some() {
Some(NotNone(value))
} else {
None
}
}
pub fn into_inner(self) -> Option<T> {
self.0
}
pub fn unwrap(self) -> T {
match self.0 {
Some(inner) => inner,
None => unsafe { ::std::hint::unreachable_unchecked() },
}
}
pub fn map<U, F>(self, f: F) -> NotNone<U>
where
F: FnOnce(T) -> U,
{
NotNone::new(f(self.unwrap()))
}
}
pub trait MaybeNanExt<A, S, D>
where
A: MaybeNan,
S: Data<Elem = A>,
D: Dimension,
{
fn fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B
where
A: 'a,
F: FnMut(B, &'a A::NotNan) -> B;
fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, f: F) -> B
where
A: 'a,
F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B;
fn visit_skipnan<'a, F>(&'a self, f: F)
where
A: 'a,
F: FnMut(&'a A::NotNan);
fn fold_axis_skipnan<B, F>(&self, axis: Axis, init: B, fold: F) -> Array<B, D::Smaller>
where
D: RemoveAxis,
F: FnMut(&B, &A::NotNan) -> B,
B: Clone;
fn map_axis_skipnan_mut<'a, B, F>(&'a mut self, axis: Axis, mapping: F) -> Array<B, D::Smaller>
where
A: 'a,
S: DataMut,
D: RemoveAxis,
F: FnMut(ArrayViewMut1<'a, A::NotNan>) -> B;
private_decl! {}
}
impl<A, S, D> MaybeNanExt<A, S, D> for ArrayBase<S, D>
where
A: MaybeNan,
S: Data<Elem = A>,
D: Dimension,
{
fn fold_skipnan<'a, F, B>(&'a self, init: B, mut f: F) -> B
where
A: 'a,
F: FnMut(B, &'a A::NotNan) -> B,
{
self.fold(init, |acc, elem| {
if let Some(not_nan) = elem.try_as_not_nan() {
f(acc, not_nan)
} else {
acc
}
})
}
fn indexed_fold_skipnan<'a, F, B>(&'a self, init: B, mut f: F) -> B
where
A: 'a,
F: FnMut(B, (D::Pattern, &'a A::NotNan)) -> B,
{
self.indexed_iter().fold(init, |acc, (idx, elem)| {
if let Some(not_nan) = elem.try_as_not_nan() {
f(acc, (idx, not_nan))
} else {
acc
}
})
}
fn visit_skipnan<'a, F>(&'a self, mut f: F)
where
A: 'a,
F: FnMut(&'a A::NotNan),
{
self.visit(|elem| {
if let Some(not_nan) = elem.try_as_not_nan() {
f(not_nan)
}
})
}
fn fold_axis_skipnan<B, F>(&self, axis: Axis, init: B, mut fold: F) -> Array<B, D::Smaller>
where
D: RemoveAxis,
F: FnMut(&B, &A::NotNan) -> B,
B: Clone,
{
self.fold_axis(axis, init, |acc, elem| {
if let Some(not_nan) = elem.try_as_not_nan() {
fold(acc, not_nan)
} else {
acc.clone()
}
})
}
fn map_axis_skipnan_mut<'a, B, F>(
&'a mut self,
axis: Axis,
mut mapping: F,
) -> Array<B, D::Smaller>
where
A: 'a,
S: DataMut,
D: RemoveAxis,
F: FnMut(ArrayViewMut1<'a, A::NotNan>) -> B,
{
self.map_axis_mut(axis, |lane| mapping(A::remove_nan_mut(lane)))
}
private_impl! {}
}
#[cfg(test)]
mod tests {
use super::*;
use quickcheck_macros::quickcheck;
#[quickcheck]
fn remove_nan_mut_idempotent(is_nan: Vec<bool>) -> bool {
let mut values: Vec<_> = is_nan
.into_iter()
.map(|is_nan| if is_nan { None } else { Some(1) })
.collect();
let view = ArrayViewMut1::from_shape(values.len(), &mut values).unwrap();
let removed = remove_nan_mut(view);
removed == remove_nan_mut(removed.to_owned().view_mut())
}
#[quickcheck]
fn remove_nan_mut_only_nan_remaining(is_nan: Vec<bool>) -> bool {
let mut values: Vec<_> = is_nan
.into_iter()
.map(|is_nan| if is_nan { None } else { Some(1) })
.collect();
let view = ArrayViewMut1::from_shape(values.len(), &mut values).unwrap();
remove_nan_mut(view).iter().all(|elem| !elem.is_nan())
}
#[quickcheck]
fn remove_nan_mut_keep_all_non_nan(is_nan: Vec<bool>) -> bool {
let non_nan_count = is_nan.iter().filter(|&&is_nan| !is_nan).count();
let mut values: Vec<_> = is_nan
.into_iter()
.map(|is_nan| if is_nan { None } else { Some(1) })
.collect();
let view = ArrayViewMut1::from_shape(values.len(), &mut values).unwrap();
remove_nan_mut(view).len() == non_nan_count
}
}
mod impl_not_none;