use crate::cs::error::{Error, Result};
use num_traits::ToPrimitive;
use std::cmp::Ord;
pub fn search<T>(data: &[T], target: &T) -> Result<Option<usize>>
where
T: Ord + ToPrimitive,
{
if data.is_empty() {
return Ok(None);
}
if !is_sorted(data) {
return Err(Error::invalid_input(
"Interpolation search requires sorted input",
));
}
let mut low = 0;
let mut high = data.len() - 1;
while low <= high && target >= &data[low] && target <= &data[high] {
let target_f = to_f64(target)?;
let low_val_f = to_f64(&data[low])?;
let high_val_f = to_f64(&data[high])?;
if high_val_f == low_val_f {
if &data[low] == target {
return Ok(Some(low));
}
return Ok(None);
}
let pos_f =
low as f64 + ((high - low) as f64 * (target_f - low_val_f) / (high_val_f - low_val_f));
let pos = pos_f.round() as usize;
if pos > high {
break;
}
match target.cmp(&data[pos]) {
std::cmp::Ordering::Equal => return Ok(Some(pos)),
std::cmp::Ordering::Less => {
if pos == 0 {
break;
}
high = pos - 1;
}
std::cmp::Ordering::Greater => low = pos + 1,
}
}
Ok(None)
}
fn is_sorted<T: Ord>(data: &[T]) -> bool {
data.windows(2).all(|w| w[0] <= w[1])
}
fn to_f64<T: ToPrimitive>(value: &T) -> Result<f64> {
value
.to_f64()
.ok_or_else(|| Error::invalid_input("Failed to convert value to f64 for interpolation"))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_empty_slice() {
let data: Vec<i32> = vec![];
assert!(matches!(search(&data, &5).unwrap(), None));
}
#[test]
fn test_single_element_found() {
let data = vec![5];
assert!(matches!(search(&data, &5).unwrap(), Some(0)));
}
#[test]
fn test_single_element_not_found() {
let data = vec![5];
assert!(matches!(search(&data, &3).unwrap(), None));
}
#[test]
fn test_multiple_elements_found_first() {
let data = vec![1, 2, 3, 4, 5];
assert!(matches!(search(&data, &1).unwrap(), Some(0)));
}
#[test]
fn test_multiple_elements_found_last() {
let data = vec![1, 2, 3, 4, 5];
assert!(matches!(search(&data, &5).unwrap(), Some(4)));
}
#[test]
fn test_multiple_elements_found_middle() {
let data = vec![1, 2, 3, 4, 5];
assert!(matches!(search(&data, &3).unwrap(), Some(2)));
}
#[test]
fn test_multiple_elements_not_found() {
let data = vec![1, 2, 3, 4, 5];
assert!(matches!(search(&data, &6).unwrap(), None));
}
#[test]
fn test_with_duplicates() {
let data = vec![1, 2, 2, 2, 3, 4];
let result = search(&data, &2).unwrap().unwrap();
assert!(result >= 1 && result <= 3);
}
#[test]
fn test_unsorted_input() {
let data = vec![3, 1, 4, 1, 5];
assert!(matches!(search(&data, &4), Err(Error::InvalidInput(_))));
}
#[test]
fn test_uniformly_distributed() {
let data: Vec<i32> = (0..100).collect();
assert!(matches!(search(&data, &50).unwrap(), Some(50)));
assert!(matches!(search(&data, &99).unwrap(), Some(99)));
assert!(matches!(search(&data, &100).unwrap(), None));
}
#[test]
fn test_non_uniform_distribution() {
let data = vec![1, 10, 100, 1000, 10000];
assert!(matches!(search(&data, &100).unwrap(), Some(2)));
assert!(matches!(search(&data, &500).unwrap(), None));
}
#[test]
fn test_large_uniform_dataset() {
let data: Vec<i32> = (0..10_000).collect();
assert!(matches!(search(&data, &5000).unwrap(), Some(5000)));
assert!(matches!(search(&data, &10_000).unwrap(), None));
}
#[test]
fn test_floating_point() {
let data = vec![1, 2, 3, 4, 5];
assert!(matches!(search(&data, &3).unwrap(), Some(2)));
assert!(matches!(search(&data, &6).unwrap(), None));
}
#[test]
fn test_boundary_values() {
let data = vec![i32::MIN, -5, 0, 5, i32::MAX];
assert!(matches!(search(&data, &i32::MIN).unwrap(), Some(0)));
assert!(matches!(search(&data, &i32::MAX).unwrap(), Some(4)));
assert!(matches!(search(&data, &0).unwrap(), Some(2)));
}
#[test]
fn test_equal_values_target_not_found() {
let data = vec![5, 5, 5, 5, 5];
assert!(matches!(search(&data, &3).unwrap(), None));
}
#[test]
fn test_pos_greater_than_high() {
let data = vec![1, 2, 1000000];
assert!(matches!(search(&data, &999999).unwrap(), None));
}
#[test]
fn test_conversion_failure() {
#[derive(Debug, Eq, PartialEq, Ord, PartialOrd)]
struct NonNumeric(i32);
impl ToPrimitive for NonNumeric {
fn to_i64(&self) -> Option<i64> {
None
}
fn to_u64(&self) -> Option<u64> {
None
}
fn to_f64(&self) -> Option<f64> {
None
}
}
let data = vec![NonNumeric(1), NonNumeric(2), NonNumeric(3)];
assert!(matches!(
search(&data, &NonNumeric(2)),
Err(Error::InvalidInput(_))
));
}
#[test]
fn test_interpolation_out_of_bounds() {
let data = vec![1, 2, 1000000];
assert!(matches!(search(&data, &999999).unwrap(), None));
}
#[test]
fn test_target_less_than_first() {
let data = vec![5, 10, 15, 20];
assert!(matches!(search(&data, &1).unwrap(), None));
}
}