Skip to main content

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