running_buffer 0.1.1

data types for keeping track of changing values, allowing analysis in trends and histories.
Documentation
use crate::circular_buffer::SerdeCircularBuffer;
use core::clone::Clone;
use core::default::Default;
use core::ops::Add;

/// A struct to keep a record of a changing variable:
/// Keeps `N_MOST_RECENT` exact previous values.
/// For older entries, keeps a total as `CUM`, which together with the total amount of tests can be
/// used to reconstruct an average.
/// The `BUF` type is the type that is kept inside a buffer; it has multiple instances, so it's
/// worth making it a little smaller, such as u16
/// The `CUM` type contains the sum of all of them, so you might want to have a bigger type, such
/// as u64.
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Debug)]
pub struct History<T, CUM, const N_MOST_RECENT: usize>
where
    T: Default + Clone,
{
    /// Circular buffer with the most recent items.
    /// Back is most recent, front is oldest.
    pub recent: SerdeCircularBuffer<N_MOST_RECENT, T>,

    /// Sum of all history values together. Used to get an average value.
    /// Already includes the entries in most_recent.
    /// Larger type to avoid overflows.
    total_historic: CUM,

    // This needs to be saved in here, because speed can be checked unsupervised (keylogging),
    // while errors cannot.
    /// How many numbers have been summed together to get the total_historic. Used to get an
    /// average value.
    total_tests: u32,
}

impl<T, CUM, const N_MOST_RECENT: usize> Default for History<T, CUM, N_MOST_RECENT>
where
    T: Default + Clone,
    CUM: Default,
{
    fn default() -> Self {
        Self {
            recent: Default::default(),
            total_historic: Default::default(),
            total_tests: Default::default(),
        }
    }
}

impl<T, CUM, const N_MOST_RECENT: usize> History<T, CUM, N_MOST_RECENT>
where
    CUM: Clone + Add<Output = CUM>,
    T: From<CUM> + Default + Clone,
{
    /// Adds new trial, updating the total.
    /// For types that implement converting from the cumulative type → buffer type.
    pub fn push_in_cum_type(&mut self, val: CUM) {
        let as_buf = T::from(val.clone());
        self.recent.push_back(as_buf);
        self.total_tests += 1;
        let prev_sum = self.total_historic.clone();
        self.total_historic = prev_sum + val;
    }
}

impl<T, CUM, const N_MOST_RECENT: usize> History<T, CUM, N_MOST_RECENT>
where
    CUM: Clone + Add<Output = CUM> + From<T>,
    T: Clone + Default,
{
    /// Adds new entry, updating the total.
    /// For types that implement converting from the buffer → cumulative type.
    pub fn push(&mut self, val: T) {
        let as_cum = CUM::from(val.clone());
        self.recent.push_back(val);
        self.total_tests += 1;
        let prev_sum = self.total_historic.clone();
        self.total_historic = prev_sum + as_cum;
    }

    /// Consuming adder for [`History::push`]
    pub fn pushed(mut self, val: T) -> Self {
        self.push(val);
        self
    }
}

// impl<T, const N_MOST_RECENT: usize> HistoricScore<T, T, N_MOST_RECENT>
// where
//     T: std::clone::Clone + std::ops::Add<Output = T>,
// {
//     /// Adding a new trial for types
//     /// For if the buffer type and cumulative type are the same.
//     pub fn add_new_trial(&mut self, new_trial: T) {
//         self.most_recent.add_new(new_trial.clone());
//         self.total_tests += 1;
//         let prev_sum = self.total_historic.clone();
//         self.total_historic = prev_sum + new_trial;
//     }
// }

impl<T, CUM, const N: usize> History<T, CUM, N>
where
    CUM: Default,
    T: Default + Clone,
{
    pub fn new() -> History<T, CUM, N> {
        History {
            recent: Default::default(),
            total_historic: Default::default(),
            total_tests: 0,
        }
    }
}

impl<T, CUM, const N: usize> History<T, CUM, N>
where
    CUM: Clone + Add<Output = CUM>,
    T: Default + Clone,
{
    pub fn extend(&mut self, rhs: History<T, CUM, N>) {
        self.total_historic = self.total_historic.clone() + rhs.total_historic.clone();
        self.total_tests += rhs.total_tests;

        // Actual implementation of extend is not very good. Might see if I can make a PR.
        // see https://docs.rs/circular-buffer/1.1.0/src/circular_buffer/lib.rs.html#2094-2102
        self.recent.0.extend(rhs.recent.0);
    }

    /// Considers the RHS to be newer, and adds all of them to the LHS.
    pub fn extended(mut self, rhs: History<T, CUM, N>) -> History<T, CUM, N> {
        self.extend(rhs);
        self
    }

    #[inline]
    pub fn most_recent(&self) -> Option<&T> {
        self.recent.nth_back(0)
    }

    #[inline]
    pub fn previous(&self) -> Option<&T> {
        self.recent.nth_back(1)
    }
    #[inline]
    pub fn n_ago(&self, n: usize) -> Option<&T> {
        self.recent.nth_back(n)
    }
}

#[cfg(test)]
mod test {
    use super::History;

    #[test]
    fn push() {
        let mut s = History::<u32, u64, 2>::new();
        assert_eq!(s.most_recent(), None);
        assert_eq!(s.total_tests, 0);
        assert_eq!(s.total_historic, 0);

        s.push(100);
        assert_eq!(s.most_recent().unwrap(), &100);
        assert_eq!(s.total_tests, 1);
        assert_eq!(s.total_historic, 100);

        s.push(101);
        assert_eq!(s.most_recent().unwrap(), &101);
        assert_eq!(s.previous().unwrap(), &100);
        assert_eq!(s.total_tests, 2);
        assert_eq!(s.total_historic, 201);

        // Only keeps 2 most recent, other only as cumulative.
        s.push(102);
        assert_eq!(s.most_recent().unwrap(), &102);
        assert_eq!(s.previous().unwrap(), &101);
        assert_eq!(s.n_ago(2), None);
        assert_eq!(s.total_tests, 3);
        assert_eq!(s.total_historic, 303);
    }

    #[test]
    fn joined() {
        // Test where the total amount of things does still not fill up the complete buffer.
        let older = History::<u32, u64, 6>::new()
            .pushed(100)
            .pushed(101)
            .pushed(102);
        let newer = History::<u32, u64, 6>::new().pushed(103).pushed(104);
        let appended = older.extended(newer);
        assert_eq!(appended.n_ago(0).unwrap(), &104);
        assert_eq!(appended.n_ago(1).unwrap(), &103);
        assert_eq!(appended.n_ago(2).unwrap(), &102);
        assert_eq!(appended.n_ago(3).unwrap(), &101);
        assert_eq!(appended.n_ago(4).unwrap(), &100);
        assert_eq!(appended.n_ago(5), None);

        // Test where the total amount of things fills up the recent buffer.
        let older = History::<u32, u64, 3>::new()
            .pushed(100)
            .pushed(101)
            .pushed(102);
        let older_cum = older.total_historic;
        let older_tests = older.total_tests;
        let newer = History::<u32, u64, 3>::new().pushed(103).pushed(104);
        let newer_cum = newer.total_historic;
        let newer_tests = newer.total_tests;

        let appended = older.extended(newer);
        assert_eq!(appended.n_ago(0).unwrap(), &104);
        assert_eq!(appended.n_ago(1).unwrap(), &103);
        assert_eq!(appended.n_ago(2).unwrap(), &102);
        assert_eq!(appended.total_historic, older_cum + newer_cum);
        assert_eq!(appended.total_tests, older_tests + newer_tests);
    }
}