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