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}