//! `LimQ` is a queue (implemented as a wrapper around [`VecDeque`]) that //! supports an optional maximum number of elements constraint. //! //! Rather than supplying the traditional `push()` method to add elements to //! the queue, `LimQ` implements [`LimQ::try_push()`] and //! [`LimQ::force_push()`]. If no queue limit has been enabled, both of these //! act exactly like a traditional `push()` would. When a limit has been set, //! and reached, `try_push()` will fail, returning the input element. //! `force_push()` will forcibly add the new element while dropping the next //! element in line to be pulled off the queue. //! //! ``` //! use limq::LimQ; //! //! // Construct a queue with a maximum 2 element length limit //! let mut q: LimQ = LimQ::new(Some(2)); //! //! // Add elements to fill up to queue //! q.try_push(1).unwrap(); //! q.force_push(2); //! //! // Fail to add a new node //! assert_eq!(q.try_push(3), Err(3)); //! //! // Forcibly add node; expelling the oldest node //! q.force_push(4); //! //! // Remaining nodes should be 2 and 4 //! assert_eq!(q.pop(), Some(2)); //! assert_eq!(q.pop(), Some(4)); //! assert_eq!(q.pop(), None); //! ``` use std::collections::VecDeque; /// A queue with an optional number of maximum elements. pub struct LimQ { q: VecDeque, max_len: Option } impl LimQ { /// Create a new queue with an optional limit to the number of elements. /// /// Passning `None` to `max_len` will disable the limit. /// /// # Panics /// A zero-limit will cause a panic. #[must_use] pub fn new(max_len: Option) -> Self { assert!( !matches!(max_len, Some(0)), "A zero-length limit is forbidden" ); let q = max_len.map_or_else(VecDeque::new, VecDeque::with_capacity); Self { q, max_len } } /// Clear all nodes from queue. #[inline] pub fn clear(&mut self) { self.q.clear(); } /// Change the limit of the queue. /// /// Does not drain overflow elements if the new limit is lower than the /// current number of elements in queue. This can be done manually by /// calling [`LimQ::purge_overflow()`]. /// /// # Panics /// A zero-limit will cause a panic. pub fn set_max_len(&mut self, max_len: Option) { assert!( !matches!(max_len, Some(0)), "A zero-length limit is forbidden" ); self.max_len = max_len; } /// Return the maximum number of elements in queue. #[inline] #[must_use] pub const fn max_len(&self) -> Option { self.max_len } /// Return the current number of elements in the queue. #[inline] #[must_use] pub fn len(&self) -> usize { self.q.len() } /// Return a boolean indicating whether the queue is empty or not. #[inline] #[must_use] pub fn is_empty(&self) -> bool { self.q.is_empty() } /// Returns a boolean indicating whether the queue is full. /// /// If no limit has been configured, this will always return `false`. /// /// ``` /// use limq::LimQ; /// /// // Construct a queue with a maximum 1 element length limit /// let mut q: LimQ = LimQ::new(Some(1)); /// /// assert!(!q.is_full()); /// /// // Fill queue up /// q.try_push(1).unwrap(); /// /// assert!(q.is_full()); /// ``` #[inline] #[must_use] pub fn is_full(&self) -> bool { self .max_len .map_or(false, |max_len| self.q.len() >= max_len) } /// Drop elements that overflow the queue limit (if a limit has been set). /// /// ``` /// use limq::LimQ; /// /// // Construct a queue with a maximum 2 element length limit /// let mut q: LimQ = LimQ::new(Some(2)); /// /// // Fill queue up /// q.try_push(1).unwrap(); /// q.try_push(2).unwrap(); /// assert_eq!(q.len(), 2); /// /// // Lower limit to one element /// q.set_max_len(Some(1)); /// /// // Length will be unchanged /// assert_eq!(q.len(), 2); /// /// // .. until purged /// q.purge_overflow(); /// assert_eq!(q.len(), 1); /// ``` #[inline] pub fn purge_overflow(&mut self) { if let Some(max_len) = self.max_len { while self.q.len() > max_len { let _ = self.q.pop_front(); } } } /// Push a node onto queue, fail and return the node if the queue is full. /// /// For a queue with no configured limit, this is equivalent to /// [`VecDeque::push_back()`]. /// /// ``` /// use limq::LimQ; /// /// // Construct a queue with a maximum 1 element length limit /// let mut q: LimQ = LimQ::new(Some(1)); /// /// // Fill queue up /// q.try_push(42).unwrap(); /// /// // No room for new nodes -- try_push should return Err() containing /// // the node /// assert_eq!(q.try_push(11), Err(11)); /// ``` /// /// # Errors /// The node will be returned if the queue is full. #[inline] pub fn try_push(&mut self, n: T) -> Result<(), T> { if let Some(max_len) = self.max_len { if self.q.len() < max_len { self.q.push_back(n); Ok(()) } else { Err(n) } } else { // No limit -- just push self.q.push_back(n); Ok(()) } } /// Forcibly push a node onto queue. /// /// If the queue is full, remove nodes from the front (i.e. the oldest) until /// room becomes available for the new node. /// /// For a queue with no configured limit, this is equivalent to /// [`VecDeque::push_back()`]. /// /// ``` /// use limq::LimQ; /// /// // Construct a queue with a maximum 2 element length limit /// let mut q: LimQ = LimQ::new(Some(2)); /// /// // Fill queue up /// q.force_push(1); /// q.force_push(2); /// /// // Force push, ejecting the oldest element (containing '1') /// q.force_push(3); /// /// // The remaining nodes should be (in order) '2' and '3' /// assert_eq!(q.pop(), Some(2)); /// assert_eq!(q.pop(), Some(3)); /// ``` #[inline] pub fn force_push(&mut self, n: T) { // Remove node(s) before pushing new node on queue to avoid reallocation in // case if let Some(max_len) = self.max_len { // Make sure there's room for the new node while self.q.len() > (max_len - 1) { let _ = self.q.pop_front(); } } self.q.push_back(n); } /// Attempt to push node onto queue so it will be the next in line. /// /// Meant to be used to return an element to the queue that was just taken /// off it, but is currently not available to be processed. /// /// ``` /// use limq::LimQ; /// /// // Construct a queue with a maximum 2 element length limit /// let mut q: LimQ = LimQ::new(Some(2)); /// /// // Fill queue up /// q.force_push(1); /// q.force_push(2); /// /// // Take node off queue /// let n = q.pop().unwrap(); /// assert_eq!(n, 1); /// assert_eq!(q.len(), 1); /// /// // Return node to queue /// q.try_return(n); /// assert_eq!(q.len(), 2); /// /// // Should be the same node again /// let n = q.pop().unwrap(); /// assert_eq!(n, 1); /// ``` /// /// # Errors /// If the queue is full, return the node back to caller. #[inline] pub fn try_return(&mut self, n: T) -> Result<(), T> { if let Some(max_len) = self.max_len { if self.q.len() > (max_len - 1) { return Err(n); } } self.q.push_front(n); Ok(()) } /// Take node off queue. /// /// Returns `None` if the queue is empty. #[inline] pub fn pop(&mut self) -> Option { self.q.pop_front() } } #[cfg(test)] mod tests { use super::LimQ; #[test] #[should_panic(expected = "A zero-length limit is forbidden")] fn zero_len() { let _q: LimQ<()> = LimQ::new(Some(0)); } #[test] #[should_panic(expected = "A zero-length limit is forbidden")] fn set_zero_len() { let mut q: LimQ<()> = LimQ::new(Some(11)); q.set_max_len(Some(0)); } #[test] fn try_exceed() { let mut q: LimQ = LimQ::new(Some(1)); q.try_push(42).unwrap(); assert_eq!(q.len(), 1); let Err(e) = q.try_push(11) else { panic!("Unexpectedly not failure"); }; assert_eq!(e, 11); assert_eq!(q.pop(), Some(42)); assert_eq!(q.len(), 0); assert_eq!(q.pop(), None); } #[test] fn force_on_full() { let mut q: LimQ = LimQ::new(Some(1)); q.force_push(42); q.force_push(11); assert_eq!(q.pop(), Some(11)); } } // vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :