limq 0.1.0

Queue with optional maximum number of elements constraint
Documentation
//! _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<u32> = 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<T> {
  q: VecDeque<T>,
  max_len: Option<usize>
}

impl<T> LimQ<T> {
  /// 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.
  pub fn new(max_len: Option<usize>) -> Self {
    assert!(!matches!(max_len, Some(0)));

    let q = if let Some(max_len) = max_len {
      VecDeque::with_capacity(max_len)
    } else {
      VecDeque::new()
    };
    Self { max_len, q }
  }

  /// 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<usize>) {
    assert!(!matches!(max_len, Some(0)));
    self.max_len = max_len;
  }

  /// Return the maximum number of elements in queue.
  #[inline]
  pub fn max_len(&self) -> Option<usize> {
    self.max_len
  }

  /// Return the current number of elements in the queue.
  #[inline]
  pub fn len(&self) -> usize {
    self.q.len()
  }

  /// Return a boolean indicating whether the queue is empty or not.
  #[inline]
  pub fn is_empty(&self) -> bool {
    self.q.is_empty()
  }

  /// 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<u32> = 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<u32> = 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));
  /// ```
  #[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<u32> = 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);
  }

  /// Take node off queue.
  ///
  /// Returns `None` if the queue is empty.
  #[inline]
  pub fn pop(&mut self) -> Option<T> {
    self.q.pop_front()
  }
}


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

  #[test]
  #[should_panic]
  fn zero_len() {
    let _q: LimQ<()> = LimQ::new(Some(0));
  }

  #[test]
  fn try_exceed() {
    let mut q: LimQ<u32> = 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<u32> = 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 :