diff --git a/downsample_rs/Cargo.toml b/downsample_rs/Cargo.toml index 107469b..50b2185 100644 --- a/downsample_rs/Cargo.toml +++ b/downsample_rs/Cargo.toml @@ -9,6 +9,9 @@ license = "MIT" [dependencies] # TODO: perhaps use polars? argminmax = { version = "0.6.1", features = ["half"] } +# For some reason we need to explicitely add chrono, otherwise polars refuses to compile? +chrono = "0.4.31" +polars = { version = "0.33.2", features = ["lazy", "streaming", "zip_with"] } half = { version = "2.3.1", default-features = false , features=["num-traits"], optional = true} num-traits = { version = "0.2.17", default-features = false } once_cell = "1" diff --git a/downsample_rs/src/helpers.rs b/downsample_rs/src/helpers.rs index f2961ab..73c091c 100644 --- a/downsample_rs/src/helpers.rs +++ b/downsample_rs/src/helpers.rs @@ -1,4 +1,5 @@ use num_traits::AsPrimitive; +use polars::prelude::{ChunkAgg, ChunkAnyValue, ChunkedArray, Float64Type}; use crate::types::Num; @@ -31,3 +32,117 @@ where self.iter().fold(0f64, |acc, &x| acc + x.as_()) as f64 / self.len() as f64 } } + +impl Average for Vec +where + T: Num + AsPrimitive, +{ + fn average(&self) -> f64 { + self.iter().fold(0f64, |acc, &x| acc + x.as_()) as f64 / self.len() as f64 + } +} + +pub trait LttbParam { + type IterType: Iterator; + type SliceType: LttbParam; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType; + + fn get(&self, index: usize) -> f64; + + fn into_iter(&self) -> Self::IterType; + + fn len(&self) -> usize; + + fn average(&self) -> f64; +} + +impl LttbParam for [T] +where + T: Num + AsPrimitive, +{ + type IterType = std::vec::IntoIter; + type SliceType = std::vec::Vec; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self[start_idx..end_idx].iter().map(|v| v.as_()).collect() + } + + fn get(&self, index: usize) -> f64 { + self[index].as_() + } + + fn into_iter(&self) -> Self::IterType { + IntoIterator::into_iter(self.iter().map(|v| v.as_()).collect::>()) + } + + fn len(&self) -> usize { + self.len() + } + + fn average(&self) -> f64 { + Average::average(self) + } +} + +impl LttbParam for Vec +where + T: Num + AsPrimitive, +{ + type IterType = std::vec::IntoIter; + type SliceType = std::vec::Vec; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self[start_idx..end_idx].iter().map(|v| v.as_()).collect() + } + + fn get(&self, index: usize) -> f64 { + self[index].as_() + } + + fn into_iter(&self) -> Self::IterType { + IntoIterator::into_iter(self.iter().map(|v| v.as_()).collect::>()) + } + + fn len(&self) -> usize { + self.len() + } + + fn average(&self) -> f64 { + Average::average(self) + } +} + +impl LttbParam for ChunkedArray { + type IterType = std::vec::IntoIter; + type SliceType = ChunkedArray; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self.slice(start_idx as i64, end_idx - start_idx) + } + + fn get(&self, index: usize) -> f64 { + match self + .get_any_value(index) + .unwrap() + .cast(&polars::prelude::DataType::Float64) + .unwrap() + { + polars::prelude::AnyValue::Float64(x) => x, + _ => unreachable!(), // this can never be reached, as it should have panicked when casting + } + } + + fn into_iter(&self) -> Self::IterType { + // TODO: fix this so we don't do any needless copying + self.into_no_null_iter().collect::>().into_iter() + } + + fn len(&self) -> usize { + self.len() + } + + fn average(&self) -> f64 { + self.mean().unwrap_or(0.0) + } +} diff --git a/downsample_rs/src/lttb.rs b/downsample_rs/src/lttb.rs index 20d28d4..ca25032 100644 --- a/downsample_rs/src/lttb.rs +++ b/downsample_rs/src/lttb.rs @@ -1,6 +1,7 @@ -use super::helpers::Average; +use super::helpers::LttbParam; use super::types::Num; use num_traits::AsPrimitive; +use polars::prelude::*; use std::cmp; #[inline(always)] @@ -37,51 +38,7 @@ pub fn lttb_with_x, Ty: Num + AsPrimitive>( for i in 0..n_out - 2 { // Calculate point average for next bucket (containing c). - let avg_range_start = (every * (i + 1) as f64) as usize + 1; - let avg_range_end = cmp::min((every * (i + 2) as f64) as usize + 1, x.len()); - - let y_slice = &y[avg_range_start..avg_range_end]; - let avg_y: f64 = y_slice.average(); - // TODO: avg_y could be approximated argminmax instead of mean? - // TODO: below is faster than above, but not as accurate - // let avg_x: f64 = (x_slice[avg_range_end - 1].as_() + x_slice[avg_range_start].as_()) / 2.0; - let avg_x: f64 = unsafe { - (x.get_unchecked(avg_range_end - 1).as_() + x.get_unchecked(avg_range_start).as_()) - / 2.0 - }; - - // Get the range for this bucket - let range_offs = (every * i as f64) as usize + 1; - let range_to = avg_range_start; // = start of the next bucket - - // Point a - let point_ax = unsafe { x.get_unchecked(a).as_() }; - let point_ay = unsafe { y.get_unchecked(a).as_() }; - - let d1 = point_ax - avg_x; - let d2 = avg_y - point_ay; - let offset: f64 = d1 * point_ay + d2 * point_ax; - - let x_slice = &x[range_offs..range_to]; - let y_slice = &y[range_offs..range_to]; - (_, a) = y_slice.iter().zip(x_slice.iter()).enumerate().fold( - (-1i64, a), - |(max_area, a), (i, (y_, x_))| { - // Calculate triangle area over three buckets - // -> area = d1 * (y_ - point_ay) - (point_ax - x_) * d2; - // let area = d1 * y[i].as_() + d2 * x[i].as_() - offset; - // let area = d1 * y_slice[i].as_() + d2 * x_slice[i].as_() - offset; - let area = d1 * y_.as_() + d2 * x_.as_() - offset; - let area = f64_to_i64unsigned(area); // this is faster than abs - if area > max_area { - (area, i) - } else { - (max_area, a) - } - }, - ); - a += range_offs; - + a = lttb_generic(x, y, i, every, a); sampled_indices[i + 1] = a; } @@ -106,70 +63,62 @@ pub fn lttb_without_x>(y: &[Ty], n_out: usize) -> Vec let mut sampled_indices: Vec = vec![usize::default(); n_out]; + let x = (0..y.len()).map(|v| v.as_()).collect::>(); + // Always add the first point sampled_indices[0] = 0; for i in 0..n_out - 2 { - // Calculate point average for next bucket (containing c). - let avg_range_start = (every * (i + 1) as f64) as usize + 1; - let avg_range_end = cmp::min((every * (i + 2) as f64) as usize + 1, y.len()); - - let y_slice = &y[avg_range_start..avg_range_end]; - let avg_y: f64 = y_slice.average(); - let avg_x: f64 = (avg_range_start + avg_range_end - 1) as f64 / 2.0; - - // Get the range for this bucket - let range_offs = (every * i as f64) as usize + 1; - let range_to = avg_range_start; // = start of the next bucket - - // Point a - let point_ay = unsafe { y.get_unchecked(a).as_() }; - let point_ax = a as f64; - - let d1 = point_ax - avg_x; - let d2 = avg_y - point_ay; - let point_ax = point_ax - range_offs as f64; - - // let mut max_area = -1i64; - let mut ax_x = point_ax; // point_ax - x[i] - let offset: f64 = d1 * point_ay; - - // TODO: for some reason is this faster than the loop below -> check if this is true for other devices - let y_slice = &y[range_offs..range_to]; - (_, a) = y_slice - .iter() - .enumerate() - .fold((-1i64, a), |(max_area, a), (i, y)| { - // Calculate triangle area over three buckets - // -> area: f64 = d1 * y[i].as_() - ax_x * d2; - let area: f64 = d1 * y.as_() - ax_x * d2 - offset; - let area: i64 = f64_to_i64unsigned(area); - ax_x -= 1.0; - if area > max_area { - (area, i + range_offs) - } else { - (max_area, a) - } - }); - - // let y_slice = unsafe { std::slice::from_raw_parts(y_ptr.add(range_offs), range_to - range_offs) }; - // (_, a) = y_slice - // .iter() - // .enumerate() - // .fold((-1i64, a), |(max_area, a), (i, y_)| { - // // Calculate triangle area over three buckets - // // -> area: f64 = d1 * y[i].as_() - ax_x * d2; - // let area: f64 = d1 * y_.as_() - ax_x * d2 - offset; - // let area: i64 = f64_to_i64unsigned(area); - // ax_x -= 1.0; - // if area > max_area { - // (area, i) - // } else { - // (max_area, a) - // } - // }); - // a += range_offs; + a = lttb_generic::<[f64], [Ty]>(&x, y, i, every, a); + sampled_indices[i + 1] = a; + } + + // Always add the last point + sampled_indices[n_out - 1] = y.len() - 1; + + sampled_indices +} + +pub fn lttb_with_x_ca( + x: &ChunkedArray, + y: &ChunkedArray, + n_out: usize, +) -> Vec +where + Tx: PolarsNumericType, + Ty: PolarsNumericType, +{ + assert_eq!(x.len(), y.len()); + if n_out >= x.len() { + return (0..x.len()).collect::>(); + } + assert!(n_out >= 3); // avoid division by 0 + let x = x + .cast(&DataType::Float64) + .unwrap() + .f64() + .unwrap() + .to_owned(); + let y = y + .cast(&DataType::Float64) + .unwrap() + .f64() + .unwrap() + .to_owned(); + + // Bucket size. Leave room for start and end data points. + let every: f64 = (x.len() - 2) as f64 / (n_out - 2) as f64; + // Initially a is the first point in the triangle. + let mut a: usize = 0; + + let mut sampled_indices: Vec = vec![usize::default(); n_out]; + + // Always add the first point + sampled_indices[0] = 0; + + for i in 0..n_out - 2 { + a = lttb_generic(&x, &y, i, every, a); sampled_indices[i + 1] = a; } @@ -179,11 +128,108 @@ pub fn lttb_without_x>(y: &[Ty], n_out: usize) -> Vec sampled_indices } +pub fn lttb_without_x_ca(y: &ChunkedArray, n_out: usize) -> Vec { + if n_out >= y.len() { + return (0..y.len()).collect::>(); + } + assert!(n_out >= 3); // avoid division by 0 + + let y = y + .cast(&DataType::Float64) + .unwrap() + .f64() + .unwrap() + .to_owned(); + + // Bucket size. Leave room for start and end data points. + let every: f64 = (y.len() - 2) as f64 / (n_out - 2) as f64; + // Initially a is the first point in the triangle. + let mut a: usize = 0; + + let mut sampled_indices: Vec = vec![usize::default(); n_out]; + + let x = (0..y.len()).map(|v| v.as_()).collect::>(); + + // Always add the first point + sampled_indices[0] = 0; + + for i in 0..n_out - 2 { + a = lttb_generic(&x, &y, i, every, a); + + sampled_indices[i + 1] = a; + } + + // Always add the last point + sampled_indices[n_out - 1] = y.len() - 1; + + sampled_indices +} + +fn lttb_generic( + x: &X, + y: &Y, + index: usize, + every: f64, + a: usize, +) -> usize { + // Calculate point average for next bucket (containing c). + let avg_range_start = (every * (index + 1) as f64) as usize + 1; + let avg_range_end = cmp::min((every * (index + 2) as f64) as usize + 1, x.len()); + + let y_slice = y.slice(avg_range_start, avg_range_end); + // TODO: there is an implementation for mean... but how do we need to type the parameters? + let avg_y: f64 = y_slice.average(); + // TODO: avg_y could be approximated argminmax instead of mean? + // TODO: below is faster than above, but not as accurate + // let avg_x: f64 = (x_slice[avg_range_end - 1].as_() + x_slice[avg_range_start].as_()) / 2.0; + let avg_x = (x.get(avg_range_end - 1) + x.get(avg_range_start)) / 2.0; + + // Get the range for this bucket + let range_offs = (every * index as f64) as usize + 1; + let range_to = avg_range_start; // = start of the next bucket + + // Point a + let point_ax = x.get(a); + let point_ay = y.get(a); + + let d1 = point_ax - avg_x; + let d2 = avg_y - point_ay; + + let offset = d1 * point_ay + d2 * point_ax; + + let x_slice = x.slice(range_offs, range_to); + let y_slice = y.slice(range_offs, range_to); + let mut a = a; + + (_, a) = y_slice + .into_iter() + .zip(x_slice.into_iter()) + .enumerate() + .fold((-1i64, a), |(max_area, a), (i, (y_, x_))| { + // Calculate triangle area over three buckets + // -> area = d1 * (y_ - point_ay) - (point_ax - x_) * d2; + // let area = d1 * y[i].as_() + d2 * x[i].as_() - offset; + // let area = d1 * y_slice[i].as_() + d2 * x_slice[i].as_() - offset; + let area = d1 * y_ + d2 * x_ - offset; + let area = f64_to_i64unsigned(area); // this is faster than abs + if area > max_area { + (area, i) + } else { + (max_area, a) + } + }); + + a + range_offs +} + // --------------------------------------- TESTS --------------------------------------- #[cfg(test)] mod tests { use dev_utils::utils; + use polars::prelude::{ChunkedArray, Float32Type, Int32Type}; + + use crate::{lttb_with_x_ca, lttb_without_x_ca}; use super::{lttb_with_x, lttb_without_x}; @@ -195,6 +241,24 @@ mod tests { assert_eq!(sampled_indices, vec![0, 1, 5, 9]); } + #[test] + fn test_ca_lttb_with_x() { + let x: ChunkedArray = + ChunkedArray::from_vec("x", vec![0, 1, 2, 3, 4, 5, 6, 7, 8, 9]); + let y: ChunkedArray = + ChunkedArray::from_vec("y", vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]); + let sampled_indices = lttb_with_x_ca(&x, &y, 4); + assert_eq!(sampled_indices, vec![0, 1, 5, 9]); + } + + #[test] + fn test_ca_lttb_without_x() { + let y: ChunkedArray = + ChunkedArray::from_vec("y", vec![0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]); + let sampled_indices = lttb_without_x_ca(&y, 4); + assert_eq!(sampled_indices, vec![0, 1, 5, 9]); + } + #[test] fn test_lttb_without_x() { let y = [0.0, 1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0]; diff --git a/downsample_rs/src/m4.rs b/downsample_rs/src/m4.rs index 75ce224..bbd93be 100644 --- a/downsample_rs/src/m4.rs +++ b/downsample_rs/src/m4.rs @@ -11,6 +11,54 @@ use super::POOL; // ----------------------------------- NON-PARALLEL ------------------------------------ +pub trait M4Param { + type SliceType: M4Param; + + fn len(&self) -> usize; + + fn argminmax(&self) -> (usize, usize); + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType; +} + +impl M4Param for Vec +where + Vec: ArgMinMax, +{ + type SliceType = std::vec::Vec; + + fn len(&self) -> usize { + self.len() + } + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self[start_idx..end_idx].to_vec() + } + + fn argminmax(&self) -> (usize, usize) { + ArgMinMax::argminmax(self) + } +} + +impl M4Param for [T] +where + for<'a> &'a [T]: ArgMinMax, +{ + type SliceType = std::vec::Vec; + + fn len(&self) -> usize { + self.len() + } + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType { + self[start_idx..end_idx].to_vec() + } + + fn argminmax(&self) -> (usize, usize) { + ArgMinMax::argminmax(&self) + } +} + // ----------- WITH X macro_rules! m4_with_x { @@ -100,11 +148,7 @@ m4_without_x_parallel!(m4_without_x_parallel_nan, NaNArgMinMax, |arr| arr // --------------------- WITHOUT X #[inline(always)] -pub(crate) fn m4_generic( - arr: &[T], - n_out: usize, - f_argminmax: fn(&[T]) -> (usize, usize), -) -> Vec { +pub(crate) fn m4_generic(arr: &T, n_out: usize) -> Vec { // Assumes n_out is a multiple of 4 if n_out >= arr.len() { return (0..arr.len()).collect(); @@ -116,13 +160,14 @@ pub(crate) fn m4_generic( let mut sampled_indices: Vec = vec![usize::default(); n_out]; let mut start_idx: usize = 0; + for i in 0..n_out / 4 { // Decided to use multiplication instead of adding to the accumulator (end) // as multiplication seems to be less prone to rounding errors. let end: f64 = block_size * (i + 1) as f64; let end_idx: usize = end as usize + 1; - let (min_index, max_index) = f_argminmax(&arr[start_idx..end_idx]); + let (min_index, max_index) = arr.slice(start_idx, end_idx).argminmax(); // Add the indexes in sorted order sampled_indices[4 * i] = start_idx; @@ -189,11 +234,10 @@ pub(crate) fn m4_generic_parallel( // --------------------- WITH X #[inline(always)] -pub(crate) fn m4_generic_with_x( - arr: &[T], +pub(crate) fn m4_generic_with_x( + arr: &T, bin_idx_iterator: impl Iterator>, n_out: usize, - f_argminmax: fn(&[T]) -> (usize, usize), ) -> Vec { // Assumes n_out is a multiple of 4 if n_out >= arr.len() { @@ -211,8 +255,8 @@ pub(crate) fn m4_generic_with_x( } } else { // If the bin has > 4 elements, add the first and last + argmin and argmax - let step = &arr[start..end]; - let (min_index, max_index) = f_argminmax(step); + let step = arr.slice(start, end); + let (min_index, max_index) = step.argminmax(); sampled_indices.push(start); diff --git a/downsample_rs/src/searchsorted.rs b/downsample_rs/src/searchsorted.rs index c02a3e1..4c544e2 100644 --- a/downsample_rs/src/searchsorted.rs +++ b/downsample_rs/src/searchsorted.rs @@ -1,12 +1,26 @@ use rayon::iter::IndexedParallelIterator; use rayon::prelude::*; +use crate::types::{BinSearchParam, Indexable}; + use super::types::Num; use super::POOL; use num_traits::{AsPrimitive, FromPrimitive}; // ---------------------- Binary search ---------------------- +impl Indexable for Vec { + fn get(&self, index: usize) -> T { + self[index].clone() + } +} + +impl Indexable for [T] { + fn get(&self, index: usize) -> T { + self[index].clone() + } +} + /// Binary search for the index position of the given value in the given array. /// The array must be sorted in ascending order and contain no duplicates. /// @@ -14,21 +28,26 @@ use num_traits::{AsPrimitive, FromPrimitive}; /// https://docs.python.org/3/library/bisect.html#bisect.bisect /// // #[inline(always)] -fn binary_search(arr: &[T], value: T, left: usize, right: usize) -> usize { + +fn binary_search + ?Sized>( + arr: &T, + value: I, + left: usize, + right: usize, +) -> usize { let mut size: usize = right - left; let mut left: usize = left; let mut right: usize = right; - // Return the index where the value is >= arr[index] and arr[index-1] < value while left < right { let mid = left + size / 2; - if arr[mid] < value { + if arr.get(mid) < value { left = mid + 1; } else { right = mid; } size = right - left; } - if arr[left] <= value { + if arr.get(left) <= value { left + 1 } else { left @@ -44,9 +63,9 @@ fn binary_search(arr: &[T], value: T, left: usize, right: /// https://docs.python.org/3/library/bisect.html#bisect.bisect /// // #[inline(always)] -fn binary_search_with_mid( - arr: &[T], - value: T, +fn binary_search_with_mid + ?Sized>( + arr: &T, + value: I, left: usize, right: usize, mid: usize, @@ -57,7 +76,7 @@ fn binary_search_with_mid( let mut mid: usize = mid; // Return the index where the value is <= arr[index] and arr[index+1] < value while left < right { - if arr[mid] < value { + if arr.get(mid) < value { left = mid + 1; } else { right = mid; @@ -65,7 +84,7 @@ fn binary_search_with_mid( let size = right - left; mid = left + size / 2; } - if arr[left] <= value { + if arr.get(left) <= value { left + 1 } else { left @@ -76,23 +95,52 @@ fn binary_search_with_mid( // --- Sequential version -pub(crate) fn get_equidistant_bin_idx_iterator( - arr: &[T], +pub trait EqBinIdxIteratorParam { + fn get(&self, index: usize) -> T; + + fn len(&self) -> usize; +} + +impl EqBinIdxIteratorParam for Vec { + fn get(&self, index: usize) -> T { + self[index].clone() + } + + fn len(&self) -> usize { + self.len() + } +} + +impl EqBinIdxIteratorParam for [T] { + fn get(&self, index: usize) -> T { + self[index].clone() + } + + fn len(&self) -> usize { + self.len() + } +} + +pub(crate) fn get_equidistant_bin_idx_iterator< + I, + T: EqBinIdxIteratorParam + BinSearchParam + ?Sized, +>( + arr: &T, nb_bins: usize, ) -> impl Iterator> + '_ where - T: Num + FromPrimitive + AsPrimitive, + I: Num + FromPrimitive + AsPrimitive, { assert!(nb_bins >= 2); // 1. Compute the step between each bin // Divide by nb_bins to avoid overflow! - let val_step: f64 = - (arr[arr.len() - 1].as_() / nb_bins as f64) - (arr[0].as_() / nb_bins as f64); + let val_step: f64 = (EqBinIdxIteratorParam::get(arr, arr.len() - 1).as_() / nb_bins as f64) + - (EqBinIdxIteratorParam::get(arr, 0).as_() / nb_bins as f64); // Estimate the step between each index (used to pre-guess the mid index) let idx_step: usize = arr.len() / nb_bins; // 2. The moving index & value - let arr0: f64 = arr[0].as_(); // The first value of the array + let arr0: f64 = EqBinIdxIteratorParam::get(arr, 0).as_(); // The first value of the array let mut idx: usize = 0; // Index of the search value // 3. Iterate over the bins @@ -100,8 +148,8 @@ where let start_idx: usize = idx; // Start index of the bin (previous end index) // Update the search value - let search_value: T = T::from_f64(arr0 + val_step * (i + 1) as f64).unwrap(); - if arr[start_idx] >= search_value { + let search_value: I = I::from_f64(arr0 + val_step * (i + 1) as f64).unwrap(); + if EqBinIdxIteratorParam::get(arr, start_idx) >= search_value { // If the first value of the bin is already >= the search value, // then the bin is empty. return None; @@ -132,6 +180,7 @@ pub(crate) fn get_equidistant_bin_idx_iterator_parallel( ) -> impl IndexedParallelIterator> + '_> + '_ where T: Num + FromPrimitive + AsPrimitive + Sync + Send, + [T]: BinSearchParam, { assert!(nb_bins >= 2); // 1. Compute the step between each bin @@ -202,6 +251,7 @@ mod tests { fn test_search_sorted_identicial_to_np_linspace_searchsorted() { // Create a 0..9999 array let arr: [u32; 10_000] = core::array::from_fn(|i| i.as_()); + let arr = Vec::from(arr); assert!(arr.len() == 10_000); let iterator = get_equidistant_bin_idx_iterator(&arr, 4); // Check the iterator @@ -216,7 +266,8 @@ mod tests { #[test] fn test_binary_search() { - let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + // https://rust-lang.github.io/rfcs/2000-const-generics.html + let arr = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; assert_eq!(binary_search(&arr, 0, 0, arr.len() - 1), 0); assert_eq!(binary_search(&arr, 1, 0, arr.len() - 1), 1); assert_eq!(binary_search(&arr, 2, 0, arr.len() - 1), 2); @@ -233,7 +284,8 @@ mod tests { #[test] fn test_binary_search_with_mid() { - let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + // https://rust-lang.github.io/rfcs/2000-const-generics.html + let arr = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; assert_eq!(binary_search_with_mid(&arr, 0, 0, arr.len() - 1, 0), 0); assert_eq!(binary_search_with_mid(&arr, 1, 0, arr.len() - 1, 0), 1); assert_eq!(binary_search_with_mid(&arr, 2, 0, arr.len() - 1, 1), 2); @@ -254,6 +306,7 @@ mod tests { let expected_indices = vec![0, 4, 7]; let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]; + let arr = Vec::from(arr); let bin_idxs_iter = get_equidistant_bin_idx_iterator(&arr, 3); let bin_idxs = bin_idxs_iter.map(|x| x.unwrap().0).collect::>(); assert_eq!(bin_idxs, expected_indices); @@ -276,7 +329,7 @@ mod tests { arr.sort_by(|a, b| a.partial_cmp(b).unwrap()); // Calculate the bin indexes - let bin_idxs_iter = get_equidistant_bin_idx_iterator(&arr[..], nb_bins); + let bin_idxs_iter = get_equidistant_bin_idx_iterator(&arr, nb_bins); let bin_idxs = bin_idxs_iter.map(|x| x.unwrap().0).collect::>(); // Calculate the bin indexes in parallel diff --git a/downsample_rs/src/types.rs b/downsample_rs/src/types.rs index 6d50ddf..f026d2d 100644 --- a/downsample_rs/src/types.rs +++ b/downsample_rs/src/types.rs @@ -1,5 +1,9 @@ use std::ops::{Add, Div, Mul, Sub}; +use argminmax::ArgMinMax; + +use crate::helpers::Average; + pub trait Num: Copy + PartialOrd @@ -15,3 +19,42 @@ impl Num for T where T: Copy + PartialOrd + Add + Sub + Mul + Div { } + +pub trait Indexable { + fn get(&self, index: usize) -> T; +} + +pub trait HasLength { + fn len(&self) -> usize; +} + +pub trait Sliceable { + type SliceType; + + fn slice(&self, start_idx: usize, end_idx: usize) -> Self::SliceType; +} + +pub trait IntoIter { + type IterType: Iterator; + + fn into_iter(&self) -> Self::IterType; +} + +pub trait M4Param: HasLength + ArgMinMax + Sliceable {} + +impl M4Param for T where T: HasLength + ArgMinMax + Sliceable {} + +pub trait LttbParam: HasLength + Indexable + IntoIter + Sliceable + Average {} + +impl> LttbParam for T where + T: HasLength + Indexable + IntoIter + Sliceable + Average +{ +} + +pub trait BinSearchParam: Indexable {} + +impl BinSearchParam for T where T: Indexable {} + +pub trait EqBinIdxIteratorParam: Indexable + HasLength {} + +impl EqBinIdxIteratorParam for T where T: Indexable + HasLength {}