dsa_rust/sequences/queues/
vec_circ_queue.rs

1/*! A circular Vec-based queue
2
3# About
4This simple, safe, Vec-based circular queue is mostly just a fun experiment, but can be used for situations in which you need a fixed-sized buffer with FIFO logic. The circular queue can be used to provide a solution to the [Josephus problem](https://en.wikipedia.org/wiki/Josephus_problem).
5
6# Design
7Rust takes its types (and its type safety) seriously. Array's must have a known size at compile time, forcing any array-backed structure's size hard coded. `[T; N]`
8
9This example illustrates the circular queue logic. The example provides a queue postcondition for enqueue/dequeue operations to illustrate the state of the list after each operation. Remember that `enqueue()` adds to the _back_, and `dequeue()` removes from the _front_ of the queue.
10```rust
11
12   use dsa_rust::sequences::queues::vec_circ_queue::CircularQueue;
13
14   // The queue is a fixed size
15   let mut q: CircularQueue<char> = CircularQueue::new(3);
16
17   // enqueue/dequeue return Result because adding/removing on a
18   // full/empty queue is an error
19   // Postcondition
20   //
21   //  <-> a <-> b <-> c <->
22   //      ^           ^
23   //   front        back
24   q.enqueue('a');
25   q.enqueue('b');
26   q.enqueue('c');
27
28   // The queue is at capacity, and the queue hasn't shifted
29   assert_eq!(q.peek().unwrap(), &'a');
30   assert_eq!(q.size(), q.capacity());
31   assert_eq!(q.front(), 0);
32   assert_eq!(q.back(), 2);
33
34   // The queue cannot take additional elements
35   assert!(q.enqueue_with_check('d').is_err());
36
37   // Remove the head node and check changes:
38   // Postcondition:
39   // - There is only one item in the queue
40   // - The front and back of the queue point to the same index
41   // Postcondition
42   //
43   //  <-> None <-> b <-> c <->
44   //               ^     ^
45   //            front  back
46   assert_eq!(q.dequeue().unwrap(), 'a');
47
48   // Postcondition
49   //
50   //  <-> None <-> None <-> c <->
51   //                        ^
52   //                   front/back
53   assert_eq!(q.dequeue().unwrap(), 'b'); // queue: 0:None, 1:None, 2:c (fr/ba)
54   assert_eq!(2, q.capacity() - q.size()); // Remaining capacity
55   assert_eq!(q.front(), 2);
56   assert_eq!(q.back(), 2);
57   assert_eq!(q.size(), 1);
58
59   // Adding new items wraps the queue, hence circular queue
60   // Postcondition
61   //
62   //  <-> d <-> None <-> c <->
63   //      ^              ^
64   //    back           front
65   q.enqueue('d');
66
67   // Postcondition
68   //
69   //  <-> d <-> e <-> c <->
70   //            ^     ^
71   //          back  front
72   q.enqueue('e');
73   assert_eq!(q.front(), 2);
74   assert_eq!(q.back(), 1);
75   assert_eq!(q.size(), 3);
76
77   // Removes two more elements and checks that there is just one element left
78   // Postcondition
79   //
80   //  <-> d <-> e <-> None <->
81   //      ^     ^
82   //    front  back
83   assert_eq!(q.dequeue().unwrap(), 'c');
84
85   // Postcondition
86   //
87   //  <-> None <-> e <-> None <->
88   //               ^
89   //          front/back
90   assert_eq!(q.dequeue().unwrap(), 'd');
91   assert_eq!(2, q.capacity() - q.size()); // Remaining capacity
92   assert_eq!(q.front(), 1);
93   assert_eq!(q.back(), 1);
94   assert_eq!(q.size(), 1);
95   assert_eq!(q.peek().unwrap(), &'e');
96```
97*/
98
99/// # About
100/// All functions operate in `O(1)` time unless otherwise noted.
101///
102/// See the [module-level documentation](`crate::lists::queues::vec_circ_queue`) for more information.
103#[derive(Debug)]
104pub struct CircularQueue<T> {
105    data: Vec<Option<T>>, // Store elements as `Option` to allow reusing slots
106    front: usize,
107    back: usize,
108    size: usize,
109    capacity: usize,
110}
111impl<T> CircularQueue<T> {
112    /// Creates a queue that contains (at least) `capacity` number of elements,
113    /// and initializes all positions to `None` in `O(n)` time.
114    pub fn new(capacity: usize) -> CircularQueue<T> {
115        let mut data = Vec::with_capacity(capacity);
116        // Fills the vec with None
117        //data.resize_with(capacity, Default::default); // Clean and generic
118        //data.resize_with(capacity, || None); // More explicit and slightly more efficient
119        // Somehow the fastest initializiation option
120        for _ in 0..capacity {
121            data.push(None)
122        }
123        CircularQueue {
124            data,
125            front: 0,
126            back: 0,
127            size: 0,
128            capacity,
129        }
130    }
131
132    /// Returns the index representing the front of the queue.
133    pub fn front(&self) -> usize {
134        self.front
135    }
136
137    /// Returns the index representing the back of the queue.
138    pub fn back(&self) -> usize {
139        self.back
140    }
141
142    /// Returns the number of elements in the queue.
143    pub fn size(&self) -> usize {
144        self.size
145    }
146
147    /// Returns the total capacity of the queue.
148    ///
149    /// This is the maximum number of elements the queue can hold,
150    /// determined at instantiation. It does not reflect the number of
151    /// currently available slots. To find the number of free slots,
152    /// subtract the current size from the capacity.
153    pub fn capacity(&self) -> usize {
154        self.capacity
155    }
156
157    /// Returns a reference to the front of the queue.
158    pub fn peek(&self) -> Option<&T> {
159        self.data[self.front].as_ref()
160    }
161
162    /// Adds an element to the back of the queue. This operation has no size or
163    /// capacity checks for efficiency and performance, may overwrite existing
164    /// slots. Use the `enqueue_with_check()` operation to prevent data overwrites
165    /// at the slight performance cost of an additional runtime check.
166    pub fn enqueue(&mut self, item: T) {
167        // Calculates the next available positionm, writes to it, and increases size
168        self.back = (self.front + self.size) % self.capacity;
169        self.data[self.back] = Some(item);
170        self.size += 1;
171    }
172
173    /// Adds an element to the back of the queue. If the size is equal to
174    /// the queue's capacity, this function performs an _O(n)_ grow operation.
175    pub fn enqueue_dyn(&mut self, item: T) {
176        // Check size + 1 < capacity
177
178        // Grow the underlying Vec if necessary
179
180        // Reset:
181        // - capacity
182        // - head index
183        // - tail index
184
185        // Re-linearization operation
186        //for i in 0..self.size {
187        //    let idx = (self.front + i) % old_cap;
188        //    new_data.push(self.data[idx].take());
189        //}
190    }
191
192    /// Adds an element to the back of the queue. This operation checks that
193    /// there is sufficient queue capacity to add an element and errors if you
194    /// attempt to add more elements than the queue's capacity. The
195    /// `enqueue()` operation is slightly more performanant but does not
196    /// automatically prevent overwrites.
197    pub fn enqueue_with_check(&mut self, item: T) -> Result<(), &str> {
198        // Ensures that the queue cannot take more elements than its capacity
199        if self.size == self.capacity {
200            return Err("Queue is full");
201        }
202        // Calculates the next available positionm, writes to it, and increases size
203        self.back = (self.front + self.size) % self.capacity;
204        self.data[self.back] = Some(item);
205        self.size += 1;
206        Ok(())
207    }
208
209    /// Removes and returns the front element of the queue, if an element exists.
210    pub fn dequeue(&mut self) -> Option<T> {
211        // Checks if queue is empty and returns proper None
212        if self.size == 0 {
213            return None;
214        }
215        // Otherwise take() the value from the front, leaving None in its place
216        let item = self.data[self.front].take();
217        // Properly advances the front index with wrapping
218        self.front = (self.front + 1) % self.capacity;
219        self.size -= 1;
220        item
221    }
222}
223
224/** Illustrates that the for loop is the most efficient way to initialize an
225array with None values:
226
227100x
228Default: 2.803µs
229None: 2.505µs
230For: 2.193µs
231
23210000x
233Default: 212.307µs
234None: 205.76µs
235For: 178.989µs
236
237100000000x
238Default: 2.285708255s
239None: 2.203727308s
240For: 1.947812524s
241
242*/
243//#[allow(clippy::type_complexity)]
244//fn _empirical_test() {
245//    use std::time::{Duration, Instant};
246//
247//    let allocations = [100, 10_000, 100_000_000];
248//    let runs = 100;
249//    // TODO: Clippy isn't happy about this
250//    let methods: Vec<(&str, fn(usize) -> CircularQueue<char>)> = vec![(
251//        "For",
252//        CircularQueue::new as fn(usize) -> CircularQueue<char>,
253//    )];
254//
255//    for &n in &allocations {
256//        println!("\n{}x", n);
257//
258//        for &(method_name, method) in &methods {
259//            let mut avg = Duration::new(0, 0);
260//            for _ in 0..runs {
261//                let start_time = Instant::now();
262//                let _t: CircularQueue<char> = method(n);
263//                let duration = start_time.elapsed();
264//                avg += duration;
265//            }
266//            println!("{}: {:?}", method_name, avg / runs);
267//        }
268//    }
269//}
270// To avoid Clippy warning suppression:
271pub fn empirical_test() {
272    use std::time::{Duration, Instant};
273
274    let allocations = [100, 10_000, 100_000_000];
275    let runs = 100;
276
277    // Use closures to avoid function pointer type
278    let methods = [("For", |n| CircularQueue::<char>::new(n))];
279
280    for &n in &allocations {
281        println!("\n{n}x");
282
283        for &(name, constructor) in &methods {
284            let total_duration = (0..runs)
285                .map(|_| {
286                    let start = Instant::now();
287                    let _queue = constructor(n);
288                    start.elapsed()
289                })
290                .sum::<Duration>();
291
292            let avg = total_duration / runs;
293            println!("{name}: {avg:?}");
294        }
295    }
296}
297
298#[test]
299fn first() {
300    let mut queue = CircularQueue::<usize>::new(3);
301
302    queue.enqueue(1);
303    queue.enqueue(2);
304    queue.enqueue(3);
305    queue.enqueue(4);
306    queue.enqueue(5);
307    queue.enqueue(6);
308    queue.enqueue(7);
309
310    println!("Circular queue: {queue:#?}");
311    println!("Vec len: {}", queue.data.len());
312    println!("Vec len: {}", queue.data.len());
313}
314
315pub fn example() {
316    //TODO
317}