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