use crate::core::error::PlottingError;
use crate::data::traits::Data1D;
pub fn collect_finite_values<T, D>(data: &D) -> Result<Vec<f64>, PlottingError>
where
T: Into<f64> + Copy,
D: Data1D<T>,
{
if data.len() == 0 {
return Err(PlottingError::EmptyDataSet);
}
let values: Vec<f64> = match data.as_slice() {
Some(slice) => slice
.iter()
.map(|v| (*v).into())
.filter(|v: &f64| v.is_finite())
.collect(),
None => (0..data.len())
.filter_map(|i| data.get(i))
.map(|v| (*v).into())
.filter(|v: &f64| v.is_finite())
.collect(),
};
if values.is_empty() {
return Err(PlottingError::InvalidData {
message: "No finite values in data".to_string(),
position: None,
});
}
Ok(values)
}
pub fn collect_finite_values_sorted<T, D>(data: &D) -> Result<Vec<f64>, PlottingError>
where
T: Into<f64> + Copy,
D: Data1D<T>,
{
let mut values = collect_finite_values(data)?;
values.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
Ok(values)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_collect_finite_values_basic() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0];
let result = collect_finite_values(&data).unwrap();
assert_eq!(result, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn test_collect_finite_values_filters_nan() {
let data = vec![1.0, f64::NAN, 3.0, f64::NAN, 5.0];
let result = collect_finite_values(&data).unwrap();
assert_eq!(result, vec![1.0, 3.0, 5.0]);
}
#[test]
fn test_collect_finite_values_filters_infinity() {
let data = vec![1.0, f64::INFINITY, 3.0, f64::NEG_INFINITY, 5.0];
let result = collect_finite_values(&data).unwrap();
assert_eq!(result, vec![1.0, 3.0, 5.0]);
}
#[test]
fn test_collect_finite_values_empty_data() {
let data: Vec<f64> = vec![];
let result = collect_finite_values(&data);
assert!(matches!(result, Err(PlottingError::EmptyDataSet)));
}
#[test]
fn test_collect_finite_values_all_nan() {
let data = vec![f64::NAN, f64::NAN, f64::NAN];
let result = collect_finite_values(&data);
assert!(matches!(result, Err(PlottingError::InvalidData { .. })));
}
#[test]
fn test_collect_finite_values_sorted() {
let data = vec![5.0, 2.0, 8.0, 1.0, 9.0];
let result = collect_finite_values_sorted(&data).unwrap();
assert_eq!(result, vec![1.0, 2.0, 5.0, 8.0, 9.0]);
}
#[test]
fn test_collect_finite_values_with_integers() {
let data: Vec<i32> = vec![1, 2, 3, 4, 5];
let result = collect_finite_values(&data).unwrap();
assert_eq!(result, vec![1.0, 2.0, 3.0, 4.0, 5.0]);
}
#[test]
fn test_both_arms_agree_on_a_payload_full_of_holes() {
struct NoSlice(Vec<f64>);
impl Data1D<f64> for NoSlice {
fn len(&self) -> usize {
self.0.len()
}
fn get(&self, index: usize) -> Option<&f64> {
self.0.get(index)
}
fn iter(&self) -> Box<dyn Iterator<Item = &f64> + '_> {
Box::new(self.0.iter())
}
}
let payload = vec![
1.5,
f64::NAN,
-0.0,
f64::INFINITY,
2.5,
f64::NEG_INFINITY,
0.0,
];
let fast = collect_finite_values(&payload).expect("contiguous arm");
let slow = collect_finite_values(&NoSlice(payload.clone())).expect("index arm");
assert_eq!(
fast.iter().map(|v| v.to_bits()).collect::<Vec<_>>(),
slow.iter().map(|v| v.to_bits()).collect::<Vec<_>>(),
"the two arms must agree bit for bit, including on -0.0"
);
assert_eq!(fast, vec![1.5, -0.0, 2.5, 0.0]);
}
}