stats/
lib.rs

1#![allow(clippy::default_trait_access)]
2#![allow(clippy::cast_precision_loss)]
3#![allow(clippy::cast_possible_truncation)]
4#![allow(clippy::module_name_repetitions)]
5#![allow(clippy::missing_panics_doc)]
6#![allow(clippy::use_self)]
7
8use num_traits::ToPrimitive;
9use std::cmp::Ordering;
10use std::hash;
11
12use serde::{Deserialize, Serialize};
13
14pub use frequency::{Frequencies, UniqueValues};
15pub use minmax::MinMax;
16pub use online::{OnlineStats, mean, stddev, variance};
17pub use unsorted::{
18    Unsorted, antimodes, atkinson, gini, kurtosis, mad, median, mode, modes, percentile_rank,
19    quartiles,
20};
21
22/// Partial wraps a type that satisfies `PartialOrd` and implements `Ord`.
23///
24/// This allows types like `f64` to be used in data structures that require
25/// `Ord`. When an ordering is not defined, an arbitrary order is returned.
26#[allow(clippy::non_send_fields_in_send_ty)]
27#[allow(clippy::derive_ord_xor_partial_ord)]
28#[derive(Clone, PartialEq, PartialOrd, Serialize, Deserialize)]
29struct Partial<T>(pub T);
30
31impl<T: PartialEq> Eq for Partial<T> {}
32unsafe impl<T> Send for Partial<T> {}
33unsafe impl<T> Sync for Partial<T> {}
34
35#[allow(clippy::derive_ord_xor_partial_ord)]
36impl<T: PartialOrd> Ord for Partial<T> {
37    #[inline]
38    fn cmp(&self, other: &Partial<T>) -> Ordering {
39        self.partial_cmp(other).unwrap_or(Ordering::Less)
40    }
41}
42
43impl<T: ToPrimitive> ToPrimitive for Partial<T> {
44    #[inline]
45    fn to_isize(&self) -> Option<isize> {
46        self.0.to_isize()
47    }
48    #[inline]
49    fn to_i8(&self) -> Option<i8> {
50        self.0.to_i8()
51    }
52    #[inline]
53    fn to_i16(&self) -> Option<i16> {
54        self.0.to_i16()
55    }
56    #[inline]
57    fn to_i32(&self) -> Option<i32> {
58        self.0.to_i32()
59    }
60    #[inline]
61    fn to_i64(&self) -> Option<i64> {
62        self.0.to_i64()
63    }
64
65    #[inline]
66    fn to_usize(&self) -> Option<usize> {
67        self.0.to_usize()
68    }
69    #[inline]
70    fn to_u8(&self) -> Option<u8> {
71        self.0.to_u8()
72    }
73    #[inline]
74    fn to_u16(&self) -> Option<u16> {
75        self.0.to_u16()
76    }
77    #[inline]
78    fn to_u32(&self) -> Option<u32> {
79        self.0.to_u32()
80    }
81    #[inline]
82    fn to_u64(&self) -> Option<u64> {
83        self.0.to_u64()
84    }
85
86    #[inline]
87    fn to_f32(&self) -> Option<f32> {
88        self.0.to_f32()
89    }
90    #[inline]
91    fn to_f64(&self) -> Option<f64> {
92        self.0.to_f64()
93    }
94}
95
96#[allow(clippy::derived_hash_with_manual_eq)]
97impl<T: hash::Hash> hash::Hash for Partial<T> {
98    #[inline]
99    fn hash<H: hash::Hasher>(&self, state: &mut H) {
100        self.0.hash(state);
101    }
102}
103
104/// Defines an interface for types that have an identity and can be commuted.
105///
106/// The value returned by `Default::default` must be its identity with respect
107/// to the `merge` operation.
108pub trait Commute: Sized {
109    /// Merges the value `other` into `self`.
110    fn merge(&mut self, other: Self);
111
112    /// Merges the values in the iterator into `self`.
113    #[inline]
114    fn consume<I: Iterator<Item = Self>>(&mut self, other: I) {
115        for v in other {
116            self.merge(v);
117        }
118    }
119}
120
121/// Merges all items in the stream.
122///
123/// If the stream is empty, `None` is returned.
124#[inline]
125pub fn merge_all<T: Commute, I: Iterator<Item = T>>(mut it: I) -> Option<T> {
126    it.next().map_or_else(
127        || None,
128        |mut init| {
129            init.consume(it);
130            Some(init)
131        },
132    )
133}
134
135impl<T: Commute> Commute for Option<T> {
136    #[inline]
137    fn merge(&mut self, other: Option<T>) {
138        match *self {
139            None => {
140                *self = other;
141            }
142            Some(ref mut v1) => {
143                if let Some(v2) = other {
144                    v1.merge(v2);
145                }
146            }
147        }
148    }
149}
150
151impl<T: Commute, E> Commute for Result<T, E> {
152    #[inline]
153    fn merge(&mut self, other: Result<T, E>) {
154        if !self.is_err() && other.is_err() {
155            *self = other;
156            return;
157        }
158        #[allow(clippy::let_unit_value)]
159        #[allow(clippy::ignored_unit_patterns)]
160        let _ = self.as_mut().map_or((), |v1| {
161            other.map_or_else(
162                |_| {
163                    unreachable!();
164                },
165                |v2| {
166                    v1.merge(v2);
167                },
168            );
169        });
170    }
171}
172
173impl<T: Commute> Commute for Vec<T> {
174    #[inline]
175    fn merge(&mut self, other: Vec<T>) {
176        assert_eq!(self.len(), other.len());
177        for (v1, v2) in self.iter_mut().zip(other) {
178            v1.merge(v2);
179        }
180    }
181}
182
183mod frequency;
184mod minmax;
185mod online;
186mod unsorted;
187
188#[cfg(test)]
189mod test {
190    use crate::Commute;
191    use crate::unsorted::Unsorted;
192
193    #[test]
194    fn options() {
195        let v1: Unsorted<usize> = vec![2, 1, 3, 2].into_iter().collect();
196        let v2: Unsorted<usize> = vec![5, 6, 5, 5].into_iter().collect();
197        let mut merged = Some(v1);
198        merged.merge(Some(v2));
199        assert_eq!(merged.unwrap().mode(), Some(5));
200    }
201}