moving_average/
lib.rs

1//! # Moving Average Library
2//!
3//! `moving_average` is a library for calculating the moving average on a stream of data.
4//!
5//! ## Features
6//!
7//! - Calculate moving average in an ergonomic way.
8//!
9//! ## Usage
10//!
11//! First, add this to your `Cargo.toml`:
12//!
13//! ```toml
14//! [dependencies]
15//! moving_average = "0.1.0"
16//! ```
17//!
18//! Then, add this to your crate root:
19//!
20//! ```rust
21//! extern crate moving_average;
22//! ```
23//!
24//! ### Basic Operations
25//!
26//! You can create a new `Moving` instance and add or subtract values from it:
27//!
28//! ```rust
29//! use moving_average::Moving;
30//!
31//! let mut moving_average: Moving<usize> = Moving::new();
32//! moving_average.add(10);
33//! moving_average.add(20);
34//! assert_eq!(moving_average, 15);
35//! ```
36
37use num_traits::ToPrimitive;
38use std::{
39    cell::RefCell,
40    collections::HashMap,
41    fmt::Display,
42    marker::PhantomData,
43    ops::{AddAssign, Deref},
44};
45
46use ordered_float::OrderedFloat;
47
48#[derive(Debug, Clone)]
49pub struct Value(f64);
50
51impl Deref for Value {
52    type Target = f64;
53
54    fn deref(&self) -> &Self::Target {
55        &self.0
56    }
57}
58
59impl Display for Value {
60    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61        write!(f, "{}", self.0)
62    }
63}
64
65macro_rules! impl_partial_eq {
66    ($($ty:ty), *) => {
67        $(
68            impl PartialEq<$ty> for Value {
69                fn eq(&self, other: &$ty) -> bool {
70                    self.0 == *other as f64
71                }
72            }
73        )*
74    };
75    () => {
76
77    };
78}
79
80impl_partial_eq!(usize, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64);
81
82macro_rules! utilities {
83    ($($ty:ty),*) => {
84        $(
85            impl AddAssign<$ty> for Moving<$ty> {
86                fn add_assign(&mut self, other: $ty) {
87                    let _ = self.add(other);
88                }
89            }
90
91            impl PartialEq<$ty> for Moving<$ty> {
92                fn eq(&self, other: &$ty) -> bool {
93                    self.mean() == *other as f64
94                }
95            }
96
97            impl PartialOrd<$ty> for Moving<$ty> {
98                fn partial_cmp(&self, other: &$ty) -> Option<std::cmp::Ordering> {
99                    self.mean().partial_cmp(&(*other as f64))
100                }
101            }
102
103            impl PartialEq<Moving<$ty>> for $ty {
104                fn eq(&self, other: &Moving<$ty>) -> bool {
105                    *self as f64 == other.mean()
106                }
107            }
108
109            impl PartialOrd<Moving<$ty>> for $ty {
110                fn partial_cmp(&self, other: &Moving<$ty>) -> Option<std::cmp::Ordering> {
111                    (*self as f64).partial_cmp(&other.mean())
112                }
113            }
114        )*
115    };
116}
117
118macro_rules! partial_non {
119    ($($ty:ty), *) => {
120        $(
121        impl PartialEq<f32> for Moving<$ty> {
122            fn eq(&self, other: &f32) -> bool {
123                self.mean() == *other as f64
124            }
125        }
126
127        impl PartialEq<f64> for Moving<$ty> {
128            fn eq(&self, other: &f64) -> bool {
129                self.mean() == *other
130            }
131        }
132    )*
133
134    };
135}
136
137macro_rules! signed {
138    ($($ty:ty), *) => {
139        $(
140        impl Sign for $ty {
141            fn signed() -> bool {
142               true
143            }
144        }
145        )*
146    };
147}
148macro_rules! unsigned {
149    ($($ty:ty), *) => {
150    $(
151        impl Sign for $ty {
152            fn signed() -> bool {
153               false
154            }
155        }
156    )*
157    };
158}
159
160utilities!(usize, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128, f32, f64);
161partial_non!(usize, i8, i16, i32, i64, i128, u8, u16, u32, u64, u128);
162signed!(i8, i16, i32, i64, i128, f32, f64);
163unsigned!(usize, u8, u16, u32, u64, u128);
164
165pub trait Sign {
166    fn signed() -> bool;
167}
168
169#[derive(Debug, PartialEq, Eq, Clone, Copy)]
170/// Represents the possible errors that can occur in the `Moving` struct.
171pub enum MovingError {
172    /// Error indicating that a negative value was attempted to be added to an unsigned type.
173    NegativeValueToUnsignedType,
174
175    /// Error indicating that an overflow occurred during an operation.
176    /// Note: This is unlikely to occur with floating-point operations.
177    Overflow,
178
179    /// Error indicating that an underflow occurred during an operation.
180    Underflow,
181
182    /// Error indicating that the count of values has overflowed.
183    CountOverflow,
184
185    /// Error indicating that a value has reached or exceeded the specified threshold.
186    ///
187    /// This error is triggered when a value added to the `Moving` instance meets or exceeds
188    /// the threshold value specified during the creation of the instance. This can be used
189    /// to signal that a certain limit has been reached, which might require special handling
190    /// or termination of further processing.
191    ThresholdReached,
192}
193
194/// `Moving<T>` provides an ergonomic way to compute the moving average, mode, and count
195/// for a sequence of values of type `T`. It supports both signed and unsigned numeric types,
196/// and can enforce a threshold to stop accepting new values when the mean reaches or exceeds it.
197///
198/// Internally, it tracks:
199/// - The number of values added (`count`)
200/// - The current mean (`mean`)
201/// - The frequency of each value for mode calculation (`mode`)
202/// - An optional threshold (`threshold`)
203///
204/// # Type Parameters
205///
206/// - `T`: The numeric type of the values (e.g., `usize`, `i32`, `f64`).
207///
208/// # Examples
209///
210/// ```
211/// use moving_average::Moving;
212///
213/// let moving = Moving::new();
214/// moving.add(10);
215/// moving.add(10);
216/// moving.add(10);
217/// moving.add(20);
218/// assert_eq!(moving.mean(), 12.5);
219/// assert_eq!(moving.count(), 4);
220/// assert_eq!(moving.mode(), 10.0);
221/// ```
222#[derive(Debug, Default)]
223pub struct Moving<T> {
224    count: RefCell<usize>,
225    mean: RefCell<f64>,
226    mode: RefCell<HashMap<OrderedFloat<f64>, usize>>,
227    threshold: f64,
228    phantom: std::marker::PhantomData<T>,
229}
230
231impl<T> Moving<T>
232where
233    T: Sign + ToPrimitive,
234{
235    /// Creates a new [`Moving<T>`] instance with default values.
236    ///
237    /// # Returns
238    ///
239    /// A new instance of [`Moving<T>`].
240    /// Values can ge added to this instance to calculate the moving average.
241    pub fn new() -> Self {
242        Self {
243            count: RefCell::new(0),
244            mean: RefCell::new(0.0),
245            mode: RefCell::new(HashMap::new()),
246            threshold: f64::MAX,
247            phantom: PhantomData,
248        }
249    }
250
251    /// Creates a new [`Moving<T>`] instance with a specified threshold.
252    ///
253    /// This method initializes the `count` to 0, `mean` to 0.0, `is_error` to `false`,
254    /// and `threshold` to the provided value.
255    ///
256    /// # Parameters
257    ///
258    /// - `threshold`: The threshold value to be used for the new instance.
259    ///
260    /// # Returns
261    ///
262    /// A new instance of [`Moving<T>`] with the specified threshold.
263    /// Values can be added to this instance to calculate the moving average.
264    /// When values are greater than or equal to the threshold, the [`MovingResults::ThresholdReached`] variant is returned and no further values are added.
265    pub fn new_with_threshold(threshold: f64) -> Self {
266        Self {
267            count: RefCell::new(0),
268            mean: RefCell::new(0.0),
269            mode: RefCell::new(HashMap::new()),
270            threshold,
271            phantom: PhantomData,
272        }
273    }
274    /// Adds a value to the current statistical collection, updating the mean accordingly.
275    ///
276    /// This function converts the input value to an `f64` and then updates the mean of the collection
277    /// based on the new value.
278    ///
279    /// # Returns
280    /// If the mean is less than the threshold, the [`MovingResults::Value`] variant is returned with the new mean.
281    ///
282    /// # Panics
283    ///
284    /// Panics if the type `T` is unsigned and a negative value is attempted to be added. This is because
285    /// negative values are not allowed for unsigned types. If negative values are needed, it is recommended
286    /// to use signed types instead.
287    pub fn add_with_result(&self, value: T) -> Result<f64, MovingError> {
288        let value_f64: f64 = value.to_f64().unwrap();
289        if !T::signed() && value_f64 < 0.0 {
290            return Err(MovingError::NegativeValueToUnsignedType);
291        }
292        let mut count = self.count.borrow_mut();
293        let mut mean = self.mean.borrow_mut();
294        let mut mode = self.mode.borrow_mut();
295        mode.entry(OrderedFloat(value_f64))
296            .and_modify(|e| *e += 1)
297            .or_insert(1);
298
299        *count += 1;
300        *mean += (value_f64 - *mean) / *count as f64;
301
302        if *mean >= self.threshold {
303            return Err(MovingError::ThresholdReached);
304        }
305
306        Ok(*mean)
307    }
308
309    /// Adds a value to the current statistical collection, ignoring the result.
310    ///
311    /// This method calls the `add` method and ignores any errors that occur.
312    pub fn add(&self, value: T) {
313        let _ = self.add_with_result(value);
314    }
315
316    /// Returns the mean value of the moving average
317    pub fn mean(&self) -> f64 {
318        *self.mean.borrow()
319    }
320
321    /// Returns the statistical mode of the values added so far.
322    ///
323    /// The **mode** is the value that appears most frequently in the data set.
324    ///
325    /// # Behavior
326    ///
327    /// - If **no values** have been added, returns `0.0`.
328    /// - If **all values are unique** (no repeats), returns the current mean.
329    /// - If **one value** occurs more frequently than any other, returns that value as the mode.
330    /// - If **multiple values** are tied for the highest frequency (i.e., a multi-modal distribution),
331    ///   returns the value among the tied modes that is **closest to the mean**.
332    ///
333    /// # Examples
334    ///
335    /// ```
336    /// use moving_average::Moving;
337    /// let moving = Moving::new();
338    /// moving.add(10);
339    /// moving.add(20);
340    /// moving.add(10);
341    /// assert_eq!(moving.mode(), 10.0); // 10 appears most frequently
342    ///
343    /// let moving = Moving::new();
344    /// moving.add(1);
345    /// moving.add(2);
346    /// moving.add(3);
347    /// assert_eq!(moving.mode(), moving.mean()); // all unique, returns mean
348    ///
349    /// let moving = Moving::new();
350    /// moving.add(10);
351    /// moving.add(20);
352    /// moving.add(10);
353    /// moving.add(20);
354    /// // Ensure the mean is lowered
355    /// moving.add(1);
356    /// // Both 10 and 20 appear twice, closest to mean is returned
357    /// let mode = moving.mode();
358    /// assert_eq!(mode, 10.0); // 10 is closer to the mean than 20
359    /// ```
360    pub fn mode(&self) -> f64 {
361        let mode_map = self.mode.borrow();
362        if mode_map.is_empty() {
363            return 0.0;
364        }
365        // Find the maximum count
366        let max_count = match mode_map.values().max() {
367            Some(&max) if max > 1 => max,
368            // If all values are unique, return the mean
369            _ => return self.mean(),
370        };
371        // Collect all values with the maximum count
372        let modes: Vec<_> = mode_map
373            .iter()
374            .filter(|&(_, &count)| count == max_count)
375            .collect();
376        // If there's a tie return the mode closest to the mean
377        if modes.len() != 1 {
378            let mean = self.mean();
379            let closest = modes
380                .iter()
381                .min_by_key(|&&(value, _)| (value.0 - mean).abs() as i64)
382                .unwrap();
383            return closest.0.into_inner();
384        }
385        // Otherwise, return the mode value
386        modes[0].0.into_inner()
387    }
388
389    /// Returns the count of events added
390    ///
391    /// # Examples
392    ///
393    /// ```
394    /// use moving_average::Moving;
395    /// let moving = Moving::new();
396    /// moving.add(3);
397    /// assert_eq!(moving.count(), 1);
398    /// moving.add(3);
399    /// assert_eq!(moving.count(), 2);
400    /// assert_eq!(moving.mean(), 3.0);
401    /// ```
402    pub fn count(&self) -> usize {
403        *self.count.borrow()
404    }
405}
406
407impl<T> std::fmt::Display for Moving<T> {
408    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409        write!(f, "{}", self.mean.borrow())
410    }
411}
412
413impl<T> PartialEq for Moving<T> {
414    fn eq(&self, other: &Self) -> bool {
415        *self.mean.borrow() == *other.mean.borrow()
416    }
417}
418
419impl<T> PartialOrd for Moving<T> {
420    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
421        self.mean.borrow().partial_cmp(&*other.mean.borrow())
422    }
423}
424
425impl std::fmt::Display for MovingError {
426    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
427        write!(f, "{:?}", self)
428    }
429}
430
431#[cfg(test)]
432mod tests {
433    use crate::Moving;
434
435    #[test]
436    fn mode() {
437        let moving = Moving::new();
438        moving.add(10);
439        moving.add(20);
440        moving.add(10);
441        assert_eq!(moving.mode(), 10.0);
442    }
443
444    #[test]
445    fn big_mode() {
446        let moving = Moving::new();
447        for i in 0..10000 {
448            moving.add(i);
449        }
450        assert_eq!(moving.mode(), moving.mean());
451        moving.add(9999);
452        assert_eq!(moving.mode(), 9999.0);
453    }
454
455    #[test]
456    fn double_mode() {
457        let moving = Moving::new();
458        moving.add(10);
459        moving.add(20);
460        moving.add(10);
461        moving.add(20);
462        moving.add(1);
463        assert_eq!(moving.mode(), 10.0);
464        moving.add(3000);
465        assert_eq!(moving.mode(), 20.0);
466    }
467
468    #[test]
469    fn partial_order() {
470        let m1 = Moving::new();
471        let m2 = Moving::new();
472        m1.add(10);
473        m2.add(20);
474        assert!(m1 < m2);
475    }
476
477    #[test]
478    fn thresholds() {
479        let moving_threshold = Moving::new_with_threshold(10.0);
480        let result = moving_threshold.add_with_result(9);
481        assert_eq!(result.unwrap(), 9.0);
482        let result = moving_threshold.add_with_result(15);
483        assert!(result.is_err(), "{:?}", result);
484        assert_eq!(result.unwrap_err(), crate::MovingError::ThresholdReached);
485    }
486
487    #[test]
488    fn never_overflow() {
489        let moving_average: Moving<usize> = Moving::new();
490        let result = moving_average.add_with_result(usize::MAX);
491        assert!(result.is_ok());
492        assert_eq!(result.unwrap(), usize::MAX as f64);
493        let result = moving_average.add_with_result(usize::MAX);
494        assert!(result.is_ok());
495
496        assert_eq!(result.unwrap(), usize::MAX as f64);
497    }
498
499    #[test]
500    fn add_moving_average() {
501        let moving_average: Moving<usize> = Moving::new();
502        moving_average.add(10);
503        assert_eq!(moving_average, 10);
504        moving_average.add(20);
505        assert_eq!(moving_average, 15);
506    }
507
508    #[test]
509    fn float_moving_average() {
510        let moving_average: Moving<f32> = Moving::new();
511        moving_average.add(10.0);
512        moving_average.add(20.0);
513        assert_eq!(moving_average, 15.0);
514    }
515
516    #[test]
517    fn assign_add() {
518        let mut moving_average: Moving<usize> = Moving::new();
519        moving_average.add(10);
520        moving_average += 20;
521        assert_eq!(moving_average, 15);
522    }
523
524    #[test]
525    fn assign_add_float() {
526        let mut moving_average: Moving<f32> = Moving::new();
527        moving_average.add(10.0);
528        moving_average += 20.0;
529        assert_eq!(moving_average, 15.0);
530    }
531
532    #[test]
533    fn assign_add_i64() {
534        let mut moving_average: Moving<i64> = Moving::new();
535        moving_average.add(10);
536        moving_average += 20;
537        assert_eq!(moving_average, 15);
538    }
539    #[test]
540    fn default_works() {
541        let moving_average: Moving<usize> = Default::default();
542        assert_eq!(moving_average, 0);
543        let moving_average: Moving<f32> = Default::default();
544        assert_eq!(moving_average, 0.0);
545    }
546
547    #[test]
548    fn binary_operations() {
549        let moving_average: Moving<usize> = Moving::new();
550        moving_average.add(10);
551        moving_average.add(20);
552        assert!(moving_average < usize::MAX)
553    }
554
555    #[test]
556    fn binary_operations_float() {
557        let moving_average: Moving<f32> = Moving::new();
558        moving_average.add(10.0);
559        moving_average.add(20.0);
560        assert!(moving_average < f32::MAX)
561    }
562
563    #[test]
564    fn many_operations() {
565        let moving_average: Moving<_> = Moving::new();
566        for i in 0..1000 {
567            moving_average.add(i);
568        }
569        assert_eq!(moving_average, 999.0 / 2.0);
570    }
571}