Skip to main content

stats/
unsorted.rs

1use num_traits::ToPrimitive;
2use rayon::iter::{IndexedParallelIterator, IntoParallelRefIterator, ParallelIterator};
3use rayon::prelude::ParallelSlice;
4use rayon::slice::ParallelSliceMut;
5
6use serde::{Deserialize, Serialize};
7
8use {crate::Commute, crate::Partial};
9
10const PARALLEL_THRESHOLD: usize = 10_000;
11
12/// Compute the exact median on a stream of data.
13///
14/// (This has time complexity `O(nlogn)` and space complexity `O(n)`.)
15pub fn median<I>(it: I) -> Option<f64>
16where
17    I: Iterator,
18    <I as Iterator>::Item: PartialOrd + ToPrimitive + Send,
19{
20    it.collect::<Unsorted<_>>().median()
21}
22
23/// Compute the median absolute deviation (MAD) on a stream of data.
24pub fn mad<I>(it: I, precalc_median: Option<f64>) -> Option<f64>
25where
26    I: Iterator,
27    <I as Iterator>::Item: PartialOrd + ToPrimitive + Send + Sync,
28{
29    it.collect::<Unsorted<_>>().mad(precalc_median)
30}
31
32/// Compute the exact 1-, 2-, and 3-quartiles (Q1, Q2 a.k.a. median, and Q3) on a stream of data.
33///
34/// (This has time complexity `O(n log n)` and space complexity `O(n)`.)
35pub fn quartiles<I>(it: I) -> Option<(f64, f64, f64)>
36where
37    I: Iterator,
38    <I as Iterator>::Item: PartialOrd + ToPrimitive + Send,
39{
40    it.collect::<Unsorted<_>>().quartiles()
41}
42
43/// Compute the exact mode on a stream of data.
44///
45/// (This has time complexity `O(nlogn)` and space complexity `O(n)`.)
46///
47/// If the data does not have a mode, then `None` is returned.
48pub fn mode<T, I>(it: I) -> Option<T>
49where
50    T: PartialOrd + Clone + Send,
51    I: Iterator<Item = T>,
52{
53    it.collect::<Unsorted<T>>().mode()
54}
55
56/// Compute the modes on a stream of data.
57///
58/// If there is a single mode, then only that value is returned in the `Vec`
59/// however, if there are multiple values tied for occurring the most amount of times
60/// those values are returned.
61///
62/// ## Example
63/// ```
64/// use stats;
65///
66/// let vals = vec![1, 1, 2, 2, 3];
67///
68/// assert_eq!(stats::modes(vals.into_iter()), (vec![1, 2], 2, 2));
69/// ```
70/// This has time complexity `O(n)`
71///
72/// If the data does not have a mode, then an empty `Vec` is returned.
73pub fn modes<T, I>(it: I) -> (Vec<T>, usize, u32)
74where
75    T: PartialOrd + Clone + Send,
76    I: Iterator<Item = T>,
77{
78    it.collect::<Unsorted<T>>().modes()
79}
80
81/// Compute the antimodes on a stream of data.
82///
83/// Antimode is the least frequent non-zero score.
84///
85/// If there is a single antimode, then only that value is returned in the `Vec`
86/// however, if there are multiple values tied for occurring the least amount of times
87/// those values are returned.
88///
89/// Only the first 10 antimodes are returned to prevent returning the whole set
90/// when cardinality = number of records (i.e. all unique values).
91///
92/// ## Example
93/// ```
94/// use stats;
95///
96/// let vals = vec![1, 1, 2, 2, 3];
97///
98/// assert_eq!(stats::antimodes(vals.into_iter()), (vec![3], 1, 1));
99/// ```
100/// This has time complexity `O(n)`
101///
102/// If the data does not have an antimode, then an empty `Vec` is returned.
103pub fn antimodes<T, I>(it: I) -> (Vec<T>, usize, u32)
104where
105    T: PartialOrd + Clone + Send,
106    I: Iterator<Item = T>,
107{
108    let (antimodes_result, antimodes_count, antimodes_occurrences) =
109        it.collect::<Unsorted<T>>().antimodes();
110    (antimodes_result, antimodes_count, antimodes_occurrences)
111}
112
113/// Compute the Gini Coefficient on a stream of data.
114///
115/// The Gini Coefficient measures inequality in a distribution, ranging from 0 (perfect equality)
116/// to 1 (perfect inequality).
117///
118/// (This has time complexity `O(n log n)` and space complexity `O(n)`.)
119pub fn gini<I>(it: I, precalc_sum: Option<f64>) -> Option<f64>
120where
121    I: Iterator,
122    <I as Iterator>::Item: PartialOrd + ToPrimitive + Send + Sync,
123{
124    it.collect::<Unsorted<_>>().gini(precalc_sum)
125}
126
127/// Compute the kurtosis (excess kurtosis) on a stream of data.
128///
129/// Kurtosis measures the "tailedness" of a distribution. Excess kurtosis is kurtosis - 3,
130/// where 0 indicates a normal distribution, positive values indicate heavy tails, and
131/// negative values indicate light tails.
132///
133/// (This has time complexity `O(n log n)` and space complexity `O(n)`.)
134pub fn kurtosis<I>(it: I, precalc_mean: Option<f64>, precalc_variance: Option<f64>) -> Option<f64>
135where
136    I: Iterator,
137    <I as Iterator>::Item: PartialOrd + ToPrimitive + Send + Sync,
138{
139    it.collect::<Unsorted<_>>()
140        .kurtosis(precalc_mean, precalc_variance)
141}
142
143/// Compute the percentile rank of a value on a stream of data.
144///
145/// Returns the percentile rank (0-100) of the given value in the distribution.
146/// If the value is less than all values, returns 0.0. If greater than all, returns 100.0.
147///
148/// (This has time complexity `O(n log n)` and space complexity `O(n)`.)
149pub fn percentile_rank<I, V>(it: I, value: V) -> Option<f64>
150where
151    I: Iterator,
152    <I as Iterator>::Item: PartialOrd + ToPrimitive + Send + Sync,
153    V: PartialOrd + ToPrimitive,
154{
155    it.collect::<Unsorted<_>>().percentile_rank(value)
156}
157
158/// Compute the Atkinson Index on a stream of data.
159///
160/// The Atkinson Index measures inequality with an inequality aversion parameter ε.
161/// It ranges from 0 (perfect equality) to 1 (perfect inequality).
162/// Higher ε values give more weight to inequality at the lower end of the distribution.
163///
164/// (This has time complexity `O(n log n)` and space complexity `O(n)`.)
165pub fn atkinson<I>(
166    it: I,
167    epsilon: f64,
168    precalc_mean: Option<f64>,
169    precalc_geometric_sum: Option<f64>,
170) -> Option<f64>
171where
172    I: Iterator,
173    <I as Iterator>::Item: PartialOrd + ToPrimitive + Send + Sync,
174{
175    it.collect::<Unsorted<_>>()
176        .atkinson(epsilon, precalc_mean, precalc_geometric_sum)
177}
178
179fn median_on_sorted<T>(data: &[T]) -> Option<f64>
180where
181    T: PartialOrd + ToPrimitive,
182{
183    Some(match data.len() {
184        // Empty slice case - return None early
185        0 => return None,
186        // Single element case - return that element converted to f64
187        1 => data.first()?.to_f64()?,
188        // Even length case - average the two middle elements
189        len if len.is_multiple_of(2) => {
190            let idx = len / 2;
191            // Safety: we know these indices are valid because we checked len is even and non-zero,
192            // so idx-1 and idx are valid indices into data
193            let v1 = unsafe { data.get_unchecked(idx - 1) }.to_f64()?;
194            let v2 = unsafe { data.get_unchecked(idx) }.to_f64()?;
195            f64::midpoint(v1, v2)
196        }
197        // Odd length case - return the middle element
198        // Safety: we know the index is within bounds
199        len => unsafe { data.get_unchecked(len / 2) }.to_f64()?,
200    })
201}
202
203fn mad_on_sorted<T>(data: &[T], precalc_median: Option<f64>) -> Option<f64>
204where
205    T: Sync + PartialOrd + ToPrimitive,
206{
207    if data.is_empty() {
208        return None;
209    }
210    let median_obs = precalc_median.unwrap_or_else(|| median_on_sorted(data).unwrap());
211
212    // Use adaptive parallel processing based on data size
213    let mut abs_diff_vec: Vec<f64> = if data.len() < PARALLEL_THRESHOLD {
214        // Sequential processing for small datasets
215        // Iterator collect enables TrustedLen optimization, eliminating per-element bounds checks
216        data.iter()
217            // SAFETY: to_f64() always returns Some for standard numeric types (f32/f64, i/u 8-64)
218            .map(|x| (median_obs - unsafe { x.to_f64().unwrap_unchecked() }).abs())
219            .collect()
220    } else {
221        // Parallel processing for large datasets
222        data.par_iter()
223            // SAFETY: to_f64() always returns Some for standard numeric types
224            .map(|x| (median_obs - unsafe { x.to_f64().unwrap_unchecked() }).abs())
225            .collect()
226    };
227
228    // Use select_nth_unstable to find the median in O(n) instead of O(n log n) full sort
229    let len = abs_diff_vec.len();
230    let mid = len / 2;
231    let cmp = |a: &f64, b: &f64| a.total_cmp(b);
232
233    if len.is_multiple_of(2) {
234        // Even length: need both mid-1 and mid elements
235        abs_diff_vec.select_nth_unstable_by(mid, cmp);
236        let right = abs_diff_vec[mid];
237        // The left partition [0..mid] contains elements <= abs_diff_vec[mid],
238        // so we can find the max of the left partition for mid-1
239        let left = abs_diff_vec[..mid]
240            .iter()
241            .max_by(|a, b| cmp(a, b))
242            .copied()?;
243        Some(f64::midpoint(left, right))
244    } else {
245        abs_diff_vec.select_nth_unstable_by(mid, cmp);
246        Some(abs_diff_vec[mid])
247    }
248}
249
250fn gini_on_sorted<T>(data: &[Partial<T>], precalc_sum: Option<f64>) -> Option<f64>
251where
252    T: Sync + PartialOrd + ToPrimitive,
253{
254    let len = data.len();
255
256    // Early return for empty data
257    if len == 0 {
258        return None;
259    }
260
261    // Single element case: perfect equality, Gini = 0
262    if len == 1 {
263        return Some(0.0);
264    }
265
266    // Gini coefficient is only defined for non-negative distributions.
267    // Since data is sorted, check the first (smallest) element.
268    // SAFETY: len > 1 guaranteed by checks above
269    let first_val = unsafe { data.get_unchecked(0).0.to_f64().unwrap_unchecked() };
270    if first_val < 0.0 {
271        return None;
272    }
273
274    // Use pre-calculated sum if provided, otherwise compute it
275    let sum = if let Some(precalc) = precalc_sum {
276        if precalc < 0.0 {
277            return None;
278        }
279        precalc
280    } else if len < PARALLEL_THRESHOLD {
281        // Iterator sum enables auto-vectorization (SIMD) by the compiler
282        data.iter()
283            // SAFETY: to_f64() always returns Some for standard numeric types (f32/f64, i/u 8-64)
284            .map(|x| unsafe { x.0.to_f64().unwrap_unchecked() })
285            .sum()
286    } else {
287        data.par_iter()
288            // SAFETY: to_f64() always returns Some for standard numeric types
289            .map(|x| unsafe { x.0.to_f64().unwrap_unchecked() })
290            .sum()
291    };
292
293    // Compute weighted sum
294    let weighted_sum = if len < PARALLEL_THRESHOLD {
295        let mut weighted_sum = 0.0;
296        for (i, x) in data.iter().enumerate() {
297            // SAFETY: to_f64() always returns Some for standard numeric types
298            let val = unsafe { x.0.to_f64().unwrap_unchecked() };
299            weighted_sum = ((i + 1) as f64).mul_add(val, weighted_sum);
300        }
301        weighted_sum
302    } else {
303        // Compute weighted sum in parallel using enumerate to get indices
304        data.par_iter()
305            .enumerate()
306            .map(|(i, x)| {
307                // SAFETY: to_f64() always returns Some for standard numeric types
308                let val = unsafe { x.0.to_f64().unwrap_unchecked() };
309                ((i + 1) as f64).mul_add(val, 0.0)
310            })
311            .sum()
312    };
313
314    // If sum is zero, Gini is undefined
315    if sum == 0.0 {
316        return None;
317    }
318
319    // Compute Gini coefficient using the formula:
320    // G = (2 * Σ(i * y_i)) / (n * Σ(y_i)) - (n + 1) / n
321    // where i is 1-indexed rank and y_i are sorted values
322    let n = len as f64;
323    let gini = 2.0f64.mul_add(weighted_sum / (n * sum), -(n + 1.0) / n);
324
325    Some(gini)
326}
327
328fn kurtosis_on_sorted<T>(
329    data: &[Partial<T>],
330    precalc_mean: Option<f64>,
331    precalc_variance: Option<f64>,
332) -> Option<f64>
333where
334    T: Sync + PartialOrd + ToPrimitive,
335{
336    let len = data.len();
337
338    // Need at least 4 elements for meaningful kurtosis
339    if len < 4 {
340        return None;
341    }
342
343    // Use pre-calculated mean if provided, otherwise compute it
344    let mean = if let Some(precalc) = precalc_mean {
345        precalc
346    } else {
347        let sum: f64 = if len < PARALLEL_THRESHOLD {
348            // Iterator sum enables auto-vectorization (SIMD) by the compiler
349            data.iter()
350                // SAFETY: to_f64() always returns Some for standard numeric types (f32/f64, i/u 8-64)
351                .map(|x| unsafe { x.0.to_f64().unwrap_unchecked() })
352                .sum()
353        } else {
354            data.par_iter()
355                // SAFETY: to_f64() always returns Some for standard numeric types
356                .map(|x| unsafe { x.0.to_f64().unwrap_unchecked() })
357                .sum()
358        };
359        sum / len as f64
360    };
361
362    // Compute variance_sq and fourth_power_sum
363    // If variance is provided, we can compute variance_sq directly (variance_sq = variance^2)
364    // Otherwise, we need to compute variance from the data
365    let (variance_sq, fourth_power_sum) = if let Some(variance) = precalc_variance {
366        // Negative variance is invalid (possible floating-point rounding artifact)
367        if variance < 0.0 {
368            return None;
369        }
370        // Use pre-calculated variance: variance_sq = variance^2
371        let variance_sq = variance * variance;
372
373        // Still need to compute fourth_power_sum
374        let fourth_power_sum = if len < PARALLEL_THRESHOLD {
375            let mut sum = 0.0;
376            for x in data {
377                // SAFETY: to_f64() always returns Some for standard numeric types
378                let val = unsafe { x.0.to_f64().unwrap_unchecked() };
379                let diff = val - mean;
380                let diff_sq = diff * diff;
381                sum = diff_sq.mul_add(diff_sq, sum);
382            }
383            sum
384        } else {
385            data.par_iter()
386                .map(|x| {
387                    // SAFETY: to_f64() always returns Some for standard numeric types
388                    let val = unsafe { x.0.to_f64().unwrap_unchecked() };
389                    let diff = val - mean;
390                    let diff_sq = diff * diff;
391                    diff_sq * diff_sq
392                })
393                .sum()
394        };
395
396        (variance_sq, fourth_power_sum)
397    } else {
398        // Compute both variance_sum and fourth_power_sum
399        let (variance_sum, fourth_power_sum) = if len < PARALLEL_THRESHOLD {
400            let mut variance_sum = 0.0;
401            let mut fourth_power_sum = 0.0;
402
403            for x in data {
404                // SAFETY: to_f64() always returns Some for standard numeric types
405                let val = unsafe { x.0.to_f64().unwrap_unchecked() };
406                let diff = val - mean;
407                let diff_sq = diff * diff;
408                variance_sum += diff_sq;
409                fourth_power_sum = diff_sq.mul_add(diff_sq, fourth_power_sum);
410            }
411
412            (variance_sum, fourth_power_sum)
413        } else {
414            // Single pass computing both sums simultaneously
415            data.par_iter()
416                .fold(
417                    || (0.0_f64, 0.0_f64),
418                    |acc, x| {
419                        // SAFETY: to_f64() always returns Some for standard numeric types
420                        let val = unsafe { x.0.to_f64().unwrap_unchecked() };
421                        let diff = val - mean;
422                        let diff_sq = diff * diff;
423                        (acc.0 + diff_sq, diff_sq.mul_add(diff_sq, acc.1))
424                    },
425                )
426                .reduce(|| (0.0, 0.0), |a, b| (a.0 + b.0, a.1 + b.1))
427        };
428
429        let variance = variance_sum / len as f64;
430
431        // If variance is zero, all values are the same, kurtosis is undefined
432        if variance == 0.0 {
433            return None;
434        }
435
436        let variance_sq = variance * variance;
437        (variance_sq, fourth_power_sum)
438    };
439
440    // If variance_sq is zero, all values are the same, kurtosis is undefined
441    if variance_sq == 0.0 {
442        return None;
443    }
444
445    let n = len as f64;
446
447    // Sample excess kurtosis formula:
448    // kurtosis = (n(n+1) * Σ((x_i - mean)⁴)) / ((n-1)(n-2)(n-3) * variance²) - 3(n-1)²/((n-2)(n-3))
449    let denominator = (n - 1.0) * (n - 2.0) * (n - 3.0);
450    let adjustment = 3.0 * (n - 1.0) * (n - 1.0) / denominator;
451    let kurtosis = (n * (n + 1.0) * fourth_power_sum).mul_add(1.0 / (denominator * variance_sq), -adjustment);
452
453    Some(kurtosis)
454}
455
456fn percentile_rank_on_sorted<T, V>(data: &[Partial<T>], value: &V) -> Option<f64>
457where
458    T: PartialOrd + ToPrimitive,
459    V: PartialOrd + ToPrimitive,
460{
461    let len = data.len();
462
463    if len == 0 {
464        return None;
465    }
466
467    let value_f64 = value.to_f64()?;
468
469    // Binary search to find the position where value would be inserted
470    // This gives us the number of values <= value
471    let count_leq = data.binary_search_by(|x| {
472        x.0.to_f64()
473            .unwrap_or(f64::NAN)
474            .partial_cmp(&value_f64)
475            .unwrap_or(std::cmp::Ordering::Less)
476    });
477
478    let count = match count_leq {
479        Ok(idx) => {
480            // Value found at idx, but we need to count all equal values
481            let mut count = idx + 1;
482            // Count all equal values after idx
483            for x in data.iter().skip(idx + 1) {
484                if let Some(x_val) = x.0.to_f64() {
485                    if x_val.total_cmp(&value_f64).is_eq() {
486                        count += 1;
487                    } else {
488                        break;
489                    }
490                } else {
491                    break;
492                }
493            }
494            count
495        }
496        Err(idx) => idx, // Number of values less than value
497    };
498
499    // Percentile rank = (count / n) * 100
500    Some((count as f64 / len as f64) * 100.0)
501}
502
503fn atkinson_on_sorted<T>(
504    data: &[Partial<T>],
505    epsilon: f64,
506    precalc_mean: Option<f64>,
507    precalc_geometric_sum: Option<f64>,
508) -> Option<f64>
509where
510    T: Sync + PartialOrd + ToPrimitive,
511{
512    let len = data.len();
513
514    // Early return for empty data
515    if len == 0 {
516        return None;
517    }
518
519    // Single element case: perfect equality, Atkinson = 0
520    if len == 1 {
521        return Some(0.0);
522    }
523
524    // Epsilon must be non-negative
525    if epsilon < 0.0 {
526        return None;
527    }
528
529    // Use pre-calculated mean if provided, otherwise compute it
530    let mean = if let Some(precalc) = precalc_mean {
531        precalc
532    } else {
533        let sum: f64 = if len < PARALLEL_THRESHOLD {
534            // Iterator sum enables auto-vectorization (SIMD) by the compiler
535            data.iter()
536                // SAFETY: to_f64() always returns Some for standard numeric types (f32/f64, i/u 8-64)
537                .map(|x| unsafe { x.0.to_f64().unwrap_unchecked() })
538                .sum()
539        } else {
540            data.par_iter()
541                // SAFETY: to_f64() always returns Some for standard numeric types
542                .map(|x| unsafe { x.0.to_f64().unwrap_unchecked() })
543                .sum()
544        };
545        sum / len as f64
546    };
547
548    // If mean is zero, Atkinson is undefined
549    if mean == 0.0 {
550        return None;
551    }
552
553    // Handle special case: epsilon = 1 (uses geometric mean)
554    if (epsilon - 1.0).abs() < 1e-10 {
555        // A_1 = 1 - (geometric_mean / mean)
556        let geometric_sum: f64 = if let Some(precalc) = precalc_geometric_sum {
557            precalc
558        } else if len < PARALLEL_THRESHOLD {
559            let mut sum = 0.0;
560            for x in data {
561                // SAFETY: to_f64() always returns Some for standard numeric types
562                let val = unsafe { x.0.to_f64().unwrap_unchecked() };
563                if val <= 0.0 {
564                    // Geometric mean undefined for non-positive values
565                    return None;
566                }
567                sum += val.ln();
568            }
569            sum
570        } else {
571            data.par_iter()
572                .map(|x| {
573                    // SAFETY: to_f64() always returns Some for standard numeric types
574                    let val = unsafe { x.0.to_f64().unwrap_unchecked() };
575                    if val <= 0.0 {
576                        return f64::NAN;
577                    }
578                    val.ln()
579                })
580                .sum()
581        };
582
583        if geometric_sum.is_nan() {
584            return None;
585        }
586
587        let geometric_mean = (geometric_sum / len as f64).exp();
588        return Some(1.0 - geometric_mean / mean);
589    }
590
591    // General case: epsilon != 1
592    // A_ε = 1 - (1/n * Σ((x_i/mean)^(1-ε)))^(1/(1-ε))
593    let exponent = 1.0 - epsilon;
594
595    let sum_powered: f64 = if len < PARALLEL_THRESHOLD {
596        let mut sum = 0.0;
597        for x in data {
598            // SAFETY: to_f64() always returns Some for standard numeric types
599            let val = unsafe { x.0.to_f64().unwrap_unchecked() };
600            if val < 0.0 {
601                // Negative values with non-integer exponent are undefined
602                return None;
603            }
604            let ratio = val / mean;
605            sum += ratio.powf(exponent);
606        }
607        sum
608    } else {
609        data.par_iter()
610            .map(|x| {
611                // SAFETY: to_f64() always returns Some for standard numeric types
612                let val = unsafe { x.0.to_f64().unwrap_unchecked() };
613                if val < 0.0 {
614                    return f64::NAN;
615                }
616                let ratio = val / mean;
617                ratio.powf(exponent)
618            })
619            .sum()
620    };
621
622    if sum_powered.is_nan() || sum_powered <= 0.0 {
623        return None;
624    }
625
626    let atkinson = 1.0 - (sum_powered / len as f64).powf(1.0 / exponent);
627    Some(atkinson)
628}
629
630/// Selection algorithm to find the k-th smallest element in O(n) average time.
631/// This is an implementation of quickselect algorithm.
632#[cfg(test)]
633fn quickselect<T>(data: &mut [Partial<T>], k: usize) -> Option<&T>
634where
635    T: PartialOrd,
636{
637    if data.is_empty() || k >= data.len() {
638        return None;
639    }
640
641    let mut left = 0;
642    let mut right = data.len() - 1;
643
644    loop {
645        if left == right {
646            return Some(&data[left].0);
647        }
648
649        // Use median-of-three pivot selection for better performance
650        let pivot_idx = median_of_three_pivot(data, left, right);
651        let pivot_idx = partition(data, left, right, pivot_idx);
652
653        match k.cmp(&pivot_idx) {
654            std::cmp::Ordering::Equal => return Some(&data[pivot_idx].0),
655            std::cmp::Ordering::Less => right = pivot_idx - 1,
656            std::cmp::Ordering::Greater => left = pivot_idx + 1,
657        }
658    }
659}
660
661/// Select the median of three elements as pivot for better quickselect performance
662#[cfg(test)]
663fn median_of_three_pivot<T>(data: &[Partial<T>], left: usize, right: usize) -> usize
664where
665    T: PartialOrd,
666{
667    let mid = left + (right - left) / 2;
668
669    if data[left] <= data[mid] {
670        if data[mid] <= data[right] {
671            mid
672        } else if data[left] <= data[right] {
673            right
674        } else {
675            left
676        }
677    } else if data[left] <= data[right] {
678        left
679    } else if data[mid] <= data[right] {
680        right
681    } else {
682        mid
683    }
684}
685
686/// Partition function for quickselect
687#[cfg(test)]
688fn partition<T>(data: &mut [Partial<T>], left: usize, right: usize, pivot_idx: usize) -> usize
689where
690    T: PartialOrd,
691{
692    // Move pivot to end
693    data.swap(pivot_idx, right);
694    let mut store_idx = left;
695
696    // Move all elements smaller than pivot to the left
697    // Cache pivot position for better cache locality (access data[right] directly each time)
698    for i in left..right {
699        // Safety: i, store_idx, and right are guaranteed to be in bounds
700        // Compare directly with pivot at data[right] - compiler should optimize this access
701        if unsafe { data.get_unchecked(i) <= data.get_unchecked(right) } {
702            data.swap(i, store_idx);
703            store_idx += 1;
704        }
705    }
706
707    // Move pivot to its final place
708    data.swap(store_idx, right);
709    store_idx
710}
711
712// This implementation follows Method 3 from https://en.wikipedia.org/wiki/Quartile
713// It divides data into quarters based on the length n = 4k + r where r is remainder.
714// For each remainder case (0,1,2,3), it uses different formulas to compute Q1, Q2, Q3.
715fn quartiles_on_sorted<T>(data: &[Partial<T>]) -> Option<(f64, f64, f64)>
716where
717    T: PartialOrd + ToPrimitive,
718{
719    let len = data.len();
720
721    // Early return for small arrays
722    match len {
723        0..=2 => return None,
724        3 => {
725            return Some(
726                // SAFETY: We know these indices are valid because len == 3
727                unsafe {
728                    (
729                        data.get_unchecked(0).0.to_f64()?,
730                        data.get_unchecked(1).0.to_f64()?,
731                        data.get_unchecked(2).0.to_f64()?,
732                    )
733                },
734            );
735        }
736        _ => {}
737    }
738
739    // Calculate k and remainder in one division
740    let k = len / 4;
741    let remainder = len % 4;
742
743    // SAFETY: All index calculations below are guaranteed to be in bounds
744    // because we've verified len >= 4 above and k is len/4
745    unsafe {
746        Some(match remainder {
747            0 => {
748                // Let data = {x_i}_{i=0..4k} where k is positive integer.
749                // Median q2 = (x_{2k-1} + x_{2k}) / 2.
750                // If we divide data into two parts {x_i < q2} as L and
751                // {x_i > q2} as R, #L == #R == 2k holds true. Thus,
752                // q1 = (x_{k-1} + x_{k}) / 2 and q3 = (x_{3k-1} + x_{3k}) / 2.
753                // =============
754                // Simply put: Length is multiple of 4 (4k)
755                // q1 = (x_{k-1} + x_k) / 2
756                // q2 = (x_{2k-1} + x_{2k}) / 2
757                // q3 = (x_{3k-1} + x_{3k}) / 2
758                let q1 = f64::midpoint(
759                    data.get_unchecked(k - 1).0.to_f64()?,
760                    data.get_unchecked(k).0.to_f64()?,
761                );
762                let q2 = f64::midpoint(
763                    data.get_unchecked(2 * k - 1).0.to_f64()?,
764                    data.get_unchecked(2 * k).0.to_f64()?,
765                );
766                let q3 = f64::midpoint(
767                    data.get_unchecked(3 * k - 1).0.to_f64()?,
768                    data.get_unchecked(3 * k).0.to_f64()?,
769                );
770                (q1, q2, q3)
771            }
772            1 => {
773                // Let data = {x_i}_{i=0..4k+1} where k is positive integer.
774                // Median q2 = x_{2k}.
775                // If we divide data other than q2 into two parts {x_i < q2}
776                // as L and {x_i > q2} as R, #L == #R == 2k holds true. Thus,
777                // q1 = (x_{k-1} + x_{k}) / 2 and q3 = (x_{3k} + x_{3k+1}) / 2.
778                // =============
779                // Simply put: Length is 4k + 1
780                // q1 = (x_{k-1} + x_k) / 2
781                // q2 = x_{2k}
782                // q3 = (x_{3k} + x_{3k+1}) / 2
783                let q1 = f64::midpoint(
784                    data.get_unchecked(k - 1).0.to_f64()?,
785                    data.get_unchecked(k).0.to_f64()?,
786                );
787                let q2 = data.get_unchecked(2 * k).0.to_f64()?;
788                let q3 = f64::midpoint(
789                    data.get_unchecked(3 * k).0.to_f64()?,
790                    data.get_unchecked(3 * k + 1).0.to_f64()?,
791                );
792                (q1, q2, q3)
793            }
794            2 => {
795                // Let data = {x_i}_{i=0..4k+2} where k is positive integer.
796                // Median q2 = (x_{(2k+1)-1} + x_{2k+1}) / 2.
797                // If we divide data into two parts {x_i < q2} as L and
798                // {x_i > q2} as R, it's true that #L == #R == 2k+1.
799                // Thus, q1 = x_{k} and q3 = x_{3k+1}.
800                // =============
801                // Simply put: Length is 4k + 2
802                // q1 = x_k
803                // q2 = (x_{2k} + x_{2k+1}) / 2
804                // q3 = x_{3k+1}
805                let q1 = data.get_unchecked(k).0.to_f64()?;
806                let q2 = f64::midpoint(
807                    data.get_unchecked(2 * k).0.to_f64()?,
808                    data.get_unchecked(2 * k + 1).0.to_f64()?,
809                );
810                let q3 = data.get_unchecked(3 * k + 1).0.to_f64()?;
811                (q1, q2, q3)
812            }
813            _ => {
814                // Let data = {x_i}_{i=0..4k+3} where k is positive integer.
815                // Median q2 = x_{2k+1}.
816                // If we divide data other than q2 into two parts {x_i < q2}
817                // as L and {x_i > q2} as R, #L == #R == 2k+1 holds true.
818                // Thus, q1 = x_{k} and q3 = x_{3k+2}.
819                // =============
820                // Simply put: Length is 4k + 3
821                // q1 = x_k
822                // q2 = x_{2k+1}
823                // q3 = x_{3k+2}
824                let q1 = data.get_unchecked(k).0.to_f64()?;
825                let q2 = data.get_unchecked(2 * k + 1).0.to_f64()?;
826                let q3 = data.get_unchecked(3 * k + 2).0.to_f64()?;
827                (q1, q2, q3)
828            }
829        })
830    }
831}
832
833/// Zero-copy quartiles computation using index-based selection.
834/// This avoids copying data by working with an array of indices.
835///
836/// Uses `select_nth_unstable_by` on the indices array, which partitions in-place.
837/// After selecting position p, elements at indices [0..p] are <= the p-th element
838/// and elements at [p+1..] are >= it. By selecting positions in ascending order,
839/// each subsequent selection only needs to search within the right partition,
840/// avoiding redundant O(n) resets.
841fn quartiles_with_zero_copy_selection<T>(data: &[Partial<T>]) -> Option<(f64, f64, f64)>
842where
843    T: PartialOrd + ToPrimitive,
844{
845    let len = data.len();
846
847    // Early return for small arrays
848    match len {
849        0..=2 => return None,
850        3 => {
851            let mut indices: Vec<usize> = (0..3).collect();
852            let cmp =
853                |a: &usize, b: &usize| data[*a].partial_cmp(&data[*b]).unwrap_or(std::cmp::Ordering::Less);
854            indices.sort_unstable_by(cmp);
855            let min_val = data[indices[0]].0.to_f64()?;
856            let med_val = data[indices[1]].0.to_f64()?;
857            let max_val = data[indices[2]].0.to_f64()?;
858            return Some((min_val, med_val, max_val));
859        }
860        _ => {}
861    }
862
863    let k = len / 4;
864    let remainder = len % 4;
865
866    let mut indices: Vec<usize> = (0..len).collect();
867    let cmp = |a: &usize, b: &usize| {
868        data[*a]
869            .partial_cmp(&data[*b])
870            .unwrap_or(std::cmp::Ordering::Less)
871    };
872
873    // Collect the unique positions we need in ascending order.
874    // By selecting in ascending order, each select_nth_unstable_by partitions
875    // the array so subsequent selections operate on progressively smaller slices.
876    // We deduplicate because adjacent quartile boundaries can overlap for small k.
877    let raw_positions: Vec<usize> = match remainder {
878        0 => vec![k - 1, k, 2 * k - 1, 2 * k, 3 * k - 1, 3 * k],
879        1 => vec![k - 1, k, 2 * k, 3 * k, 3 * k + 1],
880        2 => vec![k, 2 * k, 2 * k + 1, 3 * k + 1],
881        _ => vec![k, 2 * k + 1, 3 * k + 2],
882    };
883
884    let mut unique_positions = raw_positions.clone();
885    unique_positions.dedup();
886
887    // Select each unique position in ascending order, narrowing the search range
888    let mut start = 0;
889    for &pos in &unique_positions {
890        indices[start..].select_nth_unstable_by(pos - start, &cmp);
891        start = pos + 1;
892    }
893
894    // Now read all needed values (including duplicates) from the partitioned indices
895    let values: Vec<f64> = raw_positions
896        .iter()
897        .map(|&pos| data[indices[pos]].0.to_f64())
898        .collect::<Option<Vec<_>>>()?;
899
900    match remainder {
901        0 => {
902            let q1 = f64::midpoint(values[0], values[1]);
903            let q2 = f64::midpoint(values[2], values[3]);
904            let q3 = f64::midpoint(values[4], values[5]);
905            Some((q1, q2, q3))
906        }
907        1 => {
908            let q1 = f64::midpoint(values[0], values[1]);
909            let q2 = values[2];
910            let q3 = f64::midpoint(values[3], values[4]);
911            Some((q1, q2, q3))
912        }
913        2 => {
914            let q1 = values[0];
915            let q2 = f64::midpoint(values[1], values[2]);
916            let q3 = values[3];
917            Some((q1, q2, q3))
918        }
919        _ => Some((values[0], values[1], values[2])),
920    }
921}
922
923fn mode_on_sorted<T, I>(it: I) -> Option<T>
924where
925    T: PartialOrd,
926    I: Iterator<Item = T>,
927{
928    use std::cmp::Ordering;
929
930    // This approach to computing the mode works very nicely when the
931    // number of samples is large and is close to its cardinality.
932    // In other cases, a hashmap would be much better.
933    // But really, how can we know this when given an arbitrary stream?
934    // Might just switch to a hashmap to track frequencies. That would also
935    // be generally useful for discovering the cardinality of a sample.
936    let (mut mode, mut next) = (None, None);
937    let (mut mode_count, mut next_count) = (0usize, 0usize);
938    for x in it {
939        if mode.as_ref() == Some(&x) {
940            mode_count += 1;
941        } else if next.as_ref() == Some(&x) {
942            next_count += 1;
943        } else {
944            next = Some(x);
945            next_count = 0;
946        }
947
948        match next_count.cmp(&mode_count) {
949            Ordering::Greater => {
950                mode = next;
951                mode_count = next_count;
952                next = None;
953                next_count = 0;
954            }
955            Ordering::Equal => {
956                mode = None;
957                mode_count = 0;
958            }
959            Ordering::Less => {}
960        }
961    }
962    mode
963}
964
965/// Computes both modes and antimodes from a sorted slice of values.
966/// This version works with references to avoid unnecessary cloning.
967///
968/// # Arguments
969///
970/// * `data` - A sorted slice of values
971///
972/// # Notes
973///
974/// - Mode is the most frequently occurring value(s)
975/// - Antimode is the least frequently occurring value(s)
976/// - Only returns up to 10 antimodes to avoid returning the full set when all values are unique
977/// - For empty slices, returns empty vectors and zero counts
978/// - For single value slices, returns that value as the mode and empty antimode
979/// - When all values occur exactly once, returns empty mode and up to 10 values as antimodes
980///
981/// # Returns
982///
983/// A tuple containing:
984/// * Modes information: `(Vec<T>, usize, u32)` where:
985///   - Vec<T>: Vector containing the mode values
986///   - usize: Number of modes found
987///   - u32: Frequency/count of the mode values
988/// * Antimodes information: `(Vec<T>, usize, u32)` where:
989///   - Vec<T>: Vector containing up to 10 antimode values
990///   - usize: Total number of antimodes
991///   - u32: Frequency/count of the antimode values
992#[allow(clippy::type_complexity)]
993#[inline]
994fn modes_and_antimodes_on_sorted_slice<T>(
995    data: &[Partial<T>],
996) -> ((Vec<T>, usize, u32), (Vec<T>, usize, u32))
997where
998    T: PartialOrd + Clone,
999{
1000    let size = data.len();
1001
1002    // Early return for empty slice
1003    if size == 0 {
1004        return ((Vec::new(), 0, 0), (Vec::new(), 0, 0));
1005    }
1006
1007    // Estimate capacity using integer square root of size
1008    // Integer square root using Newton's method (faster than floating point sqrt)
1009    let mut x = size;
1010    let mut y = x.div_ceil(2);
1011    while y < x {
1012        x = y;
1013        y = usize::midpoint(x, size / x);
1014    }
1015    let sqrt_size = x;
1016    let mut runs: Vec<(&T, u32)> = Vec::with_capacity(sqrt_size.clamp(16, 1_000));
1017
1018    let mut current_value = &data[0].0;
1019    let mut current_count = 1;
1020    let mut highest_count = 1;
1021    let mut lowest_count = u32::MAX;
1022
1023    // Count consecutive runs - optimized to reduce allocations
1024    for x in data.iter().skip(1) {
1025        if x.0 == *current_value {
1026            current_count += 1;
1027            highest_count = highest_count.max(current_count);
1028        } else {
1029            runs.push((current_value, current_count));
1030            lowest_count = lowest_count.min(current_count);
1031            current_value = &x.0;
1032            current_count = 1;
1033        }
1034    }
1035    runs.push((current_value, current_count));
1036    lowest_count = lowest_count.min(current_count);
1037
1038    // Early return if only one unique value
1039    if runs.len() == 1 {
1040        let (val, count) = runs.pop().unwrap();
1041        return ((vec![val.clone()], 1, count), (Vec::new(), 0, 0));
1042    }
1043
1044    // Special case: if all values appear exactly once
1045    if highest_count == 1 {
1046        let antimodes_count = runs.len().min(10);
1047        let total_count = runs.len();
1048        let mut antimodes = Vec::with_capacity(antimodes_count);
1049        for (val, _) in runs.into_iter().take(antimodes_count) {
1050            antimodes.push(val.clone());
1051        }
1052        // For modes: empty, count 0, occurrences 0 (not 1, 1)
1053        return ((Vec::new(), 0, 0), (antimodes, total_count, 1));
1054    }
1055
1056    // Collect modes and antimodes directly in a single pass, cloning values immediately
1057    // instead of collecting indices first and then cloning in a second pass
1058    let estimated_modes = (runs.len() / 10).clamp(1, 10);
1059    let estimated_antimodes = 10.min(runs.len());
1060
1061    let mut modes_result = Vec::with_capacity(estimated_modes);
1062    let mut antimodes_result = Vec::with_capacity(estimated_antimodes);
1063    let mut mode_count = 0;
1064    let mut antimodes_count = 0;
1065    let mut antimodes_collected = 0_u32;
1066
1067    for (val, count) in &runs {
1068        if *count == highest_count {
1069            modes_result.push((*val).clone());
1070            mode_count += 1;
1071        }
1072        if *count == lowest_count {
1073            antimodes_count += 1;
1074            if antimodes_collected < 10 {
1075                antimodes_result.push((*val).clone());
1076                antimodes_collected += 1;
1077            }
1078        }
1079    }
1080
1081    (
1082        (modes_result, mode_count, highest_count),
1083        (antimodes_result, antimodes_count, lowest_count),
1084    )
1085}
1086
1087/// A commutative data structure for lazily sorted sequences of data.
1088///
1089/// The sort does not occur until statistics need to be computed.
1090///
1091/// Note that this works on types that do not define a total ordering like
1092/// `f32` and `f64`. When an ordering is not defined, an arbitrary order
1093/// is returned.
1094#[allow(clippy::unsafe_derive_deserialize)]
1095#[derive(Clone, Serialize, Deserialize, Eq, PartialEq)]
1096pub struct Unsorted<T> {
1097    sorted: bool,
1098    data: Vec<Partial<T>>,
1099}
1100
1101impl<T: PartialOrd + Send> Unsorted<T> {
1102    /// Create initial empty state.
1103    #[inline]
1104    #[must_use]
1105    pub fn new() -> Unsorted<T> {
1106        Default::default()
1107    }
1108
1109    /// Add a new element to the set.
1110    #[allow(clippy::inline_always)]
1111    #[inline(always)]
1112    pub fn add(&mut self, v: T) {
1113        self.sorted = false;
1114        self.data.push(Partial(v));
1115    }
1116
1117    /// Return the number of data points.
1118    #[inline]
1119    #[must_use]
1120    pub const fn len(&self) -> usize {
1121        self.data.len()
1122    }
1123
1124    #[inline]
1125    #[must_use]
1126    pub const fn is_empty(&self) -> bool {
1127        self.data.is_empty()
1128    }
1129
1130    #[inline]
1131    fn sort(&mut self) {
1132        if !self.sorted {
1133            // Use sequential sort for small datasets (< 10k elements) to avoid parallel overhead
1134            if self.data.len() < PARALLEL_THRESHOLD {
1135                self.data.sort_unstable();
1136            } else {
1137                self.data.par_sort_unstable();
1138            }
1139            self.sorted = true;
1140        }
1141    }
1142
1143    #[inline]
1144    const fn already_sorted(&mut self) {
1145        self.sorted = true;
1146    }
1147
1148    /// Add multiple elements efficiently
1149    #[inline]
1150    pub fn add_bulk(&mut self, values: Vec<T>) {
1151        self.sorted = false;
1152        self.data.reserve(values.len());
1153        self.data.extend(values.into_iter().map(Partial));
1154    }
1155
1156    /// Shrink capacity to fit current data
1157    #[inline]
1158    pub fn shrink_to_fit(&mut self) {
1159        self.data.shrink_to_fit();
1160    }
1161
1162    /// Create with specific capacity
1163    #[inline]
1164    #[must_use]
1165    pub fn with_capacity(capacity: usize) -> Self {
1166        Unsorted {
1167            sorted: true,
1168            data: Vec::with_capacity(capacity),
1169        }
1170    }
1171
1172    /// Add a value assuming it's greater than all existing values
1173    #[inline]
1174    pub fn push_ascending(&mut self, value: T) {
1175        if let Some(last) = self.data.last() {
1176            debug_assert!(last.0 <= value, "Value must be >= than last element");
1177        }
1178        self.data.push(Partial(value));
1179        // Data remains sorted
1180    }
1181}
1182
1183impl<T: PartialOrd + PartialEq + Clone + Send + Sync> Unsorted<T> {
1184    #[inline]
1185    /// Returns the cardinality of the data.
1186    /// Set `sorted` to `true` if the data is already sorted.
1187    /// Set `parallel_threshold` to `0` to force sequential processing.
1188    /// Set `parallel_threshold` to `1` to use the default parallel threshold (`10_000`).
1189    /// Set `parallel_threshold` to `2` to force parallel processing.
1190    /// Set `parallel_threshold` to any other value to use a custom parallel threshold
1191    /// greater than the default threshold of `10_000`.
1192    pub fn cardinality(&mut self, sorted: bool, parallel_threshold: usize) -> u64 {
1193        const CHUNK_SIZE: usize = 2048; // Process data in chunks of 2048 elements
1194        const DEFAULT_PARALLEL_THRESHOLD: usize = 10_240; // multiple of 2048
1195
1196        let len = self.data.len();
1197        match len {
1198            0 => return 0,
1199            1 => return 1,
1200            _ => {}
1201        }
1202
1203        if sorted {
1204            self.already_sorted();
1205        } else {
1206            self.sort();
1207        }
1208
1209        let use_parallel = parallel_threshold != 0
1210            && (parallel_threshold == 1
1211                || len > parallel_threshold.max(DEFAULT_PARALLEL_THRESHOLD));
1212
1213        if use_parallel {
1214            // Parallel processing using chunks
1215            // Process chunks in parallel, returning (count, first_elem, last_elem) for each
1216            type ChunkInfo<'a, T> = Vec<(u64, Option<&'a Partial<T>>, Option<&'a Partial<T>>)>;
1217            let chunk_info: ChunkInfo<'_, T> = self
1218                .data
1219                .par_chunks(CHUNK_SIZE)
1220                .map(|chunk| {
1221                    // Count unique elements within this chunk
1222                    let mut count = u64::from(!chunk.is_empty());
1223                    for window in chunk.windows(2) {
1224                        // safety: windows(2) guarantees window has length 2
1225                        if unsafe { window.get_unchecked(0) != window.get_unchecked(1) } {
1226                            count += 1;
1227                        }
1228                    }
1229                    (count, chunk.first(), chunk.last())
1230                })
1231                .collect();
1232
1233            // Combine results, checking boundaries between chunks
1234            let mut total = 0;
1235            for (i, &(count, first_opt, _last_opt)) in chunk_info.iter().enumerate() {
1236                total += count;
1237
1238                // Check boundary with previous chunk
1239                if i > 0
1240                    && let (Some(prev_last), Some(curr_first)) = (chunk_info[i - 1].2, first_opt)
1241                    && prev_last == curr_first
1242                {
1243                    total -= 1; // Deduct 1 if boundary values are equal
1244                }
1245            }
1246
1247            total
1248        } else {
1249            // Sequential processing
1250
1251            // the statement below is equivalent to:
1252            // let mut count = if self.data.is_empty() { 0 } else { 1 };
1253            let mut count = u64::from(!self.data.is_empty());
1254
1255            for window in self.data.windows(2) {
1256                // safety: windows(2) guarantees window has length 2
1257                if unsafe { window.get_unchecked(0) != window.get_unchecked(1) } {
1258                    count += 1;
1259                }
1260            }
1261            count
1262        }
1263    }
1264}
1265
1266impl<T: PartialOrd + Clone + Send> Unsorted<T> {
1267    /// Returns the mode of the data.
1268    #[inline]
1269    pub fn mode(&mut self) -> Option<T> {
1270        if self.data.is_empty() {
1271            return None;
1272        }
1273        self.sort();
1274        mode_on_sorted(self.data.iter().map(|p| &p.0)).cloned()
1275    }
1276
1277    /// Returns the modes of the data.
1278    /// Note that there is also a `frequency::mode()` function that return one mode
1279    /// with the highest frequency. If there is a tie, it returns None.
1280    #[inline]
1281    fn modes(&mut self) -> (Vec<T>, usize, u32) {
1282        if self.data.is_empty() {
1283            return (Vec::new(), 0, 0);
1284        }
1285        self.sort();
1286        modes_and_antimodes_on_sorted_slice(&self.data).0
1287    }
1288
1289    /// Returns the antimodes of the data.
1290    /// `antimodes_result` only returns the first 10 antimodes
1291    #[inline]
1292    fn antimodes(&mut self) -> (Vec<T>, usize, u32) {
1293        if self.data.is_empty() {
1294            return (Vec::new(), 0, 0);
1295        }
1296        self.sort();
1297        modes_and_antimodes_on_sorted_slice(&self.data).1
1298    }
1299
1300    /// Returns the modes and antimodes of the data.
1301    /// `antimodes_result` only returns the first 10 antimodes
1302    #[allow(clippy::type_complexity)]
1303    #[inline]
1304    pub fn modes_antimodes(&mut self) -> ((Vec<T>, usize, u32), (Vec<T>, usize, u32)) {
1305        if self.data.is_empty() {
1306            return ((Vec::new(), 0, 0), (Vec::new(), 0, 0));
1307        }
1308        self.sort();
1309        modes_and_antimodes_on_sorted_slice(&self.data)
1310    }
1311}
1312
1313impl Unsorted<Vec<u8>> {
1314    /// Add a byte slice, converting to `Vec<u8>` internally.
1315    ///
1316    /// This is a convenience method that avoids requiring the caller to call
1317    /// `.to_vec()` before `add()`. The allocation still occurs internally,
1318    /// but the API is cleaner and opens the door for future optimizations
1319    /// (e.g., frequency-map deduplication for high-cardinality data).
1320    #[allow(clippy::inline_always)]
1321    #[inline(always)]
1322    pub fn add_bytes(&mut self, v: &[u8]) {
1323        self.sorted = false;
1324        self.data.push(Partial(v.to_vec()));
1325    }
1326}
1327
1328impl<T: PartialOrd + ToPrimitive + Send> Unsorted<T> {
1329    /// Returns the median of the data.
1330    #[inline]
1331    pub fn median(&mut self) -> Option<f64> {
1332        if self.data.is_empty() {
1333            return None;
1334        }
1335        self.sort();
1336        median_on_sorted(&self.data)
1337    }
1338}
1339
1340impl<T: PartialOrd + ToPrimitive + Send + Sync> Unsorted<T> {
1341    /// Returns the Median Absolute Deviation (MAD) of the data.
1342    #[inline]
1343    pub fn mad(&mut self, existing_median: Option<f64>) -> Option<f64> {
1344        if self.data.is_empty() {
1345            return None;
1346        }
1347        if existing_median.is_none() {
1348            self.sort();
1349        }
1350        mad_on_sorted(&self.data, existing_median)
1351    }
1352}
1353
1354impl<T: PartialOrd + ToPrimitive + Send> Unsorted<T> {
1355    /// Returns the quartiles of the data using the traditional sorting approach.
1356    ///
1357    /// This method sorts the data first and then computes quartiles.
1358    /// Time complexity: O(n log n)
1359    #[inline]
1360    pub fn quartiles(&mut self) -> Option<(f64, f64, f64)> {
1361        if self.data.is_empty() {
1362            return None;
1363        }
1364        self.sort();
1365        quartiles_on_sorted(&self.data)
1366    }
1367}
1368
1369impl<T: PartialOrd + ToPrimitive + Send + Sync> Unsorted<T> {
1370    /// Returns the Gini Coefficient of the data.
1371    ///
1372    /// The Gini Coefficient measures inequality in a distribution, ranging from 0 (perfect equality)
1373    /// to 1 (perfect inequality). This method sorts the data first and then computes the Gini coefficient.
1374    /// Time complexity: O(n log n)
1375    #[inline]
1376    pub fn gini(&mut self, precalc_sum: Option<f64>) -> Option<f64> {
1377        if self.data.is_empty() {
1378            return None;
1379        }
1380        self.sort();
1381        gini_on_sorted(&self.data, precalc_sum)
1382    }
1383
1384    /// Returns the kurtosis (excess kurtosis) of the data.
1385    ///
1386    /// Kurtosis measures the "tailedness" of a distribution. Excess kurtosis is kurtosis - 3,
1387    /// where 0 indicates a normal distribution, positive values indicate heavy tails, and
1388    /// negative values indicate light tails. This method sorts the data first and then computes kurtosis.
1389    /// Time complexity: O(n log n)
1390    #[inline]
1391    pub fn kurtosis(
1392        &mut self,
1393        precalc_mean: Option<f64>,
1394        precalc_variance: Option<f64>,
1395    ) -> Option<f64> {
1396        if self.data.is_empty() {
1397            return None;
1398        }
1399        self.sort();
1400        kurtosis_on_sorted(&self.data, precalc_mean, precalc_variance)
1401    }
1402
1403    /// Returns the percentile rank of a value in the data.
1404    ///
1405    /// Returns the percentile rank (0-100) of the given value. If the value is less than all
1406    /// values, returns 0.0. If greater than all, returns 100.0.
1407    /// This method sorts the data first and then computes the percentile rank.
1408    /// Time complexity: O(n log n)
1409    #[inline]
1410    pub fn percentile_rank<V>(&mut self, value: V) -> Option<f64>
1411    where
1412        V: PartialOrd + ToPrimitive,
1413    {
1414        if self.data.is_empty() {
1415            return None;
1416        }
1417        self.sort();
1418        percentile_rank_on_sorted(&self.data, &value)
1419    }
1420
1421    /// Returns the Atkinson Index of the data.
1422    ///
1423    /// The Atkinson Index measures inequality with an inequality aversion parameter ε.
1424    /// It ranges from 0 (perfect equality) to 1 (perfect inequality).
1425    /// Higher ε values give more weight to inequality at the lower end of the distribution.
1426    /// This method sorts the data first and then computes the Atkinson index.
1427    /// Time complexity: O(n log n)
1428    ///
1429    /// # Arguments
1430    /// * `epsilon` - Inequality aversion parameter (must be >= 0). Common values:
1431    ///   - 0.0: No inequality aversion (returns 0)
1432    ///   - 0.5: Moderate aversion
1433    ///   - 1.0: Uses geometric mean (special case)
1434    ///   - 2.0: High aversion
1435    /// * `precalc_mean` - Optional pre-calculated mean
1436    /// * `precalc_geometric_sum` - Optional pre-calculated geometric sum (sum of ln(val)), only used when epsilon = 1
1437    #[inline]
1438    pub fn atkinson(
1439        &mut self,
1440        epsilon: f64,
1441        precalc_mean: Option<f64>,
1442        precalc_geometric_sum: Option<f64>,
1443    ) -> Option<f64> {
1444        if self.data.is_empty() {
1445            return None;
1446        }
1447        self.sort();
1448        atkinson_on_sorted(&self.data, epsilon, precalc_mean, precalc_geometric_sum)
1449    }
1450}
1451
1452impl<T: PartialOrd + ToPrimitive + Clone + Send> Unsorted<T> {
1453    /// Returns the quartiles of the data using selection algorithm.
1454    ///
1455    /// This implementation uses a selection algorithm (quickselect) to find quartiles
1456    /// in O(n) average time complexity instead of O(n log n) sorting.
1457    /// Requires T to implement Clone to create a working copy of the data.
1458    ///
1459    /// **Performance Note**: While theoretically O(n) vs O(n log n), this implementation
1460    /// is often slower than the sorting-based approach for small to medium datasets due to:
1461    /// - Need to find multiple order statistics (3 separate quickselect calls)
1462    /// - Overhead of copying data to avoid mutation
1463    /// - Rayon's highly optimized parallel sorting
1464    #[inline]
1465    pub fn quartiles_with_selection(&mut self) -> Option<(f64, f64, f64)> {
1466        if self.data.is_empty() {
1467            return None;
1468        }
1469        // Use zero-copy approach (indices-based) to avoid cloning all elements
1470        quartiles_with_zero_copy_selection(&self.data)
1471    }
1472}
1473
1474impl<T: PartialOrd + ToPrimitive + Send> Unsorted<T> {
1475    /// Returns the quartiles using zero-copy selection algorithm.
1476    ///
1477    /// This implementation avoids copying data by working with indices instead,
1478    /// providing better performance than the clone-based selection approach.
1479    /// The algorithm is O(n) average time and only allocates a vector of indices (usize).
1480    #[inline]
1481    #[must_use]
1482    pub fn quartiles_zero_copy(&self) -> Option<(f64, f64, f64)> {
1483        if self.data.is_empty() {
1484            return None;
1485        }
1486        quartiles_with_zero_copy_selection(&self.data)
1487    }
1488}
1489
1490impl<T: PartialOrd + Send> Commute for Unsorted<T> {
1491    #[inline]
1492    fn merge(&mut self, mut v: Unsorted<T>) {
1493        if v.is_empty() {
1494            return;
1495        }
1496
1497        self.sorted = false;
1498        // we use std::mem::take to avoid unnecessary allocations
1499        self.data.extend(std::mem::take(&mut v.data));
1500    }
1501}
1502
1503impl<T: PartialOrd> Default for Unsorted<T> {
1504    #[inline]
1505    fn default() -> Unsorted<T> {
1506        Unsorted {
1507            data: Vec::with_capacity(16),
1508            sorted: true, // empty is sorted
1509        }
1510    }
1511}
1512
1513impl<T: PartialOrd + Send> FromIterator<T> for Unsorted<T> {
1514    #[inline]
1515    fn from_iter<I: IntoIterator<Item = T>>(it: I) -> Unsorted<T> {
1516        let mut v = Unsorted::new();
1517        v.extend(it);
1518        v
1519    }
1520}
1521
1522impl<T: PartialOrd> Extend<T> for Unsorted<T> {
1523    #[inline]
1524    fn extend<I: IntoIterator<Item = T>>(&mut self, it: I) {
1525        self.sorted = false;
1526        self.data.extend(it.into_iter().map(Partial));
1527    }
1528}
1529
1530fn custom_percentiles_on_sorted<T>(data: &[Partial<T>], percentiles: &[u8]) -> Option<Vec<T>>
1531where
1532    T: PartialOrd + Clone,
1533{
1534    let len = data.len();
1535
1536    // Early return for empty array or invalid percentiles
1537    if len == 0 || percentiles.iter().any(|&p| p > 100) {
1538        return None;
1539    }
1540
1541    // Optimize: Check if percentiles are already sorted and unique
1542    let unique_percentiles: Vec<u8> = if percentiles.len() <= 1 {
1543        // Single or empty percentile - no need to sort/dedup
1544        percentiles.to_vec()
1545    } else {
1546        // Check if already sorted and unique (common case)
1547        let is_sorted_unique = percentiles.windows(2).all(|w| w[0] < w[1]);
1548
1549        if is_sorted_unique {
1550            // Already sorted and unique, use directly without cloning
1551            percentiles.to_vec()
1552        } else {
1553            // Need to sort and dedup - use fixed-size bool array (domain is 0..=100)
1554            let mut seen = [false; 101];
1555            let mut sorted_unique = Vec::with_capacity(percentiles.len().min(101));
1556            for &p in percentiles {
1557                if !seen[p as usize] {
1558                    seen[p as usize] = true;
1559                    sorted_unique.push(p);
1560                }
1561            }
1562            sorted_unique.sort_unstable();
1563            sorted_unique
1564        }
1565    };
1566
1567    let mut results = Vec::with_capacity(unique_percentiles.len());
1568
1569    // SAFETY: All index calculations below are guaranteed to be in bounds
1570    // because we've verified len > 0 and the rank calculation ensures
1571    // the index is within bounds
1572    unsafe {
1573        for &p in &unique_percentiles {
1574            // Calculate the ordinal rank using nearest-rank method
1575            // see https://en.wikipedia.org/wiki/Percentile#The_nearest-rank_method
1576            // n = ⌈(P/100) × N⌉
1577            #[allow(clippy::cast_sign_loss)]
1578            let rank = ((f64::from(p) / 100.0) * len as f64).ceil() as usize;
1579
1580            // Convert to 0-based index
1581            let idx = rank.saturating_sub(1);
1582
1583            // Get the value at that rank and extract the inner value
1584            results.push(data.get_unchecked(idx).0.clone());
1585        }
1586    }
1587
1588    Some(results)
1589}
1590
1591impl<T: PartialOrd + Clone + Send> Unsorted<T> {
1592    /// Returns the requested percentiles of the data.
1593    ///
1594    /// Uses the nearest-rank method to compute percentiles.
1595    /// Each returned value is an actual value from the dataset.
1596    ///
1597    /// # Arguments
1598    /// * `percentiles` - A slice of u8 values representing percentiles to compute (0-100)
1599    ///
1600    /// # Returns
1601    /// * `None` if the data is empty or if any percentile is > 100
1602    /// * `Some(Vec<T>)` containing percentile values in the same order as requested
1603    ///
1604    /// # Example
1605    /// ```
1606    /// use stats::Unsorted;
1607    /// let mut data = Unsorted::new();
1608    /// data.extend(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
1609    /// let percentiles = vec![25, 50, 75];
1610    /// let results = data.custom_percentiles(&percentiles).unwrap();
1611    /// assert_eq!(results, vec![3, 5, 8]);
1612    /// ```
1613    #[inline]
1614    pub fn custom_percentiles(&mut self, percentiles: &[u8]) -> Option<Vec<T>> {
1615        if self.data.is_empty() {
1616            return None;
1617        }
1618        self.sort();
1619        custom_percentiles_on_sorted(&self.data, percentiles)
1620    }
1621}
1622
1623#[cfg(test)]
1624mod test {
1625    use super::*;
1626
1627    #[test]
1628    fn test_cardinality_empty() {
1629        let mut unsorted: Unsorted<i32> = Unsorted::new();
1630        assert_eq!(unsorted.cardinality(false, 1), 0);
1631    }
1632
1633    #[test]
1634    fn test_cardinality_single_element() {
1635        let mut unsorted = Unsorted::new();
1636        unsorted.add(5);
1637        assert_eq!(unsorted.cardinality(false, 1), 1);
1638    }
1639
1640    #[test]
1641    fn test_cardinality_unique_elements() {
1642        let mut unsorted = Unsorted::new();
1643        unsorted.extend(vec![1, 2, 3, 4, 5]);
1644        assert_eq!(unsorted.cardinality(false, 1), 5);
1645    }
1646
1647    #[test]
1648    fn test_cardinality_duplicate_elements() {
1649        let mut unsorted = Unsorted::new();
1650        unsorted.extend(vec![1, 2, 2, 3, 3, 3, 4, 4, 4, 4]);
1651        assert_eq!(unsorted.cardinality(false, 1), 4);
1652    }
1653
1654    #[test]
1655    fn test_cardinality_all_same() {
1656        let mut unsorted = Unsorted::new();
1657        unsorted.extend(vec![1; 100]);
1658        assert_eq!(unsorted.cardinality(false, 1), 1);
1659    }
1660
1661    #[test]
1662    fn test_cardinality_large_range() {
1663        let mut unsorted = Unsorted::new();
1664        unsorted.extend(0..1_000_000);
1665        assert_eq!(unsorted.cardinality(false, 1), 1_000_000);
1666    }
1667
1668    #[test]
1669    fn test_cardinality_large_range_sequential() {
1670        let mut unsorted = Unsorted::new();
1671        unsorted.extend(0..1_000_000);
1672        assert_eq!(unsorted.cardinality(false, 2_000_000), 1_000_000);
1673    }
1674
1675    #[test]
1676    fn test_cardinality_presorted() {
1677        let mut unsorted = Unsorted::new();
1678        unsorted.extend(vec![1, 2, 3, 4, 5]);
1679        unsorted.sort();
1680        assert_eq!(unsorted.cardinality(true, 1), 5);
1681    }
1682
1683    #[test]
1684    fn test_cardinality_float() {
1685        let mut unsorted = Unsorted::new();
1686        unsorted.extend(vec![1.0, 1.0, 2.0, 3.0, 3.0, 4.0]);
1687        assert_eq!(unsorted.cardinality(false, 1), 4);
1688    }
1689
1690    #[test]
1691    fn test_cardinality_string() {
1692        let mut unsorted = Unsorted::new();
1693        unsorted.extend(vec!["a", "b", "b", "c", "c", "c"]);
1694        assert_eq!(unsorted.cardinality(false, 1), 3);
1695    }
1696
1697    #[test]
1698    fn test_quartiles_selection_vs_sorted() {
1699        // Test that selection-based quartiles gives same results as sorting-based
1700        let test_cases = vec![
1701            vec![3, 5, 7, 9],
1702            vec![3, 5, 7],
1703            vec![1, 2, 7, 11],
1704            vec![3, 5, 7, 9, 12],
1705            vec![2, 2, 3, 8, 10],
1706            vec![3, 5, 7, 9, 12, 20],
1707            vec![0, 2, 4, 8, 10, 11],
1708            vec![3, 5, 7, 9, 12, 20, 21],
1709            vec![1, 5, 6, 6, 7, 10, 19],
1710        ];
1711
1712        for test_case in test_cases {
1713            let mut unsorted1 = Unsorted::new();
1714            let mut unsorted2 = Unsorted::new();
1715            let mut unsorted3 = Unsorted::new();
1716            unsorted1.extend(test_case.clone());
1717            unsorted2.extend(test_case.clone());
1718            unsorted3.extend(test_case.clone());
1719
1720            let result_sorted = unsorted1.quartiles();
1721            let result_selection = unsorted2.quartiles_with_selection();
1722            let result_zero_copy = unsorted3.quartiles_zero_copy();
1723
1724            assert_eq!(
1725                result_sorted, result_selection,
1726                "Selection mismatch for test case: {:?}",
1727                test_case
1728            );
1729            assert_eq!(
1730                result_sorted, result_zero_copy,
1731                "Zero-copy mismatch for test case: {:?}",
1732                test_case
1733            );
1734        }
1735    }
1736
1737    #[test]
1738    fn test_quartiles_with_selection_small() {
1739        // Test edge cases for selection-based quartiles
1740        let mut unsorted: Unsorted<i32> = Unsorted::new();
1741        assert_eq!(unsorted.quartiles_with_selection(), None);
1742
1743        let mut unsorted = Unsorted::new();
1744        unsorted.extend(vec![1, 2]);
1745        assert_eq!(unsorted.quartiles_with_selection(), None);
1746
1747        let mut unsorted = Unsorted::new();
1748        unsorted.extend(vec![1, 2, 3]);
1749        assert_eq!(unsorted.quartiles_with_selection(), Some((1.0, 2.0, 3.0)));
1750    }
1751
1752    #[test]
1753    fn test_quickselect() {
1754        let data = vec![
1755            Partial(3),
1756            Partial(1),
1757            Partial(4),
1758            Partial(1),
1759            Partial(5),
1760            Partial(9),
1761            Partial(2),
1762            Partial(6),
1763        ];
1764
1765        // Test finding different positions
1766        assert_eq!(quickselect(&mut data.clone(), 0), Some(&1));
1767        assert_eq!(quickselect(&mut data.clone(), 3), Some(&3));
1768        assert_eq!(quickselect(&mut data.clone(), 7), Some(&9));
1769
1770        // Test edge cases
1771        let mut empty: Vec<Partial<i32>> = vec![];
1772        assert_eq!(quickselect(&mut empty, 0), None);
1773
1774        let mut data = vec![Partial(3), Partial(1), Partial(4), Partial(1), Partial(5)];
1775        assert_eq!(quickselect(&mut data, 10), None); // k >= len
1776    }
1777
1778    #[test]
1779    fn median_stream() {
1780        assert_eq!(median(vec![3usize, 5, 7, 9].into_iter()), Some(6.0));
1781        assert_eq!(median(vec![3usize, 5, 7].into_iter()), Some(5.0));
1782    }
1783
1784    #[test]
1785    fn mad_stream() {
1786        assert_eq!(mad(vec![3usize, 5, 7, 9].into_iter(), None), Some(2.0));
1787        assert_eq!(
1788            mad(
1789                vec![
1790                    86usize, 60, 95, 39, 49, 12, 56, 82, 92, 24, 33, 28, 46, 34, 100, 39, 100, 38,
1791                    50, 61, 39, 88, 5, 13, 64
1792                ]
1793                .into_iter(),
1794                None
1795            ),
1796            Some(16.0)
1797        );
1798    }
1799
1800    #[test]
1801    fn mad_stream_precalc_median() {
1802        let data = vec![3usize, 5, 7, 9].into_iter();
1803        let median1 = median(data.clone());
1804        assert_eq!(mad(data, median1), Some(2.0));
1805
1806        let data2 = vec![
1807            86usize, 60, 95, 39, 49, 12, 56, 82, 92, 24, 33, 28, 46, 34, 100, 39, 100, 38, 50, 61,
1808            39, 88, 5, 13, 64,
1809        ]
1810        .into_iter();
1811        let median2 = median(data2.clone());
1812        assert_eq!(mad(data2, median2), Some(16.0));
1813    }
1814
1815    #[test]
1816    fn mode_stream() {
1817        assert_eq!(mode(vec![3usize, 5, 7, 9].into_iter()), None);
1818        assert_eq!(mode(vec![3usize, 3, 3, 3].into_iter()), Some(3));
1819        assert_eq!(mode(vec![3usize, 3, 3, 4].into_iter()), Some(3));
1820        assert_eq!(mode(vec![4usize, 3, 3, 3].into_iter()), Some(3));
1821        assert_eq!(mode(vec![1usize, 1, 2, 3, 3].into_iter()), None);
1822    }
1823
1824    #[test]
1825    fn median_floats() {
1826        assert_eq!(median(vec![3.0f64, 5.0, 7.0, 9.0].into_iter()), Some(6.0));
1827        assert_eq!(median(vec![3.0f64, 5.0, 7.0].into_iter()), Some(5.0));
1828    }
1829
1830    #[test]
1831    fn mode_floats() {
1832        assert_eq!(mode(vec![3.0f64, 5.0, 7.0, 9.0].into_iter()), None);
1833        assert_eq!(mode(vec![3.0f64, 3.0, 3.0, 3.0].into_iter()), Some(3.0));
1834        assert_eq!(mode(vec![3.0f64, 3.0, 3.0, 4.0].into_iter()), Some(3.0));
1835        assert_eq!(mode(vec![4.0f64, 3.0, 3.0, 3.0].into_iter()), Some(3.0));
1836        assert_eq!(mode(vec![1.0f64, 1.0, 2.0, 3.0, 3.0].into_iter()), None);
1837    }
1838
1839    #[test]
1840    fn modes_stream() {
1841        assert_eq!(modes(vec![3usize, 5, 7, 9].into_iter()), (vec![], 0, 0));
1842        assert_eq!(modes(vec![3usize, 3, 3, 3].into_iter()), (vec![3], 1, 4));
1843        assert_eq!(modes(vec![3usize, 3, 4, 4].into_iter()), (vec![3, 4], 2, 2));
1844        assert_eq!(modes(vec![4usize, 3, 3, 3].into_iter()), (vec![3], 1, 3));
1845        assert_eq!(modes(vec![1usize, 1, 2, 2].into_iter()), (vec![1, 2], 2, 2));
1846        let vec: Vec<u32> = vec![];
1847        assert_eq!(modes(vec.into_iter()), (vec![], 0, 0));
1848    }
1849
1850    #[test]
1851    fn modes_floats() {
1852        assert_eq!(
1853            modes(vec![3_f64, 5.0, 7.0, 9.0].into_iter()),
1854            (vec![], 0, 0)
1855        );
1856        assert_eq!(
1857            modes(vec![3_f64, 3.0, 3.0, 3.0].into_iter()),
1858            (vec![3.0], 1, 4)
1859        );
1860        assert_eq!(
1861            modes(vec![3_f64, 3.0, 4.0, 4.0].into_iter()),
1862            (vec![3.0, 4.0], 2, 2)
1863        );
1864        assert_eq!(
1865            modes(vec![1_f64, 1.0, 2.0, 3.0, 3.0].into_iter()),
1866            (vec![1.0, 3.0], 2, 2)
1867        );
1868    }
1869
1870    #[test]
1871    fn antimodes_stream() {
1872        assert_eq!(
1873            antimodes(vec![3usize, 5, 7, 9].into_iter()),
1874            (vec![3, 5, 7, 9], 4, 1)
1875        );
1876        assert_eq!(
1877            antimodes(vec![1usize, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13].into_iter()),
1878            (vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10], 13, 1)
1879        );
1880        assert_eq!(
1881            antimodes(vec![1usize, 3, 3, 3].into_iter()),
1882            (vec![1], 1, 1)
1883        );
1884        assert_eq!(
1885            antimodes(vec![3usize, 3, 4, 4].into_iter()),
1886            (vec![3, 4], 2, 2)
1887        );
1888        assert_eq!(
1889            antimodes(
1890                vec![
1891                    3usize, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13,
1892                    14, 14, 15, 15
1893                ]
1894                .into_iter()
1895            ),
1896            // we only show the first 10 of the 13 antimodes
1897            (vec![3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 13, 2)
1898        );
1899        assert_eq!(
1900            antimodes(
1901                vec![
1902                    3usize, 3, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 4, 4, 5, 5, 6, 6, 7, 7, 13, 13,
1903                    14, 14, 15, 15
1904                ]
1905                .into_iter()
1906            ),
1907            (vec![3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 13, 2)
1908        );
1909        assert_eq!(
1910            antimodes(vec![3usize, 3, 3, 4].into_iter()),
1911            (vec![4], 1, 1)
1912        );
1913        assert_eq!(
1914            antimodes(vec![4usize, 3, 3, 3].into_iter()),
1915            (vec![4], 1, 1)
1916        );
1917        assert_eq!(
1918            antimodes(vec![1usize, 1, 2, 2].into_iter()),
1919            (vec![1, 2], 2, 2)
1920        );
1921        let vec: Vec<u32> = vec![];
1922        assert_eq!(antimodes(vec.into_iter()), (vec![], 0, 0));
1923    }
1924
1925    #[test]
1926    fn antimodes_floats() {
1927        assert_eq!(
1928            antimodes(vec![3_f64, 5.0, 7.0, 9.0].into_iter()),
1929            (vec![3.0, 5.0, 7.0, 9.0], 4, 1)
1930        );
1931        assert_eq!(
1932            antimodes(vec![3_f64, 3.0, 3.0, 3.0].into_iter()),
1933            (vec![], 0, 0)
1934        );
1935        assert_eq!(
1936            antimodes(vec![3_f64, 3.0, 4.0, 4.0].into_iter()),
1937            (vec![3.0, 4.0], 2, 2)
1938        );
1939        assert_eq!(
1940            antimodes(vec![1_f64, 1.0, 2.0, 3.0, 3.0].into_iter()),
1941            (vec![2.0], 1, 1)
1942        );
1943    }
1944
1945    #[test]
1946    fn test_custom_percentiles() {
1947        // Test with integers
1948        let mut unsorted: Unsorted<i32> = Unsorted::new();
1949        unsorted.extend(1..=11); // [1,2,3,4,5,6,7,8,9,10,11]
1950
1951        let result = unsorted.custom_percentiles(&[25, 50, 75]).unwrap();
1952        assert_eq!(result, vec![3, 6, 9]);
1953
1954        // Test with strings
1955        let mut str_data = Unsorted::new();
1956        str_data.extend(vec!["a", "b", "c", "d", "e"]);
1957        let result = str_data.custom_percentiles(&[20, 40, 60, 80]).unwrap();
1958        assert_eq!(result, vec!["a", "b", "c", "d"]);
1959
1960        // Test with chars
1961        let mut char_data = Unsorted::new();
1962        char_data.extend('a'..='e');
1963        let result = char_data.custom_percentiles(&[25, 50, 75]).unwrap();
1964        assert_eq!(result, vec!['b', 'c', 'd']);
1965
1966        // Test with floats
1967        let mut float_data = Unsorted::new();
1968        float_data.extend(vec![1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9]);
1969        let result = float_data
1970            .custom_percentiles(&[10, 30, 50, 70, 90])
1971            .unwrap();
1972        assert_eq!(result, vec![1.1, 3.3, 5.5, 7.7, 9.9]);
1973
1974        // Test with empty percentiles array
1975        let result = float_data.custom_percentiles(&[]).unwrap();
1976        assert_eq!(result, Vec::<f64>::new());
1977
1978        // Test with duplicate percentiles
1979        let result = float_data.custom_percentiles(&[50, 50, 50]).unwrap();
1980        assert_eq!(result, vec![5.5]);
1981
1982        // Test with extreme percentiles
1983        let result = float_data.custom_percentiles(&[0, 100]).unwrap();
1984        assert_eq!(result, vec![1.1, 9.9]);
1985
1986        // Test with unsorted percentiles
1987        let result = float_data.custom_percentiles(&[75, 25, 50]).unwrap();
1988        assert_eq!(result, vec![3.3, 5.5, 7.7]); // results always sorted
1989
1990        // Test with single element
1991        let mut single = Unsorted::new();
1992        single.add(42);
1993        let result = single.custom_percentiles(&[0, 50, 100]).unwrap();
1994        assert_eq!(result, vec![42, 42, 42]);
1995    }
1996
1997    #[test]
1998    fn quartiles_stream() {
1999        assert_eq!(
2000            quartiles(vec![3usize, 5, 7].into_iter()),
2001            Some((3., 5., 7.))
2002        );
2003        assert_eq!(
2004            quartiles(vec![3usize, 5, 7, 9].into_iter()),
2005            Some((4., 6., 8.))
2006        );
2007        assert_eq!(
2008            quartiles(vec![1usize, 2, 7, 11].into_iter()),
2009            Some((1.5, 4.5, 9.))
2010        );
2011        assert_eq!(
2012            quartiles(vec![3usize, 5, 7, 9, 12].into_iter()),
2013            Some((4., 7., 10.5))
2014        );
2015        assert_eq!(
2016            quartiles(vec![2usize, 2, 3, 8, 10].into_iter()),
2017            Some((2., 3., 9.))
2018        );
2019        assert_eq!(
2020            quartiles(vec![3usize, 5, 7, 9, 12, 20].into_iter()),
2021            Some((5., 8., 12.))
2022        );
2023        assert_eq!(
2024            quartiles(vec![0usize, 2, 4, 8, 10, 11].into_iter()),
2025            Some((2., 6., 10.))
2026        );
2027        assert_eq!(
2028            quartiles(vec![3usize, 5, 7, 9, 12, 20, 21].into_iter()),
2029            Some((5., 9., 20.))
2030        );
2031        assert_eq!(
2032            quartiles(vec![1usize, 5, 6, 6, 7, 10, 19].into_iter()),
2033            Some((5., 6., 10.))
2034        );
2035    }
2036
2037    #[test]
2038    fn quartiles_floats() {
2039        assert_eq!(
2040            quartiles(vec![3_f64, 5., 7.].into_iter()),
2041            Some((3., 5., 7.))
2042        );
2043        assert_eq!(
2044            quartiles(vec![3_f64, 5., 7., 9.].into_iter()),
2045            Some((4., 6., 8.))
2046        );
2047        assert_eq!(
2048            quartiles(vec![3_f64, 5., 7., 9., 12.].into_iter()),
2049            Some((4., 7., 10.5))
2050        );
2051        assert_eq!(
2052            quartiles(vec![3_f64, 5., 7., 9., 12., 20.].into_iter()),
2053            Some((5., 8., 12.))
2054        );
2055        assert_eq!(
2056            quartiles(vec![3_f64, 5., 7., 9., 12., 20., 21.].into_iter()),
2057            Some((5., 9., 20.))
2058        );
2059    }
2060
2061    #[test]
2062    fn test_quartiles_zero_copy_small() {
2063        // Test edge cases for zero-copy quartiles
2064        let unsorted: Unsorted<i32> = Unsorted::new();
2065        assert_eq!(unsorted.quartiles_zero_copy(), None);
2066
2067        let mut unsorted = Unsorted::new();
2068        unsorted.extend(vec![1, 2]);
2069        assert_eq!(unsorted.quartiles_zero_copy(), None);
2070
2071        let mut unsorted = Unsorted::new();
2072        unsorted.extend(vec![1, 2, 3]);
2073        assert_eq!(unsorted.quartiles_zero_copy(), Some((1.0, 2.0, 3.0)));
2074
2075        // Test larger case
2076        let mut unsorted = Unsorted::new();
2077        unsorted.extend(vec![3, 5, 7, 9]);
2078        assert_eq!(unsorted.quartiles_zero_copy(), Some((4.0, 6.0, 8.0)));
2079    }
2080
2081    #[test]
2082    fn gini_empty() {
2083        let mut unsorted: Unsorted<i32> = Unsorted::new();
2084        assert_eq!(unsorted.gini(None), None);
2085        let empty_vec: Vec<i32> = vec![];
2086        assert_eq!(gini(empty_vec.into_iter(), None), None);
2087    }
2088
2089    #[test]
2090    fn gini_single_element() {
2091        let mut unsorted = Unsorted::new();
2092        unsorted.add(5);
2093        assert_eq!(unsorted.gini(None), Some(0.0));
2094        assert_eq!(gini(vec![5].into_iter(), None), Some(0.0));
2095    }
2096
2097    #[test]
2098    fn gini_perfect_equality() {
2099        // All values are the same - perfect equality, Gini = 0
2100        let mut unsorted = Unsorted::new();
2101        unsorted.extend(vec![10, 10, 10, 10, 10]);
2102        let result = unsorted.gini(None).unwrap();
2103        assert!((result - 0.0).abs() < 1e-10, "Expected 0.0, got {}", result);
2104
2105        assert!((gini(vec![10, 10, 10, 10, 10].into_iter(), None).unwrap() - 0.0).abs() < 1e-10);
2106    }
2107
2108    #[test]
2109    fn gini_perfect_inequality() {
2110        // One value has everything, others have zero - perfect inequality
2111        // For [0, 0, 0, 0, 100], Gini should be close to 1
2112        let mut unsorted = Unsorted::new();
2113        unsorted.extend(vec![0, 0, 0, 0, 100]);
2114        let result = unsorted.gini(None).unwrap();
2115        // Perfect inequality should give Gini close to 1
2116        // For n=5, one value=100, others=0: G = (2*5*100)/(5*100) - 6/5 = 2 - 1.2 = 0.8
2117        assert!((result - 0.8).abs() < 1e-10, "Expected 0.8, got {}", result);
2118    }
2119
2120    #[test]
2121    fn gini_stream() {
2122        // Test with known values
2123        // For [1, 2, 3, 4, 5]:
2124        // sum = 15
2125        // weighted_sum = 1*1 + 2*2 + 3*3 + 4*4 + 5*5 = 1 + 4 + 9 + 16 + 25 = 55
2126        // n = 5
2127        // G = (2 * 55) / (5 * 15) - 6/5 = 110/75 - 1.2 = 1.4667 - 1.2 = 0.2667
2128        let result = gini(vec![1usize, 2, 3, 4, 5].into_iter(), None).unwrap();
2129        let expected = (2.0 * 55.0) / (5.0 * 15.0) - 6.0 / 5.0;
2130        assert!(
2131            (result - expected).abs() < 1e-10,
2132            "Expected {}, got {}",
2133            expected,
2134            result
2135        );
2136    }
2137
2138    #[test]
2139    fn gini_floats() {
2140        let mut unsorted = Unsorted::new();
2141        unsorted.extend(vec![1.0, 2.0, 3.0, 4.0, 5.0]);
2142        let result = unsorted.gini(None).unwrap();
2143        let expected = (2.0 * 55.0) / (5.0 * 15.0) - 6.0 / 5.0;
2144        assert!((result - expected).abs() < 1e-10);
2145
2146        assert!(
2147            (gini(vec![1.0f64, 2.0, 3.0, 4.0, 5.0].into_iter(), None).unwrap() - expected).abs()
2148                < 1e-10
2149        );
2150    }
2151
2152    #[test]
2153    fn gini_all_zeros() {
2154        // All zeros - sum is zero, Gini is undefined
2155        let mut unsorted = Unsorted::new();
2156        unsorted.extend(vec![0, 0, 0, 0]);
2157        assert_eq!(unsorted.gini(None), None);
2158        assert_eq!(gini(vec![0, 0, 0, 0].into_iter(), None), None);
2159    }
2160
2161    #[test]
2162    fn gini_negative_values() {
2163        // Test with negative values (mathematically valid)
2164        let mut unsorted = Unsorted::new();
2165        unsorted.extend(vec![-5, -3, -1, 1, 3, 5]);
2166        let result = unsorted.gini(None);
2167        // Sum is 0, so Gini is undefined
2168        assert_eq!(result, None);
2169
2170        // Test with negative values that don't sum to zero
2171        let mut unsorted = Unsorted::new();
2172        unsorted.extend(vec![-2, -1, 0, 1, 2]);
2173        let result = unsorted.gini(None);
2174        // Sum is 0, so Gini is undefined
2175        assert_eq!(result, None);
2176
2177        // Test with values containing negatives that sum to non-zero
2178        // Gini is undefined for negative values, should return None
2179        let mut unsorted = Unsorted::new();
2180        unsorted.extend(vec![-1, 0, 1, 2, 3]);
2181        let result = unsorted.gini(None);
2182        assert_eq!(result, None);
2183    }
2184
2185    #[test]
2186    fn gini_known_cases() {
2187        // Test case: [1, 1, 1, 1, 1] - perfect equality
2188        let mut unsorted = Unsorted::new();
2189        unsorted.extend(vec![1, 1, 1, 1, 1]);
2190        let result = unsorted.gini(None).unwrap();
2191        assert!((result - 0.0).abs() < 1e-10);
2192
2193        // Test case: [0, 0, 0, 0, 1] - high inequality
2194        let mut unsorted = Unsorted::new();
2195        unsorted.extend(vec![0, 0, 0, 0, 1]);
2196        let result = unsorted.gini(None).unwrap();
2197        // G = (2 * 5 * 1) / (5 * 1) - 6/5 = 2 - 1.2 = 0.8
2198        assert!((result - 0.8).abs() < 1e-10);
2199
2200        // Test case: [1, 2, 3] - moderate inequality
2201        let mut unsorted = Unsorted::new();
2202        unsorted.extend(vec![1, 2, 3]);
2203        let result = unsorted.gini(None).unwrap();
2204        // sum = 6, weighted_sum = 1*1 + 2*2 + 3*3 = 1 + 4 + 9 = 14
2205        // G = (2 * 14) / (3 * 6) - 4/3 = 28/18 - 4/3 = 1.5556 - 1.3333 = 0.2222
2206        let expected = (2.0 * 14.0) / (3.0 * 6.0) - 4.0 / 3.0;
2207        assert!((result - expected).abs() < 1e-10);
2208    }
2209
2210    #[test]
2211    fn gini_precalc_sum() {
2212        // Test with pre-calculated sum
2213        let mut unsorted = Unsorted::new();
2214        unsorted.extend(vec![1, 2, 3, 4, 5]);
2215        let precalc_sum = Some(15.0);
2216        let result = unsorted.gini(precalc_sum).unwrap();
2217        let expected = (2.0 * 55.0) / (5.0 * 15.0) - 6.0 / 5.0;
2218        assert!((result - expected).abs() < 1e-10);
2219
2220        // Test that pre-calculated sum gives same result
2221        let mut unsorted2 = Unsorted::new();
2222        unsorted2.extend(vec![1, 2, 3, 4, 5]);
2223        let result2 = unsorted2.gini(None).unwrap();
2224        assert!((result - result2).abs() < 1e-10);
2225    }
2226
2227    #[test]
2228    fn gini_large_dataset() {
2229        // Test with larger dataset to exercise parallel path
2230        let data: Vec<i32> = (1..=1000).collect();
2231        let result = gini(data.iter().copied(), None);
2232        assert!(result.is_some());
2233        let gini_val = result.unwrap();
2234        // For uniform distribution, Gini should be positive but not too high
2235        assert!(gini_val > 0.0 && gini_val < 0.5);
2236    }
2237
2238    #[test]
2239    fn gini_unsorted_vs_sorted() {
2240        // Test that sorting doesn't affect result
2241        let mut unsorted1 = Unsorted::new();
2242        unsorted1.extend(vec![5, 2, 8, 1, 9, 3, 7, 4, 6]);
2243        let result1 = unsorted1.gini(None).unwrap();
2244
2245        let mut unsorted2 = Unsorted::new();
2246        unsorted2.extend(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
2247        let result2 = unsorted2.gini(None).unwrap();
2248
2249        assert!((result1 - result2).abs() < 1e-10);
2250    }
2251
2252    #[test]
2253    fn gini_small_values() {
2254        // Test with very small values
2255        let mut unsorted = Unsorted::new();
2256        unsorted.extend(vec![0.001, 0.002, 0.003, 0.004, 0.005]);
2257        let result = unsorted.gini(None);
2258        assert!(result.is_some());
2259        // Should be same as [1, 2, 3, 4, 5] scaled down
2260        let expected = (2.0 * 55.0) / (5.0 * 15.0) - 6.0 / 5.0;
2261        assert!((result.unwrap() - expected).abs() < 1e-10);
2262    }
2263
2264    #[test]
2265    fn gini_large_values() {
2266        // Test with large values
2267        let mut unsorted = Unsorted::new();
2268        unsorted.extend(vec![1000, 2000, 3000, 4000, 5000]);
2269        let result = unsorted.gini(None);
2270        assert!(result.is_some());
2271        // Should be same as [1, 2, 3, 4, 5] scaled up
2272        let expected = (2.0 * 55.0) / (5.0 * 15.0) - 6.0 / 5.0;
2273        assert!((result.unwrap() - expected).abs() < 1e-10);
2274    }
2275
2276    #[test]
2277    fn gini_two_elements() {
2278        // Test with exactly 2 elements
2279        let mut unsorted = Unsorted::new();
2280        unsorted.extend(vec![1, 2]);
2281        let result = unsorted.gini(None).unwrap();
2282        // For [1, 2]: sum=3, weighted_sum=1*1+2*2=5, n=2
2283        // G = (2*5)/(2*3) - 3/2 = 10/6 - 1.5 = 1.6667 - 1.5 = 0.1667
2284        let expected = (2.0 * 5.0) / (2.0 * 3.0) - 3.0 / 2.0;
2285        assert!((result - expected).abs() < 1e-10);
2286    }
2287
2288    #[test]
2289    fn gini_precalc_sum_zero() {
2290        // Test with pre-calculated sum of zero (should return None)
2291        let mut unsorted = Unsorted::new();
2292        unsorted.extend(vec![1, 2, 3, 4, 5]);
2293        let result = unsorted.gini(Some(0.0));
2294        assert_eq!(result, None);
2295    }
2296
2297    #[test]
2298    fn gini_precalc_sum_negative() {
2299        // Gini is undefined for negative values, should return None
2300        let mut unsorted = Unsorted::new();
2301        unsorted.extend(vec![-5, -3, -1, 1, 3]);
2302        let result = unsorted.gini(None);
2303        assert_eq!(result, None);
2304
2305        // Negative precalculated sum should also return None
2306        let mut unsorted = Unsorted::new();
2307        unsorted.extend(vec![1, 2, 3]);
2308        let result = unsorted.gini(Some(-5.0));
2309        assert_eq!(result, None);
2310    }
2311
2312    #[test]
2313    fn gini_different_types() {
2314        // Test with different integer types
2315        let mut unsorted_u32 = Unsorted::new();
2316        unsorted_u32.extend(vec![1u32, 2, 3, 4, 5]);
2317        let result_u32 = unsorted_u32.gini(None).unwrap();
2318
2319        let mut unsorted_i64 = Unsorted::new();
2320        unsorted_i64.extend(vec![1i64, 2, 3, 4, 5]);
2321        let result_i64 = unsorted_i64.gini(None).unwrap();
2322
2323        let expected = (2.0 * 55.0) / (5.0 * 15.0) - 6.0 / 5.0;
2324        assert!((result_u32 - expected).abs() < 1e-10);
2325        assert!((result_i64 - expected).abs() < 1e-10);
2326    }
2327
2328    #[test]
2329    fn gini_extreme_inequality() {
2330        // Test with extreme inequality: one very large value, many zeros
2331        let mut unsorted = Unsorted::new();
2332        unsorted.extend(vec![0, 0, 0, 0, 0, 0, 0, 0, 0, 1000]);
2333        let result = unsorted.gini(None).unwrap();
2334        // For [0,0,0,0,0,0,0,0,0,1000]: sum=1000, weighted_sum=10*1000=10000, n=10
2335        // G = (2*10000)/(10*1000) - 11/10 = 20/10 - 1.1 = 2 - 1.1 = 0.9
2336        assert!((result - 0.9).abs() < 1e-10);
2337    }
2338
2339    #[test]
2340    fn gini_duplicate_values() {
2341        // Test with many duplicate values
2342        let mut unsorted = Unsorted::new();
2343        unsorted.extend(vec![1, 1, 1, 5, 5, 5, 10, 10, 10]);
2344        let result = unsorted.gini(None);
2345        assert!(result.is_some());
2346        // Should be between 0 and 1
2347        let gini_val = result.unwrap();
2348        assert!(gini_val >= 0.0 && gini_val <= 1.0);
2349    }
2350
2351    #[test]
2352    fn kurtosis_empty() {
2353        let mut unsorted: Unsorted<i32> = Unsorted::new();
2354        assert_eq!(unsorted.kurtosis(None, None), None);
2355        let empty_vec: Vec<i32> = vec![];
2356        assert_eq!(kurtosis(empty_vec.into_iter(), None, None), None);
2357    }
2358
2359    #[test]
2360    fn kurtosis_small() {
2361        // Need at least 4 elements
2362        let mut unsorted = Unsorted::new();
2363        unsorted.extend(vec![1, 2]);
2364        assert_eq!(unsorted.kurtosis(None, None), None);
2365
2366        let mut unsorted = Unsorted::new();
2367        unsorted.extend(vec![1, 2, 3]);
2368        assert_eq!(unsorted.kurtosis(None, None), None);
2369    }
2370
2371    #[test]
2372    fn kurtosis_normal_distribution() {
2373        // Normal distribution should have kurtosis close to 0
2374        let mut unsorted = Unsorted::new();
2375        unsorted.extend(vec![1, 2, 3, 4, 5]);
2376        let result = unsorted.kurtosis(None, None);
2377        assert!(result.is_some());
2378        // For small samples, kurtosis can vary significantly
2379    }
2380
2381    #[test]
2382    fn kurtosis_all_same() {
2383        // All same values - variance is 0, kurtosis undefined
2384        let mut unsorted = Unsorted::new();
2385        unsorted.extend(vec![5, 5, 5, 5]);
2386        assert_eq!(unsorted.kurtosis(None, None), None);
2387    }
2388
2389    #[test]
2390    fn kurtosis_stream() {
2391        let result = kurtosis(vec![1usize, 2, 3, 4, 5].into_iter(), None, None);
2392        assert!(result.is_some());
2393    }
2394
2395    #[test]
2396    fn kurtosis_precalc_mean_variance() {
2397        // Test with pre-calculated mean and variance
2398        let mut unsorted = Unsorted::new();
2399        unsorted.extend(vec![1, 2, 3, 4, 5]);
2400
2401        // Calculate mean and variance manually
2402        let mean = 3.0f64;
2403        let variance = ((1.0f64 - 3.0).powi(2)
2404            + (2.0f64 - 3.0).powi(2)
2405            + (3.0f64 - 3.0).powi(2)
2406            + (4.0f64 - 3.0).powi(2)
2407            + (5.0f64 - 3.0).powi(2))
2408            / 5.0;
2409
2410        let result = unsorted.kurtosis(Some(mean), Some(variance));
2411        assert!(result.is_some());
2412
2413        // Test that pre-calculated values give same result
2414        let mut unsorted2 = Unsorted::new();
2415        unsorted2.extend(vec![1, 2, 3, 4, 5]);
2416        let result2 = unsorted2.kurtosis(None, None);
2417        assert!((result.unwrap() - result2.unwrap()).abs() < 1e-10);
2418    }
2419
2420    #[test]
2421    fn kurtosis_precalc_mean_only() {
2422        // Test with pre-calculated mean only
2423        let mut unsorted = Unsorted::new();
2424        unsorted.extend(vec![1, 2, 3, 4, 5]);
2425        let mean = 3.0f64;
2426
2427        let result = unsorted.kurtosis(Some(mean), None);
2428        assert!(result.is_some());
2429
2430        // Test that pre-calculated mean gives same result
2431        let mut unsorted2 = Unsorted::new();
2432        unsorted2.extend(vec![1, 2, 3, 4, 5]);
2433        let result2 = unsorted2.kurtosis(None, None);
2434        assert!((result.unwrap() - result2.unwrap()).abs() < 1e-10);
2435    }
2436
2437    #[test]
2438    fn kurtosis_precalc_variance_only() {
2439        // Test with pre-calculated variance only
2440        let mut unsorted = Unsorted::new();
2441        unsorted.extend(vec![1, 2, 3, 4, 5]);
2442        let variance = ((1.0f64 - 3.0).powi(2)
2443            + (2.0f64 - 3.0).powi(2)
2444            + (3.0f64 - 3.0).powi(2)
2445            + (4.0f64 - 3.0).powi(2)
2446            + (5.0f64 - 3.0).powi(2))
2447            / 5.0;
2448
2449        let result = unsorted.kurtosis(None, Some(variance));
2450        assert!(result.is_some());
2451
2452        // Test that pre-calculated variance gives same result
2453        let mut unsorted2 = Unsorted::new();
2454        unsorted2.extend(vec![1, 2, 3, 4, 5]);
2455        let result2 = unsorted2.kurtosis(None, None);
2456        assert!((result.unwrap() - result2.unwrap()).abs() < 1e-10);
2457    }
2458
2459    #[test]
2460    fn kurtosis_exact_calculation() {
2461        // Test with exact calculation for [1, 2, 3, 4]
2462        // Mean = 2.5
2463        // Variance = ((1-2.5)^2 + (2-2.5)^2 + (3-2.5)^2 + (4-2.5)^2) / 4 = (2.25 + 0.25 + 0.25 + 2.25) / 4 = 1.25
2464        // Variance^2 = 1.5625
2465        // Fourth powers: (1-2.5)^4 + (2-2.5)^4 + (3-2.5)^4 + (4-2.5)^4 = 5.0625 + 0.0625 + 0.0625 + 5.0625 = 10.25
2466        // n = 4
2467        // Kurtosis = (4*5*10.25) / (3*2*1*1.5625) - 3*3*3/(2*1) = 205 / 9.375 - 13.5 = 21.8667 - 13.5 = 8.3667
2468        let mut unsorted = Unsorted::new();
2469        unsorted.extend(vec![1, 2, 3, 4]);
2470        let result = unsorted.kurtosis(None, None).unwrap();
2471        // For small samples, kurtosis can be very high
2472        assert!(result.is_finite());
2473    }
2474
2475    #[test]
2476    fn kurtosis_uniform_distribution() {
2477        // Uniform distribution should have negative excess kurtosis
2478        let mut unsorted = Unsorted::new();
2479        unsorted.extend(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
2480        let result = unsorted.kurtosis(None, None).unwrap();
2481        // Uniform distribution has excess kurtosis = -1.2
2482        // But for small samples, it can vary significantly
2483        assert!(result.is_finite());
2484    }
2485
2486    #[test]
2487    fn kurtosis_large_dataset() {
2488        // Test with larger dataset to exercise parallel path
2489        let data: Vec<i32> = (1..=1000).collect();
2490        let result = kurtosis(data.iter().copied(), None, None);
2491        assert!(result.is_some());
2492        let kurt_val = result.unwrap();
2493        assert!(kurt_val.is_finite());
2494    }
2495
2496    #[test]
2497    fn kurtosis_unsorted_vs_sorted() {
2498        // Test that sorting doesn't affect result
2499        let mut unsorted1 = Unsorted::new();
2500        unsorted1.extend(vec![5, 2, 8, 1, 9, 3, 7, 4, 6]);
2501        let result1 = unsorted1.kurtosis(None, None).unwrap();
2502
2503        let mut unsorted2 = Unsorted::new();
2504        unsorted2.extend(vec![1, 2, 3, 4, 5, 6, 7, 8, 9]);
2505        let result2 = unsorted2.kurtosis(None, None).unwrap();
2506
2507        assert!((result1 - result2).abs() < 1e-10);
2508    }
2509
2510    #[test]
2511    fn kurtosis_minimum_size() {
2512        // Test with exactly 4 elements (minimum required)
2513        let mut unsorted = Unsorted::new();
2514        unsorted.extend(vec![1, 2, 3, 4]);
2515        let result = unsorted.kurtosis(None, None);
2516        assert!(result.is_some());
2517        assert!(result.unwrap().is_finite());
2518    }
2519
2520    #[test]
2521    fn kurtosis_heavy_tailed() {
2522        // Test with heavy-tailed distribution (outliers)
2523        let mut unsorted = Unsorted::new();
2524        unsorted.extend(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 100]);
2525        let result = unsorted.kurtosis(None, None).unwrap();
2526        // Heavy tails should give positive excess kurtosis
2527        assert!(result.is_finite());
2528        // With an outlier, kurtosis should be positive
2529        assert!(result > -10.0); // Allow some variance but should be reasonable
2530    }
2531
2532    #[test]
2533    fn kurtosis_light_tailed() {
2534        // Test with light-tailed distribution (values close together)
2535        let mut unsorted = Unsorted::new();
2536        unsorted.extend(vec![10, 11, 12, 13, 14, 15, 16, 17, 18, 19]);
2537        let result = unsorted.kurtosis(None, None).unwrap();
2538        // Light tails might give negative excess kurtosis
2539        assert!(result.is_finite());
2540    }
2541
2542    #[test]
2543    fn kurtosis_small_variance() {
2544        // Test with very small variance (values very close together)
2545        let mut unsorted = Unsorted::new();
2546        unsorted.extend(vec![10.0, 10.001, 10.002, 10.003, 10.004]);
2547        let result = unsorted.kurtosis(None, None);
2548        // Should still compute (variance is very small but non-zero)
2549        assert!(result.is_some());
2550        assert!(result.unwrap().is_finite());
2551    }
2552
2553    #[test]
2554    fn kurtosis_precalc_zero_variance() {
2555        // Test with pre-calculated variance of zero (should return None)
2556        let mut unsorted = Unsorted::new();
2557        unsorted.extend(vec![1, 2, 3, 4, 5]);
2558        let result = unsorted.kurtosis(None, Some(0.0));
2559        assert_eq!(result, None);
2560    }
2561
2562    #[test]
2563    fn kurtosis_precalc_negative_variance() {
2564        // Test with negative variance (invalid, but should handle gracefully)
2565        let mut unsorted = Unsorted::new();
2566        unsorted.extend(vec![1, 2, 3, 4, 5]);
2567        // Negative variance is invalid, but function should handle it
2568        let result = unsorted.kurtosis(None, Some(-1.0));
2569        // Should either return None or handle it gracefully
2570        // The function computes variance_sq = variance^2, so negative becomes positive
2571        // But this is invalid input, so behavior may vary
2572        // For now, just check it doesn't panic
2573        let _ = result;
2574    }
2575
2576    #[test]
2577    fn kurtosis_different_types() {
2578        // Test with different integer types
2579        let mut unsorted_u32 = Unsorted::new();
2580        unsorted_u32.extend(vec![1u32, 2, 3, 4, 5]);
2581        let result_u32 = unsorted_u32.kurtosis(None, None).unwrap();
2582
2583        let mut unsorted_i64 = Unsorted::new();
2584        unsorted_i64.extend(vec![1i64, 2, 3, 4, 5]);
2585        let result_i64 = unsorted_i64.kurtosis(None, None).unwrap();
2586
2587        assert!((result_u32 - result_i64).abs() < 1e-10);
2588    }
2589
2590    #[test]
2591    fn kurtosis_floating_point_precision() {
2592        // Test floating point precision
2593        let mut unsorted = Unsorted::new();
2594        unsorted.extend(vec![1.1, 2.2, 3.3, 4.4, 5.5]);
2595        let result = unsorted.kurtosis(None, None);
2596        assert!(result.is_some());
2597        assert!(result.unwrap().is_finite());
2598    }
2599
2600    #[test]
2601    fn kurtosis_negative_values() {
2602        // Test with negative values
2603        let mut unsorted = Unsorted::new();
2604        unsorted.extend(vec![-5, -3, -1, 1, 3, 5]);
2605        let result = unsorted.kurtosis(None, None);
2606        assert!(result.is_some());
2607        assert!(result.unwrap().is_finite());
2608    }
2609
2610    #[test]
2611    fn kurtosis_mixed_positive_negative() {
2612        // Test with mixed positive and negative values
2613        let mut unsorted = Unsorted::new();
2614        unsorted.extend(vec![-10, -5, 0, 5, 10]);
2615        let result = unsorted.kurtosis(None, None);
2616        assert!(result.is_some());
2617        assert!(result.unwrap().is_finite());
2618    }
2619
2620    #[test]
2621    fn kurtosis_duplicate_values() {
2622        // Test with duplicate values (but not all same)
2623        let mut unsorted = Unsorted::new();
2624        unsorted.extend(vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5]);
2625        let result = unsorted.kurtosis(None, None);
2626        assert!(result.is_some());
2627        assert!(result.unwrap().is_finite());
2628    }
2629
2630    #[test]
2631    fn kurtosis_precalc_mean_wrong() {
2632        // Test that wrong pre-calculated mean gives wrong result
2633        let mut unsorted1 = Unsorted::new();
2634        unsorted1.extend(vec![1, 2, 3, 4, 5]);
2635        let correct_result = unsorted1.kurtosis(None, None).unwrap();
2636
2637        let mut unsorted2 = Unsorted::new();
2638        unsorted2.extend(vec![1, 2, 3, 4, 5]);
2639        let wrong_mean = 10.0; // Wrong mean
2640        let wrong_result = unsorted2.kurtosis(Some(wrong_mean), None).unwrap();
2641
2642        // Results should be different
2643        assert!((correct_result - wrong_result).abs() > 1e-5);
2644    }
2645
2646    #[test]
2647    fn percentile_rank_empty() {
2648        let mut unsorted: Unsorted<i32> = Unsorted::new();
2649        assert_eq!(unsorted.percentile_rank(5), None);
2650        let empty_vec: Vec<i32> = vec![];
2651        assert_eq!(percentile_rank(empty_vec.into_iter(), 5), None);
2652    }
2653
2654    #[test]
2655    fn percentile_rank_basic() {
2656        let mut unsorted = Unsorted::new();
2657        unsorted.extend(vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10]);
2658
2659        // Value less than all
2660        assert_eq!(unsorted.percentile_rank(0), Some(0.0));
2661
2662        // Value greater than all
2663        assert_eq!(unsorted.percentile_rank(11), Some(100.0));
2664
2665        // Median (5) should be around 50th percentile
2666        let rank = unsorted.percentile_rank(5).unwrap();
2667        assert!((rank - 50.0).abs() < 1.0);
2668
2669        // First value should be at 10th percentile
2670        let rank = unsorted.percentile_rank(1).unwrap();
2671        assert!((rank - 10.0).abs() < 1.0);
2672    }
2673
2674    #[test]
2675    fn percentile_rank_duplicates() {
2676        let mut unsorted = Unsorted::new();
2677        unsorted.extend(vec![1, 1, 2, 2, 3, 3, 4, 4, 5, 5]);
2678
2679        // Value 2 appears twice, should be at 40th percentile (4 values <= 2)
2680        let rank = unsorted.percentile_rank(2).unwrap();
2681        assert!((rank - 40.0).abs() < 1.0);
2682    }
2683
2684    #[test]
2685    fn percentile_rank_stream() {
2686        let result = percentile_rank(vec![1usize, 2, 3, 4, 5].into_iter(), 3);
2687        assert_eq!(result, Some(60.0)); // 3 out of 5 values <= 3
2688    }
2689
2690    #[test]
2691    fn atkinson_empty() {
2692        let mut unsorted: Unsorted<i32> = Unsorted::new();
2693        assert_eq!(unsorted.atkinson(1.0, None, None), None);
2694        let empty_vec: Vec<i32> = vec![];
2695        assert_eq!(atkinson(empty_vec.into_iter(), 1.0, None, None), None);
2696    }
2697
2698    #[test]
2699    fn atkinson_single_element() {
2700        let mut unsorted = Unsorted::new();
2701        unsorted.add(5);
2702        assert_eq!(unsorted.atkinson(1.0, None, None), Some(0.0));
2703        assert_eq!(atkinson(vec![5].into_iter(), 1.0, None, None), Some(0.0));
2704    }
2705
2706    #[test]
2707    fn atkinson_perfect_equality() {
2708        // All values the same - perfect equality, Atkinson = 0
2709        let mut unsorted = Unsorted::new();
2710        unsorted.extend(vec![10, 10, 10, 10, 10]);
2711        let result = unsorted.atkinson(1.0, None, None).unwrap();
2712        assert!((result - 0.0).abs() < 1e-10);
2713    }
2714
2715    #[test]
2716    fn atkinson_epsilon_zero() {
2717        // Epsilon = 0 means no inequality aversion, should return 0
2718        let mut unsorted = Unsorted::new();
2719        unsorted.extend(vec![1, 2, 3, 4, 5]);
2720        let result = unsorted.atkinson(0.0, None, None).unwrap();
2721        assert!((result - 0.0).abs() < 1e-10);
2722    }
2723
2724    #[test]
2725    fn atkinson_epsilon_one() {
2726        // Epsilon = 1 uses geometric mean
2727        let mut unsorted = Unsorted::new();
2728        unsorted.extend(vec![1, 2, 3, 4, 5]);
2729        let result = unsorted.atkinson(1.0, None, None);
2730        assert!(result.is_some());
2731    }
2732
2733    #[test]
2734    fn atkinson_negative_epsilon() {
2735        let mut unsorted = Unsorted::new();
2736        unsorted.extend(vec![1, 2, 3, 4, 5]);
2737        assert_eq!(unsorted.atkinson(-1.0, None, None), None);
2738    }
2739
2740    #[test]
2741    fn atkinson_zero_mean() {
2742        // If mean is zero, Atkinson is undefined
2743        let mut unsorted = Unsorted::new();
2744        unsorted.extend(vec![0, 0, 0, 0]);
2745        assert_eq!(unsorted.atkinson(1.0, None, None), None);
2746    }
2747
2748    #[test]
2749    fn atkinson_stream() {
2750        let result = atkinson(vec![1usize, 2, 3, 4, 5].into_iter(), 1.0, None, None);
2751        assert!(result.is_some());
2752    }
2753
2754    #[test]
2755    fn atkinson_precalc_mean_geometric_sum() {
2756        // Test with pre-calculated mean and geometric_sum
2757        let mut unsorted = Unsorted::new();
2758        unsorted.extend(vec![1, 2, 3, 4, 5]);
2759
2760        // Calculate mean and geometric_sum manually
2761        let mean = 3.0f64;
2762        let geometric_sum = 1.0f64.ln() + 2.0f64.ln() + 3.0f64.ln() + 4.0f64.ln() + 5.0f64.ln();
2763
2764        let result = unsorted.atkinson(1.0, Some(mean), Some(geometric_sum));
2765        assert!(result.is_some());
2766
2767        // Test that pre-calculated values give same result
2768        let mut unsorted2 = Unsorted::new();
2769        unsorted2.extend(vec![1, 2, 3, 4, 5]);
2770        let result2 = unsorted2.atkinson(1.0, None, None);
2771        assert!((result.unwrap() - result2.unwrap()).abs() < 1e-10);
2772    }
2773
2774    #[test]
2775    fn atkinson_precalc_mean_only() {
2776        // Test with pre-calculated mean only
2777        let mut unsorted = Unsorted::new();
2778        unsorted.extend(vec![1, 2, 3, 4, 5]);
2779        let mean = 3.0f64;
2780
2781        let result = unsorted.atkinson(1.0, Some(mean), None);
2782        assert!(result.is_some());
2783
2784        // Test that pre-calculated mean gives same result
2785        let mut unsorted2 = Unsorted::new();
2786        unsorted2.extend(vec![1, 2, 3, 4, 5]);
2787        let result2 = unsorted2.atkinson(1.0, None, None);
2788        assert!((result.unwrap() - result2.unwrap()).abs() < 1e-10);
2789    }
2790
2791    #[test]
2792    fn atkinson_precalc_geometric_sum_only() {
2793        // Test with pre-calculated geometric_sum only
2794        let mut unsorted = Unsorted::new();
2795        unsorted.extend(vec![1, 2, 3, 4, 5]);
2796        let geometric_sum = 1.0f64.ln() + 2.0f64.ln() + 3.0f64.ln() + 4.0f64.ln() + 5.0f64.ln();
2797
2798        let result = unsorted.atkinson(1.0, None, Some(geometric_sum));
2799        assert!(result.is_some());
2800
2801        // Test that pre-calculated geometric_sum gives same result
2802        let mut unsorted2 = Unsorted::new();
2803        unsorted2.extend(vec![1, 2, 3, 4, 5]);
2804        let result2 = unsorted2.atkinson(1.0, None, None);
2805        assert!((result.unwrap() - result2.unwrap()).abs() < 1e-10);
2806    }
2807}
2808
2809#[cfg(test)]
2810mod bench {
2811    use super::*;
2812    use std::time::Instant;
2813
2814    #[test]
2815    #[ignore] // Run with `cargo test comprehensive_quartiles_benchmark -- --ignored --nocapture` to see performance comparison
2816    fn comprehensive_quartiles_benchmark() {
2817        // Test a wide range of data sizes
2818        let data_sizes = vec![
2819            1_000, 10_000, 100_000, 500_000, 1_000_000, 2_000_000, 5_000_000, 10_000_000,
2820        ];
2821
2822        println!("=== COMPREHENSIVE QUARTILES BENCHMARK ===\n");
2823
2824        for size in data_sizes {
2825            println!("--- Testing with {} elements ---", size);
2826
2827            // Test different data patterns
2828            let test_patterns = vec![
2829                ("Random", generate_random_data(size)),
2830                ("Reverse Sorted", {
2831                    let mut v = Vec::with_capacity(size);
2832                    for x in (0..size).rev() {
2833                        v.push(x as i32);
2834                    }
2835                    v
2836                }),
2837                ("Already Sorted", {
2838                    let mut v = Vec::with_capacity(size);
2839                    for x in 0..size {
2840                        v.push(x as i32);
2841                    }
2842                    v
2843                }),
2844                ("Many Duplicates", {
2845                    // Create a vector with just a few distinct values repeated many times
2846                    let mut v = Vec::with_capacity(size);
2847                    let chunk_size = size / 100;
2848                    for i in 0..100 {
2849                        v.extend(std::iter::repeat_n(i, chunk_size));
2850                    }
2851                    // Add any remaining elements
2852                    v.extend(std::iter::repeat_n(0, size - v.len()));
2853                    v
2854                }),
2855            ];
2856
2857            for (pattern_name, test_data) in test_patterns {
2858                println!("\n  Pattern: {}", pattern_name);
2859
2860                // Benchmark sorting-based approach
2861                let mut unsorted1 = Unsorted::new();
2862                unsorted1.extend(test_data.clone());
2863
2864                let start = Instant::now();
2865                let result_sorted = unsorted1.quartiles();
2866                let sorted_time = start.elapsed();
2867
2868                // Benchmark selection-based approach (with copying)
2869                let mut unsorted2 = Unsorted::new();
2870                unsorted2.extend(test_data.clone());
2871
2872                let start = Instant::now();
2873                let result_selection = unsorted2.quartiles_with_selection();
2874                let selection_time = start.elapsed();
2875
2876                // Benchmark zero-copy selection-based approach
2877                let mut unsorted3 = Unsorted::new();
2878                unsorted3.extend(test_data);
2879
2880                let start = Instant::now();
2881                let result_zero_copy = unsorted3.quartiles_zero_copy();
2882                let zero_copy_time = start.elapsed();
2883
2884                // Verify results are the same
2885                assert_eq!(result_sorted, result_selection);
2886                assert_eq!(result_sorted, result_zero_copy);
2887
2888                let selection_speedup =
2889                    sorted_time.as_nanos() as f64 / selection_time.as_nanos() as f64;
2890                let zero_copy_speedup =
2891                    sorted_time.as_nanos() as f64 / zero_copy_time.as_nanos() as f64;
2892
2893                println!("    Sorting:       {:>12?}", sorted_time);
2894                println!(
2895                    "    Selection:     {:>12?} (speedup: {:.2}x)",
2896                    selection_time, selection_speedup
2897                );
2898                println!(
2899                    "    Zero-copy:     {:>12?} (speedup: {:.2}x)",
2900                    zero_copy_time, zero_copy_speedup
2901                );
2902
2903                let best_algorithm =
2904                    if zero_copy_speedup > 1.0 && zero_copy_speedup >= selection_speedup {
2905                        "ZERO-COPY"
2906                    } else if selection_speedup > 1.0 {
2907                        "SELECTION"
2908                    } else {
2909                        "SORTING"
2910                    };
2911                println!("    Best: {}", best_algorithm);
2912            }
2913
2914            println!(); // Add blank line between sizes
2915        }
2916    }
2917
2918    // Generate random data for benchmarking
2919    fn generate_random_data(size: usize) -> Vec<i32> {
2920        // Simple LCG random number generator for reproducible results
2921        let mut rng = 1234567u64;
2922        let mut vec = Vec::with_capacity(size);
2923        for _ in 0..size {
2924            rng = rng.wrapping_mul(1103515245).wrapping_add(12345);
2925            vec.push((rng >> 16) as i32);
2926        }
2927        vec
2928    }
2929
2930    #[test]
2931    #[ignore] // Run with `cargo test find_selection_threshold -- --ignored --nocapture` to find exact threshold
2932    fn find_selection_threshold() {
2933        println!("=== FINDING SELECTION ALGORITHM THRESHOLD ===\n");
2934
2935        // Binary search approach to find the threshold
2936        let mut found_threshold = None;
2937        let test_sizes = vec![
2938            1_000_000, 2_000_000, 3_000_000, 4_000_000, 5_000_000, 7_500_000, 10_000_000,
2939            15_000_000, 20_000_000, 25_000_000, 30_000_000,
2940        ];
2941
2942        for size in test_sizes {
2943            println!("Testing size: {}", size);
2944
2945            // Use random data as it's most representative of real-world scenarios
2946            let test_data = generate_random_data(size);
2947
2948            // Run multiple iterations to get average performance
2949            let iterations = 3;
2950            let mut sorting_total = 0u128;
2951            let mut selection_total = 0u128;
2952            let mut zero_copy_total = 0u128;
2953
2954            for i in 0..iterations {
2955                println!("  Iteration {}/{}", i + 1, iterations);
2956
2957                // Sorting approach
2958                let mut unsorted1 = Unsorted::new();
2959                unsorted1.extend(test_data.clone());
2960
2961                let start = Instant::now();
2962                let _result_sorted = unsorted1.quartiles();
2963                sorting_total += start.elapsed().as_nanos();
2964
2965                // Selection approach (with copying)
2966                let mut unsorted2 = Unsorted::new();
2967                unsorted2.extend(test_data.clone());
2968
2969                let start = Instant::now();
2970                let _result_selection = unsorted2.quartiles_with_selection();
2971                selection_total += start.elapsed().as_nanos();
2972
2973                // Zero-copy selection approach
2974                let mut unsorted3 = Unsorted::new();
2975                unsorted3.extend(test_data.clone());
2976
2977                let start = Instant::now();
2978                let _result_zero_copy = unsorted3.quartiles_zero_copy();
2979                zero_copy_total += start.elapsed().as_nanos();
2980            }
2981
2982            let avg_sorting = sorting_total / iterations as u128;
2983            let avg_selection = selection_total / iterations as u128;
2984            let avg_zero_copy = zero_copy_total / iterations as u128;
2985            let selection_speedup = avg_sorting as f64 / avg_selection as f64;
2986            let zero_copy_speedup = avg_sorting as f64 / avg_zero_copy as f64;
2987
2988            println!(
2989                "  Average sorting:    {:>12.2}ms",
2990                avg_sorting as f64 / 1_000_000.0
2991            );
2992            println!(
2993                "  Average selection:  {:>12.2}ms (speedup: {:.2}x)",
2994                avg_selection as f64 / 1_000_000.0,
2995                selection_speedup
2996            );
2997            println!(
2998                "  Average zero-copy:  {:>12.2}ms (speedup: {:.2}x)",
2999                avg_zero_copy as f64 / 1_000_000.0,
3000                zero_copy_speedup
3001            );
3002
3003            if (selection_speedup > 1.0 || zero_copy_speedup > 1.0) && found_threshold.is_none() {
3004                found_threshold = Some(size);
3005                let best_method = if zero_copy_speedup > selection_speedup {
3006                    "Zero-copy"
3007                } else {
3008                    "Selection"
3009                };
3010                println!(
3011                    "  *** THRESHOLD FOUND: {} becomes faster at {} elements ***",
3012                    best_method, size
3013                );
3014            }
3015
3016            println!();
3017        }
3018
3019        match found_threshold {
3020            Some(threshold) => println!(
3021                "🎯 Selection algorithm becomes faster at approximately {} elements",
3022                threshold
3023            ),
3024            None => println!("❌ Selection algorithm did not become faster in the tested range"),
3025        }
3026    }
3027
3028    #[test]
3029    #[ignore] // Run with `cargo test benchmark_different_data_types -- --ignored --nocapture` to test different data types
3030    fn benchmark_different_data_types() {
3031        println!("=== BENCHMARKING DIFFERENT DATA TYPES ===\n");
3032
3033        let size = 5_000_000; // Use a large size where differences might be visible
3034
3035        // Test with f64 (floating point)
3036        println!("Testing with f64 data:");
3037        let float_data: Vec<f64> = generate_random_data(size)
3038            .into_iter()
3039            .map(|x| x as f64 / 1000.0)
3040            .collect();
3041
3042        let mut unsorted1 = Unsorted::new();
3043        unsorted1.extend(float_data.clone());
3044        let start = Instant::now();
3045        let _result = unsorted1.quartiles();
3046        let sorting_time = start.elapsed();
3047
3048        let mut unsorted2 = Unsorted::new();
3049        unsorted2.extend(float_data.clone());
3050        let start = Instant::now();
3051        let _result = unsorted2.quartiles_with_selection();
3052        let selection_time = start.elapsed();
3053
3054        let mut unsorted3 = Unsorted::new();
3055        unsorted3.extend(float_data);
3056        let start = Instant::now();
3057        let _result = unsorted3.quartiles_zero_copy();
3058        let zero_copy_time = start.elapsed();
3059
3060        println!("  Sorting:    {:?}", sorting_time);
3061        println!("  Selection:  {:?}", selection_time);
3062        println!("  Zero-copy:  {:?}", zero_copy_time);
3063        println!(
3064            "  Selection Speedup:  {:.2}x",
3065            sorting_time.as_nanos() as f64 / selection_time.as_nanos() as f64
3066        );
3067        println!(
3068            "  Zero-copy Speedup:  {:.2}x\n",
3069            sorting_time.as_nanos() as f64 / zero_copy_time.as_nanos() as f64
3070        );
3071
3072        // Test with i64 (larger integers)
3073        println!("Testing with i64 data:");
3074        let int64_data: Vec<i64> = generate_random_data(size)
3075            .into_iter()
3076            .map(|x| x as i64 * 1000)
3077            .collect();
3078
3079        let mut unsorted1 = Unsorted::new();
3080        unsorted1.extend(int64_data.clone());
3081        let start = Instant::now();
3082        let _result = unsorted1.quartiles();
3083        let sorting_time = start.elapsed();
3084
3085        let mut unsorted2 = Unsorted::new();
3086        unsorted2.extend(int64_data.clone());
3087        let start = Instant::now();
3088        let _result = unsorted2.quartiles_with_selection();
3089        let selection_time = start.elapsed();
3090
3091        let mut unsorted3 = Unsorted::new();
3092        unsorted3.extend(int64_data);
3093        let start = Instant::now();
3094        let _result = unsorted3.quartiles_zero_copy();
3095        let zero_copy_time = start.elapsed();
3096
3097        println!("  Sorting:    {:?}", sorting_time);
3098        println!("  Selection:  {:?}", selection_time);
3099        println!("  Zero-copy:  {:?}", zero_copy_time);
3100        println!(
3101            "  Selection Speedup:  {:.2}x",
3102            sorting_time.as_nanos() as f64 / selection_time.as_nanos() as f64
3103        );
3104        println!(
3105            "  Zero-copy Speedup:  {:.2}x",
3106            sorting_time.as_nanos() as f64 / zero_copy_time.as_nanos() as f64
3107        );
3108    }
3109}