use super::quartiles;
use num_traits::{Num, NumCast};
pub fn outliers<T>(data: &[T]) -> Vec<(usize, T)>
where
T: Num + NumCast + Copy + PartialOrd,
{
let mut indexed_data: Vec<(usize, T)> = data.iter().copied().enumerate().collect();
indexed_data.sort_by(|a, b| a.1.partial_cmp(&b.1).unwrap());
let sorted_values: Vec<T> = indexed_data.iter().map(|(_, v)| *v).collect();
let (q1, _, q3) = quartiles(&mut sorted_values.clone());
let iqr = q3 - q1;
let lower_bound = q1 - 1.5 * iqr;
let upper_bound = q3 + 1.5 * iqr;
let mut outlier_info = Vec::new();
for &(original_index, value) in indexed_data.iter() {
let val_f64 = value.to_f64().unwrap();
if val_f64 < lower_bound || val_f64 > upper_bound {
outlier_info.push((original_index, value));
}
}
outlier_info
}
#[cfg(test)]
mod tests {
use super::outliers;
#[test]
#[should_panic(expected = "Input data slice must not be empty")]
fn outliers_panics_on_empty_input() {
let data: Vec<i32> = vec![];
outliers(&data);
}
#[test]
fn outliers_with_one_outlier() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 100];
let outlier_info = outliers(&data);
assert_eq!(outlier_info, vec![(9, 100)]);
}
#[test]
fn outliers_with_no_outliers() {
let data = vec![1, 2, 3, 4, 5];
let outlier_info = outliers(&data);
assert_eq!(outlier_info.len(), 0);
}
#[test]
fn outliers_with_multiple_outliers() {
let data = vec![-100, 1, 2, 3, 4, 5, 6, 7, 8, 9, 100, 200];
let outlier_info = outliers(&data);
assert_eq!(outlier_info.len(), 3);
assert!(outlier_info.contains(&(0, -100)));
assert!(outlier_info.contains(&(10, 100)));
assert!(outlier_info.contains(&(11, 200)));
}
#[test]
fn outliers_with_uniform_data() {
let data = vec![5, 5, 5, 5, 5];
let outlier_info = outliers(&data);
assert_eq!(outlier_info.len(), 0);
}
#[test]
fn outliers_with_floats() {
let data = vec![1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 50.0];
let outlier_info = outliers(&data);
assert_eq!(outlier_info, vec![(9, 50.0)]);
}
#[test]
fn outliers_with_negative_values() {
let data = vec![-100, 1, 2, 3, 4, 5, 6, 7, 8, 9];
let outlier_info = outliers(&data);
assert_eq!(outlier_info, vec![(0, -100)]);
}
#[test]
fn outliers_boundary_case() {
let data = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
let outlier_info = outliers(&data);
assert_eq!(outlier_info.len(), 0); }
#[test]
fn outliers_single_element() {
let data = vec![42];
let outlier_info = outliers(&data);
assert_eq!(outlier_info.len(), 0);
}
#[test]
fn outliers_two_elements() {
let data = vec![1, 2];
let outlier_info = outliers(&data);
assert_eq!(outlier_info.len(), 0);
}
#[test]
fn outliers_does_not_modify_input() {
let data = vec![9, 1, 5, 3, 7];
let _ = outliers(&data);
assert_eq!(data, vec![9, 1, 5, 3, 7]); }
#[test]
fn outliers_with_duplicates() {
let data = vec![1, 2, 2, 3, 3, 3, 4, 4, 5, 100];
let outlier_info = outliers(&data);
assert_eq!(outlier_info, vec![(9, 100)]);
}
#[test]
fn outliers_realistic_example() {
let scores = vec![65, 70, 72, 75, 78, 80, 82, 85, 88, 90, 15]; let outlier_info = outliers(&scores);
assert_eq!(outlier_info, vec![(10, 15)]);
}
#[test]
fn outliers_indices_are_from_original_array() {
let data = vec![100, 2, 3, 4, 5, 6, 7, 8, 9, 1];
let outlier_info = outliers(&data);
assert_eq!(outlier_info, vec![(0, 100)]);
assert_eq!(data, vec![100, 2, 3, 4, 5, 6, 7, 8, 9, 1]);
}
#[test]
fn outliers_multiple_with_original_indices() {
let data = vec![5, -100, 3, 200, 4, 6, 7];
let outlier_info = outliers(&data);
assert_eq!(outlier_info.len(), 2);
assert!(outlier_info.contains(&(1, -100)));
assert!(outlier_info.contains(&(3, 200)));
}
}