968 Deque
Tutorial
The Problem
Implement a double-ended queue (deque) that supports O(1) amortized push and pop at both front and back. Present two approaches: wrapping Rust's standard VecDeque (ring buffer internally) and a functional two-stack deque that mirrors OCaml's approach — two lists where the front list and reversed back list together represent the queue contents.
🎯 Learning Outcomes
VecDeque<T> from std::collections for a production-ready O(1) amortized deque(front_list, back_list) where pop_front consumes front, and when front is empty it reverses back into frontfront is empty on pop_front, reverse back and swap listsCode Example
#![allow(clippy::all)]
// 968: Double-Ended Queue (Deque)
// Approach 1: std VecDeque (built-in, O(1) amortized all operations)
// Approach 2: Functional two-stack deque (mirrors OCaml approach)
use std::collections::VecDeque;
// --- Approach 1: VecDeque (idiomatic Rust, ring buffer internally) ---
pub struct Deque<T> {
inner: VecDeque<T>,
}
impl<T> Deque<T> {
pub fn new() -> Self {
Deque {
inner: VecDeque::new(),
}
}
pub fn push_front(&mut self, x: T) {
self.inner.push_front(x);
}
pub fn push_back(&mut self, x: T) {
self.inner.push_back(x);
}
pub fn pop_front(&mut self) -> Option<T> {
self.inner.pop_front()
}
pub fn pop_back(&mut self) -> Option<T> {
self.inner.pop_back()
}
pub fn peek_front(&self) -> Option<&T> {
self.inner.front()
}
pub fn peek_back(&self) -> Option<&T> {
self.inner.back()
}
pub fn size(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl<T> Default for Deque<T> {
fn default() -> Self {
Self::new()
}
}
// --- Approach 2: Functional two-stack deque (mirrors OCaml) ---
#[derive(Clone, Debug)]
pub struct FunctionalDeque<T: Clone> {
front: Vec<T>, // front stack (top = front of deque)
back: Vec<T>, // back stack (top = back of deque)
}
impl<T: Clone + PartialEq> FunctionalDeque<T> {
pub fn new() -> Self {
FunctionalDeque {
front: vec![],
back: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.front.is_empty() && self.back.is_empty()
}
pub fn size(&self) -> usize {
self.front.len() + self.back.len()
}
fn balance(mut self) -> Self {
if self.front.is_empty() {
self.front = self.back.clone();
self.front.reverse();
self.back.clear();
}
self
}
pub fn push_front(mut self, x: T) -> Self {
self.front.push(x);
self.balance()
}
pub fn push_back(mut self, x: T) -> Self {
self.back.push(x);
self.balance()
}
pub fn pop_front(self) -> Option<(T, Self)> {
let mut d = self.balance();
if d.front.is_empty() {
None
} else {
let x = d.front.pop().unwrap();
Some((x, d.balance()))
}
}
pub fn peek_front(&self) -> Option<&T> {
self.front.last()
}
}
impl<T: Clone + PartialEq> Default for FunctionalDeque<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vecdeque_operations() {
let mut d: Deque<i32> = Deque::new();
assert!(d.is_empty());
d.push_back(1);
d.push_back(2);
d.push_back(3);
d.push_front(0);
assert_eq!(d.size(), 4);
assert_eq!(d.peek_front(), Some(&0));
assert_eq!(d.peek_back(), Some(&3));
assert_eq!(d.pop_front(), Some(0));
assert_eq!(d.pop_back(), Some(3));
assert_eq!(d.size(), 2);
}
#[test]
fn test_vecdeque_empty() {
let mut d: Deque<i32> = Deque::new();
assert_eq!(d.pop_front(), None);
assert_eq!(d.pop_back(), None);
assert_eq!(d.peek_front(), None);
}
#[test]
fn test_functional_deque() {
let d: FunctionalDeque<i32> = FunctionalDeque::new();
assert!(d.is_empty());
let d = d.push_back(1).push_back(2).push_back(3).push_front(0);
assert_eq!(d.size(), 4);
assert_eq!(d.peek_front(), Some(&0));
let (v, d) = d.pop_front().unwrap();
assert_eq!(v, 0);
assert_eq!(d.peek_front(), Some(&1));
}
#[test]
fn test_fifo_order() {
let mut d: Deque<i32> = Deque::new();
for i in 1..=5 {
d.push_back(i);
}
let mut out = vec![];
while let Some(v) = d.pop_front() {
out.push(v);
}
assert_eq!(out, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_lifo_order() {
let mut d: Deque<i32> = Deque::new();
for i in 1..=5 {
d.push_back(i);
}
let mut out = vec![];
while let Some(v) = d.pop_back() {
out.push(v);
}
assert_eq!(out, vec![5, 4, 3, 2, 1]);
}
}Key Differences
| Aspect | Rust | OCaml |
|---|---|---|
| Standard deque | VecDeque — ring buffer, O(1) | No stdlib deque; Queue is FIFO only |
| Functional variant | Two Vecs, mutable | Two lists, immutable |
| Reversal | drain(..).rev().collect() | List.rev |
| Persistence | Mutable — no history | Persistent — old versions accessible |
VecDeque is the right default. The two-stack approach illuminates the functional deque structure but has higher constant factors due to occasional full-list reversal.
OCaml Approach
(* Functional two-list deque *)
type 'a deque = { front: 'a list; back: 'a list }
let empty = { front = []; back = [] }
let push_back x d = { d with back = x :: d.back }
let push_front x d = { d with front = x :: d.front }
let pop_front d = match d.front with
| x :: rest -> Some (x, { d with front = rest })
| [] -> match List.rev d.back with
| [] -> None
| x :: rest -> Some (x, { front = rest; back = [] })
let pop_back d = match d.back with
| x :: rest -> Some (x, { d with back = rest })
| [] -> match List.rev d.front with
| [] -> None
| x :: rest -> Some (x, { back = rest; front = [] })
OCaml's functional deque is persistent — pop_front returns a new deque value rather than mutating. Structural sharing means push_back O(1) allocation. List reversal still occurs O(n) occasionally but amortizes to O(1).
Full Source
#![allow(clippy::all)]
// 968: Double-Ended Queue (Deque)
// Approach 1: std VecDeque (built-in, O(1) amortized all operations)
// Approach 2: Functional two-stack deque (mirrors OCaml approach)
use std::collections::VecDeque;
// --- Approach 1: VecDeque (idiomatic Rust, ring buffer internally) ---
pub struct Deque<T> {
inner: VecDeque<T>,
}
impl<T> Deque<T> {
pub fn new() -> Self {
Deque {
inner: VecDeque::new(),
}
}
pub fn push_front(&mut self, x: T) {
self.inner.push_front(x);
}
pub fn push_back(&mut self, x: T) {
self.inner.push_back(x);
}
pub fn pop_front(&mut self) -> Option<T> {
self.inner.pop_front()
}
pub fn pop_back(&mut self) -> Option<T> {
self.inner.pop_back()
}
pub fn peek_front(&self) -> Option<&T> {
self.inner.front()
}
pub fn peek_back(&self) -> Option<&T> {
self.inner.back()
}
pub fn size(&self) -> usize {
self.inner.len()
}
pub fn is_empty(&self) -> bool {
self.inner.is_empty()
}
}
impl<T> Default for Deque<T> {
fn default() -> Self {
Self::new()
}
}
// --- Approach 2: Functional two-stack deque (mirrors OCaml) ---
#[derive(Clone, Debug)]
pub struct FunctionalDeque<T: Clone> {
front: Vec<T>, // front stack (top = front of deque)
back: Vec<T>, // back stack (top = back of deque)
}
impl<T: Clone + PartialEq> FunctionalDeque<T> {
pub fn new() -> Self {
FunctionalDeque {
front: vec![],
back: vec![],
}
}
pub fn is_empty(&self) -> bool {
self.front.is_empty() && self.back.is_empty()
}
pub fn size(&self) -> usize {
self.front.len() + self.back.len()
}
fn balance(mut self) -> Self {
if self.front.is_empty() {
self.front = self.back.clone();
self.front.reverse();
self.back.clear();
}
self
}
pub fn push_front(mut self, x: T) -> Self {
self.front.push(x);
self.balance()
}
pub fn push_back(mut self, x: T) -> Self {
self.back.push(x);
self.balance()
}
pub fn pop_front(self) -> Option<(T, Self)> {
let mut d = self.balance();
if d.front.is_empty() {
None
} else {
let x = d.front.pop().unwrap();
Some((x, d.balance()))
}
}
pub fn peek_front(&self) -> Option<&T> {
self.front.last()
}
}
impl<T: Clone + PartialEq> Default for FunctionalDeque<T> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vecdeque_operations() {
let mut d: Deque<i32> = Deque::new();
assert!(d.is_empty());
d.push_back(1);
d.push_back(2);
d.push_back(3);
d.push_front(0);
assert_eq!(d.size(), 4);
assert_eq!(d.peek_front(), Some(&0));
assert_eq!(d.peek_back(), Some(&3));
assert_eq!(d.pop_front(), Some(0));
assert_eq!(d.pop_back(), Some(3));
assert_eq!(d.size(), 2);
}
#[test]
fn test_vecdeque_empty() {
let mut d: Deque<i32> = Deque::new();
assert_eq!(d.pop_front(), None);
assert_eq!(d.pop_back(), None);
assert_eq!(d.peek_front(), None);
}
#[test]
fn test_functional_deque() {
let d: FunctionalDeque<i32> = FunctionalDeque::new();
assert!(d.is_empty());
let d = d.push_back(1).push_back(2).push_back(3).push_front(0);
assert_eq!(d.size(), 4);
assert_eq!(d.peek_front(), Some(&0));
let (v, d) = d.pop_front().unwrap();
assert_eq!(v, 0);
assert_eq!(d.peek_front(), Some(&1));
}
#[test]
fn test_fifo_order() {
let mut d: Deque<i32> = Deque::new();
for i in 1..=5 {
d.push_back(i);
}
let mut out = vec![];
while let Some(v) = d.pop_front() {
out.push(v);
}
assert_eq!(out, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_lifo_order() {
let mut d: Deque<i32> = Deque::new();
for i in 1..=5 {
d.push_back(i);
}
let mut out = vec![];
while let Some(v) = d.pop_back() {
out.push(v);
}
assert_eq!(out, vec![5, 4, 3, 2, 1]);
}
}#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_vecdeque_operations() {
let mut d: Deque<i32> = Deque::new();
assert!(d.is_empty());
d.push_back(1);
d.push_back(2);
d.push_back(3);
d.push_front(0);
assert_eq!(d.size(), 4);
assert_eq!(d.peek_front(), Some(&0));
assert_eq!(d.peek_back(), Some(&3));
assert_eq!(d.pop_front(), Some(0));
assert_eq!(d.pop_back(), Some(3));
assert_eq!(d.size(), 2);
}
#[test]
fn test_vecdeque_empty() {
let mut d: Deque<i32> = Deque::new();
assert_eq!(d.pop_front(), None);
assert_eq!(d.pop_back(), None);
assert_eq!(d.peek_front(), None);
}
#[test]
fn test_functional_deque() {
let d: FunctionalDeque<i32> = FunctionalDeque::new();
assert!(d.is_empty());
let d = d.push_back(1).push_back(2).push_back(3).push_front(0);
assert_eq!(d.size(), 4);
assert_eq!(d.peek_front(), Some(&0));
let (v, d) = d.pop_front().unwrap();
assert_eq!(v, 0);
assert_eq!(d.peek_front(), Some(&1));
}
#[test]
fn test_fifo_order() {
let mut d: Deque<i32> = Deque::new();
for i in 1..=5 {
d.push_back(i);
}
let mut out = vec![];
while let Some(v) = d.pop_front() {
out.push(v);
}
assert_eq!(out, vec![1, 2, 3, 4, 5]);
}
#[test]
fn test_lifo_order() {
let mut d: Deque<i32> = Deque::new();
for i in 1..=5 {
d.push_back(i);
}
let mut out = vec![];
while let Some(v) = d.pop_back() {
out.push(v);
}
assert_eq!(out, vec![5, 4, 3, 2, 1]);
}
}
Deep Comparison
Deque — Comparison
Core Insight
The functional two-list deque (front, back) achieves amortized O(1) by reversing the back list into the front when the front empties. OCaml's immutable lists make this pattern natural; Rust can implement it with Vec but idiomatic Rust uses the built-in VecDeque (circular buffer, true O(1) at both ends).
OCaml Approach
{ front: 'a list; back: 'a list } — pure functional recordList.rev to rebalance when front is emptymatch d.front with [] -> ...{ d with front = x :: d.front } — record update syntaxRust Approach (two approaches)
VecDeque<T> — std ring buffer, O(1) push/pop both ends, preferredpush_front, push_back, pop_front, pop_back — direct methodsVec<T> as stack (.push = push to top, .pop = pop from top)mut self — consume and return new (value semantics mimicking immutability)self.front.reverse() for rebalancingComparison Table
| Aspect | OCaml | Rust |
|---|---|---|
| Idiomatic impl | Two-list { front; back } | VecDeque<T> (ring buffer) |
| Push front | x :: d.front (O(1)) | push_front(x) (O(1)) |
| Push back | x :: d.back (O(1)) | push_back(x) (O(1)) |
| Pop front | Pattern match on list | pop_front() → Option<T> |
| Rebalance | List.rev d.back | front.reverse() |
| Mutability | Immutable (persistent) | &mut self (mutable) |
| Value type | Record (immutable) | VecDeque<T> (mutable) or owned struct |
| Memory | GC'd list nodes | Contiguous ring buffer |
Exercises
to_vec(&self) -> Vec<T> for FuncDeque — collect elements front-to-back.VecDeque as a monotone deque.Rc<Vec<T>> for structural sharing (persistent Rust version).VecDeque vs FuncDeque for 1,000,000 alternating push_back/pop_front operations.rotate_left(n) on VecDeque — move the first n elements to the back.