//! Data structure to represent the "queue", which encompasses the current piece, the next //! previews, and the hold slot. The data structure attempts to remove ambiguity between //! queues that have the same two pieces immediately available, by automatically moving //! the front of the queue into the hold slot if the hold slot would be empty. #[derive(Clone, Eq, PartialEq, Debug)] pub struct Queue<'a, T: Copy> { pub hold: Option, pub next: &'a [T], } impl<'a, T: Copy> Queue<'a, T> { /// Constructs a new queue. pub fn new(mut hold: Option, mut next: &'a [T]) -> Self { if hold.is_none() && !next.is_empty() { hold = Some(next[0]); next = &next[1..]; } Self { hold, next } } /// Returns `true` if the queue is completely empty (i.e., `reachable()` would yield /// no values). pub fn is_empty(&self) -> bool { // HACK: its impossible for `next` to be non-empty and `hold` to be None, because // if `next` was non-empty then the `new` constructor would automatically move // some value into the hold slot. debug_assert!(self.hold.is_some() || self.next.is_empty()); self.hold.is_none() /* && self.next.is_empty() */ } /// Returns the number of total pieces in the queue, including the hold slot. This /// equals the number of times `remove` may be called before the queue becomes empty. pub fn len(&self) -> usize { self.hold.map_or(0, |_| 1) + self.next.len() } /// Yields the next pieces available to use from the queue. The returned iterator may /// be empty, and may yield at most 2 elements. pub fn reachable(&self) -> impl Iterator { let front = self.next.get(0).copied(); [front, self.hold].into_iter().flatten() } } impl<'a, T: Copy + Eq> Queue<'a, T> { /// Removes the given piece from the front of the queue, returning a new queue with /// that piece removed. /// /// Panics if the piece is not actually reachable from this queue. pub fn remove(&self, val: T) -> Self { assert!(!self.is_empty(), "trying to remove from empty queue"); if self.hold == Some(val) { Queue::new(None, self.next) } else { assert!(self.next[0] == val, "trying to remove unreachable item"); Queue::new(self.hold, &self.next[1..]) } } } #[cfg(test)] mod test { use super::*; use alloc::vec::Vec; use core::fmt::Debug; #[test] fn test_queue_new_eq() { assert_eq!( Queue::new(None, &["A", "B", "C"]), Queue::new(Some("A"), &["B", "C"]) ); assert_eq!(Queue::new(None, &["A"]), Queue::new(Some("A"), &[])); } #[test] fn test_queue_empty() { assert!(!Queue::new(None, &["A", "B", "C"]).is_empty()); assert!(!Queue::new(None, &["A"]).is_empty()); assert!(!Queue::new(Some("A"), &[]).is_empty()); assert!(Queue::new(None, &[] as &[&str]).is_empty()); } fn assert_reachable(q: &Queue, ts: impl IntoIterator) { let mut ts = ts.into_iter().collect::>(); for t in q.reachable() { let idx = ts .iter() .enumerate() .find_map(|(i, &u)| (t == u).then_some(i)); if let Some(idx) = idx { ts.remove(idx); } else { panic!("{t:?} found but not expected"); } } for t in ts { panic!("{t:?} expected but not found"); } } #[test] fn test_queue_reachable() { assert_reachable(&Queue::new(None, &[] as &[&str]), []); assert_reachable(&Queue::new(None, &["A", "B", "C"]), ["A", "B"]); assert_reachable(&Queue::new(Some("D"), &[]), ["D"]); assert_reachable(&Queue::new(Some("D"), &["A"]), ["A", "D"]); assert_reachable(&Queue::new(Some("D"), &["A", "B", "C"]), ["A", "D"]); } #[test] fn test_queue_remove() { assert_eq!( Queue::new(None, &["A", "B", "C"]).remove(&"A"), Queue::new(Some("B"), &["C"]) ); assert_eq!( Queue::new(Some("D"), &["A", "B", "C"]).remove(&"A"), Queue::new(Some("D"), &["B", "C"]) ); assert_eq!( Queue::new(None, &["A", "B", "C"]).remove(&"B"), Queue::new(None, &["A", "C"]) ); } #[test] #[should_panic] fn test_queue_bad_remove_1() { Queue::new(None, &["A", "B", "C"]).remove(&"C"); } #[test] #[should_panic] fn test_queue_bad_remove_2() { Queue::new(Some("D"), &["A", "B", "C"]).remove(&"B"); } }