use crate::coord::ranged1d::types::RangedCoordusize;
use crate::coord::ranged1d::{
AsRangedCoord, DiscreteRanged, KeyPointHint, NoDefaultFormatting, Ranged, ValueFormatter,
};
use std::cmp::{Ordering, PartialOrd};
use std::marker::PhantomData;
use std::ops::{Add, Range, Sub};
pub trait LinspaceRoundingMethod<V> {
fn search(values: &[V], target: &V) -> Option<usize>;
}
#[derive(Clone)]
pub struct Exact<V>(PhantomData<V>);
impl<V: PartialOrd> LinspaceRoundingMethod<V> for Exact<V> {
fn search(values: &[V], target: &V) -> Option<usize> {
values.iter().position(|x| target == x)
}
}
#[derive(Clone)]
pub struct Ceil<V>(PhantomData<V>);
impl<V: PartialOrd> LinspaceRoundingMethod<V> for Ceil<V> {
fn search(values: &[V], target: &V) -> Option<usize> {
let ascending = if values.len() < 2 {
true
} else {
values[0].partial_cmp(&values[1]) == Some(Ordering::Less)
};
match values.binary_search_by(|probe| {
if ascending {
probe.partial_cmp(target).unwrap()
} else {
target.partial_cmp(probe).unwrap()
}
}) {
Ok(idx) => Some(idx),
Err(idx) => {
let offset = if ascending { 0 } else { 1 };
if idx < offset || idx >= values.len() + offset {
return None;
}
Some(idx - offset)
}
}
}
}
#[derive(Clone)]
pub struct Floor<V>(PhantomData<V>);
impl<V: PartialOrd> LinspaceRoundingMethod<V> for Floor<V> {
fn search(values: &[V], target: &V) -> Option<usize> {
let ascending = if values.len() < 2 {
true
} else {
values[0].partial_cmp(&values[1]) == Some(Ordering::Less)
};
match values.binary_search_by(|probe| {
if ascending {
probe.partial_cmp(target).unwrap()
} else {
target.partial_cmp(probe).unwrap()
}
}) {
Ok(idx) => Some(idx),
Err(idx) => {
let offset = if ascending { 1 } else { 0 };
if idx < offset || idx >= values.len() + offset {
return None;
}
Some(idx - offset)
}
}
}
}
#[derive(Clone)]
pub struct Round<V, S>(PhantomData<(V, S)>);
impl<V, S> LinspaceRoundingMethod<V> for Round<V, S>
where
V: Add<S, Output = V> + PartialOrd + Sub<V, Output = S> + Clone,
S: PartialOrd + Clone,
{
fn search(values: &[V], target: &V) -> Option<usize> {
let ascending = if values.len() < 2 {
true
} else {
values[0].partial_cmp(&values[1]) == Some(Ordering::Less)
};
match values.binary_search_by(|probe| {
if ascending {
probe.partial_cmp(target).unwrap()
} else {
target.partial_cmp(probe).unwrap()
}
}) {
Ok(idx) => Some(idx),
Err(idx) => {
if idx == 0 {
return Some(0);
}
if idx == values.len() {
return Some(idx - 1);
}
let left_delta = if ascending {
target.clone() - values[idx - 1].clone()
} else {
values[idx - 1].clone() - target.clone()
};
let right_delta = if ascending {
values[idx].clone() - target.clone()
} else {
target.clone() - values[idx].clone()
};
if left_delta.partial_cmp(&right_delta) == Some(Ordering::Less) {
Some(idx - 1)
} else {
Some(idx)
}
}
}
}
}
#[derive(Clone)]
pub struct Linspace<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>>
where
T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone,
{
step: S,
inner: T,
grid_value: Vec<T::ValueType>,
_phatom: PhantomData<R>,
}
impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> Linspace<T, S, R>
where
T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone,
{
fn compute_grid_values(&mut self) {
let range = self.inner.range();
match (
range.start.partial_cmp(&range.end),
(range.start.clone() + self.step.clone()).partial_cmp(&range.end),
) {
(Some(a), Some(b)) if a != b || a == Ordering::Equal || b == Ordering::Equal => (),
(Some(a), Some(_)) => {
let mut current = range.start;
while current.partial_cmp(&range.end) == Some(a) {
self.grid_value.push(current.clone());
current = current + self.step.clone();
}
}
_ => (),
}
}
pub fn use_ceil(self) -> Linspace<T, S, Ceil<T::ValueType>> {
Linspace {
step: self.step,
inner: self.inner,
grid_value: self.grid_value,
_phatom: PhantomData,
}
}
pub fn use_floor(self) -> Linspace<T, S, Floor<T::ValueType>> {
Linspace {
step: self.step,
inner: self.inner,
grid_value: self.grid_value,
_phatom: PhantomData,
}
}
pub fn use_round(self) -> Linspace<T, S, Round<T::ValueType, S>>
where
T::ValueType: Sub<T::ValueType, Output = S>,
S: PartialOrd,
{
Linspace {
step: self.step,
inner: self.inner,
grid_value: self.grid_value,
_phatom: PhantomData,
}
}
pub fn use_exact(self) -> Linspace<T, S, Exact<T::ValueType>>
where
T::ValueType: Sub<T::ValueType, Output = S>,
S: PartialOrd,
{
Linspace {
step: self.step,
inner: self.inner,
grid_value: self.grid_value,
_phatom: PhantomData,
}
}
}
impl<T, R, S, RM> ValueFormatter<T> for Linspace<R, S, RM>
where
R: Ranged<ValueType = T> + ValueFormatter<T>,
RM: LinspaceRoundingMethod<T>,
T: Add<S, Output = T> + PartialOrd + Clone,
S: Clone,
{
fn format(value: &T) -> String {
R::format(value)
}
}
impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> Ranged for Linspace<T, S, R>
where
T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone,
{
type FormatOption = NoDefaultFormatting;
type ValueType = T::ValueType;
fn range(&self) -> Range<T::ValueType> {
self.inner.range()
}
fn map(&self, value: &T::ValueType, limit: (i32, i32)) -> i32 {
self.inner.map(value, limit)
}
fn key_points<Hint: KeyPointHint>(&self, hint: Hint) -> Vec<T::ValueType> {
if self.grid_value.is_empty() {
return vec![];
}
let idx_range: RangedCoordusize = (0..(self.grid_value.len() - 1)).into();
idx_range
.key_points(hint)
.into_iter()
.map(|x| self.grid_value[x].clone())
.collect()
}
}
impl<T: Ranged, S: Clone, R: LinspaceRoundingMethod<T::ValueType>> DiscreteRanged
for Linspace<T, S, R>
where
T::ValueType: Add<S, Output = T::ValueType> + PartialOrd + Clone,
{
fn size(&self) -> usize {
self.grid_value.len()
}
fn index_of(&self, value: &T::ValueType) -> Option<usize> {
R::search(self.grid_value.as_ref(), value)
}
fn from_index(&self, idx: usize) -> Option<T::ValueType> {
self.grid_value.get(idx).cloned()
}
}
pub trait IntoLinspace: AsRangedCoord {
fn step<S: Clone>(self, val: S) -> Linspace<Self::CoordDescType, S, Exact<Self::Value>>
where
Self::Value: Add<S, Output = Self::Value> + PartialOrd + Clone,
{
let mut ret = Linspace {
step: val,
inner: self.into(),
grid_value: vec![],
_phatom: PhantomData,
};
ret.compute_grid_values();
ret
}
}
impl<T: AsRangedCoord> IntoLinspace for T {}
#[cfg(test)]
mod test {
use super::*;
#[test]
fn test_float_linspace() {
let coord = (0.0f64..100.0f64).step(0.1);
assert_eq!(coord.map(&23.12, (0, 10000)), 2312);
assert_eq!(coord.range(), 0.0..100.0);
assert_eq!(coord.key_points(100000).len(), 1001);
assert_eq!(coord.size(), 1001);
assert_eq!(coord.index_of(&coord.from_index(230).unwrap()), Some(230));
assert!((coord.from_index(230).unwrap() - 23.0).abs() < 1e-5);
}
#[test]
fn test_rounding_methods() {
let coord = (0.0f64..100.0f64).step(1.0);
assert_eq!(coord.index_of(&1.0), Some(1));
assert_eq!(coord.index_of(&1.2), None);
let coord = coord.use_floor();
assert_eq!(coord.index_of(&1.0), Some(1));
assert_eq!(coord.index_of(&1.2), Some(1));
assert_eq!(coord.index_of(&23.9), Some(23));
assert_eq!(coord.index_of(&10000.0), Some(99));
assert_eq!(coord.index_of(&-1.0), None);
let coord = coord.use_ceil();
assert_eq!(coord.index_of(&1.0), Some(1));
assert_eq!(coord.index_of(&1.2), Some(2));
assert_eq!(coord.index_of(&23.9), Some(24));
assert_eq!(coord.index_of(&10000.0), None);
assert_eq!(coord.index_of(&-1.0), Some(0));
let coord = coord.use_round();
assert_eq!(coord.index_of(&1.0), Some(1));
assert_eq!(coord.index_of(&1.2), Some(1));
assert_eq!(coord.index_of(&1.7), Some(2));
assert_eq!(coord.index_of(&23.9), Some(24));
assert_eq!(coord.index_of(&10000.0), Some(99));
assert_eq!(coord.index_of(&-1.0), Some(0));
let coord = (0.0f64..-100.0f64).step(-1.0);
assert_eq!(coord.index_of(&-1.0), Some(1));
assert_eq!(coord.index_of(&-1.2), None);
let coord = coord.use_floor();
assert_eq!(coord.index_of(&-1.0), Some(1));
assert_eq!(coord.index_of(&-1.2), Some(2));
assert_eq!(coord.index_of(&-23.9), Some(24));
assert_eq!(coord.index_of(&-10000.0), None);
assert_eq!(coord.index_of(&1.0), Some(0));
let coord = coord.use_ceil();
assert_eq!(coord.index_of(&-1.0), Some(1));
assert_eq!(coord.index_of(&-1.2), Some(1));
assert_eq!(coord.index_of(&-23.9), Some(23));
assert_eq!(coord.index_of(&-10000.0), Some(99));
assert_eq!(coord.index_of(&1.0), None);
let coord = coord.use_round();
assert_eq!(coord.index_of(&-1.0), Some(1));
assert_eq!(coord.index_of(&-1.2), Some(1));
assert_eq!(coord.index_of(&-1.7), Some(2));
assert_eq!(coord.index_of(&-23.9), Some(24));
assert_eq!(coord.index_of(&-10000.0), Some(99));
assert_eq!(coord.index_of(&1.0), Some(0));
}
#[cfg(feature = "chrono")]
#[test]
fn test_duration_linspace() {
use chrono::Duration;
let coord = (Duration::seconds(0)..Duration::seconds(100)).step(Duration::milliseconds(1));
assert_eq!(coord.size(), 100_000);
assert_eq!(coord.index_of(&coord.from_index(230).unwrap()), Some(230));
assert_eq!(coord.key_points(10000000).len(), 100_000);
assert_eq!(coord.range(), Duration::seconds(0)..Duration::seconds(100));
assert_eq!(coord.map(&Duration::seconds(25), (0, 100_000)), 25000);
}
}