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