diff --git a/CHANGELOG.md b/CHANGELOG.md index cdfcd481..9e604881 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,10 @@ and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added + +- Added `OldestOrdered` iterator for `HistoryBuffer` + ## [v0.7.9] - 2021-12-16 ### Fixed diff --git a/src/histbuf.rs b/src/histbuf.rs index 7104e797..2bd52ec0 100644 --- a/src/histbuf.rs +++ b/src/histbuf.rs @@ -181,6 +181,38 @@ impl HistoryBuffer { pub fn as_slice(&self) -> &[T] { unsafe { slice::from_raw_parts(self.data.as_ptr() as *const _, self.len()) } } + + /// Returns an iterator for iterating over the buffer from oldest to newest. + /// + /// # Examples + /// + /// ``` + /// use heapless::HistoryBuffer; + /// + /// let mut buffer: HistoryBuffer = HistoryBuffer::new(); + /// buffer.extend([0, 0, 0, 1, 2, 3, 4, 5, 6]); + /// let expected = [1, 2, 3, 4, 5, 6]; + /// for (x, y) in buffer.oldest_ordered().zip(expected.iter()) { + /// assert_eq!(x, y) + /// } + /// + /// ``` + pub fn oldest_ordered<'a>(&'a self) -> OldestOrdered<'a, T, N> { + if self.filled { + OldestOrdered { + buf: self, + cur: self.write_at, + wrapped: false, + } + } else { + // special case: act like we wrapped already to handle empty buffer. + OldestOrdered { + buf: self, + cur: 0, + wrapped: true, + } + } + } } impl Extend for HistoryBuffer { @@ -247,9 +279,38 @@ impl Default for HistoryBuffer { } } +/// An iterator on the underlying buffer ordered from oldest data to newest +#[derive(Clone)] +pub struct OldestOrdered<'a, T, const N: usize> { + buf: &'a HistoryBuffer, + cur: usize, + wrapped: bool, +} + +impl<'a, T, const N: usize> Iterator for OldestOrdered<'a, T, N> { + type Item = &'a T; + + fn next(&mut self) -> Option<&'a T> { + if self.cur == self.buf.len() && self.buf.filled { + // roll-over + self.cur = 0; + self.wrapped = true; + } + + if self.cur == self.buf.write_at && self.wrapped { + return None; + } + + let item = &self.buf[self.cur]; + self.cur += 1; + Some(item) + } +} + #[cfg(test)] mod tests { use crate::HistoryBuffer; + use core::fmt::Debug; #[test] fn new() { @@ -314,4 +375,59 @@ mod tests { assert_eq!(x.as_slice(), [5, 2, 3, 4]); } + + #[test] + fn ordered() { + // test on an empty buffer + let buffer: HistoryBuffer = HistoryBuffer::new(); + let mut iter = buffer.oldest_ordered(); + assert_eq!(iter.next(), None); + assert_eq!(iter.next(), None); + + // test on a un-filled buffer + let mut buffer: HistoryBuffer = HistoryBuffer::new(); + buffer.extend([1, 2, 3]); + assert_eq!(buffer.len(), 3); + assert_eq_iter(buffer.oldest_ordered(), &[1, 2, 3]); + + // test on a filled buffer + let mut buffer: HistoryBuffer = HistoryBuffer::new(); + buffer.extend([0, 0, 0, 1, 2, 3, 4, 5, 6]); + assert_eq!(buffer.len(), 6); + assert_eq_iter(buffer.oldest_ordered(), &[1, 2, 3, 4, 5, 6]); + + // comprehensive test all cases + for n in 0..50 { + const N: usize = 7; + let mut buffer: HistoryBuffer = HistoryBuffer::new(); + buffer.extend(0..n); + assert_eq_iter( + buffer.oldest_ordered().copied(), + n.saturating_sub(N as u8)..n, + ); + } + } + + /// Compares two iterators item by item, making sure they stop at the same time. + fn assert_eq_iter( + a: impl IntoIterator, + b: impl IntoIterator, + ) { + let mut a = a.into_iter(); + let mut b = b.into_iter(); + + let mut i = 0; + loop { + let a_item = a.next(); + let b_item = b.next(); + + assert_eq!(a_item, b_item, "{}", i); + + i += 1; + + if b_item.is_none() { + break; + } + } + } } diff --git a/src/lib.rs b/src/lib.rs index fe976f10..a8b52a3f 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -76,7 +76,7 @@ pub use binary_heap::BinaryHeap; pub use deque::Deque; -pub use histbuf::HistoryBuffer; +pub use histbuf::{HistoryBuffer, OldestOrdered}; pub use indexmap::{Bucket, FnvIndexMap, IndexMap, Pos}; pub use indexset::{FnvIndexSet, IndexSet}; pub use linear_map::LinearMap;