Deque
A vector is quick at its end and slow at its front. Pushing at the end writes into the next free slot; putting a value at position zero means shifting every other value along by one to make room — a thousand moves for a thousand values.
A Deque<T> — a double-ended queue, said "deck" — is quick at both ends. It remembers where its contents start as well as where they end, so adding at the front moves a marker, not the values. Its everyday use is a queue: work joins at the back and is served from the front, which is exactly the pair of operations a vector is bad at.
Four operations, two ends
flowchart LR
f["PushFront<br/>PopFront"] <--> front["front"]
front --- mid["…"]
mid --- back["back"]
back <--> b["PushBack<br/>PopBack"]| Operation | Vector | Deque |
|---|---|---|
| Add at the end | quick (Push) | quick (PushBack) |
| Remove at the end | quick | quick (PopBack) |
| Add at the front | slow: shifts every value | quick (PushFront) |
| Remove at the front | slow: shifts every value | quick (PopFront) |
| Read by position | quick (Get) | quick (Get) |
A deque is made like a vector, from an allocator:
var queue = Deque<int>(allocator);
A queue of tickets
Tickets join at the back, in the order they arrive. Each push may need more room, and so can fail; ? hands that failure to Main:
queue.PushBack(101)?;
queue.PushBack(102)?;
queue.PushBack(103)?;
An urgent ticket jumps the line by going in at the front:
queue.PushFront(900)?;
The queue is now 900 101 102 103. Serving takes from the front:
PrintLine("served {}", queue.PopFront() ?? -1);
PrintLine("served {}", queue.PopFront() ?? -1);
The urgent ticket, 900, is served first, then 101 — first in, first out. Both pops return an optional, int?, since an empty deque has nothing to give; ?? -1 supplies a stand-in that never prints here.
The newest arrival changes their mind and leaves from the back:
PrintLine("left {}", queue.PopBack() ?? -1);
That is 103, leaving only 102 waiting.
When the queue runs dry
After one more pop the deque is empty, and the next PopFront returns none. A match says so properly rather than printing a stand-in:
match queue.PopFront() {
ticket? => PrintLine("served {}", ticket),
none => PrintLine("empty nobody is waiting")
}
Looking without taking
Show prints the queue without changing it. It borrows the deque as &Deque<int> and reads each position with Get, which, like a vector's, returns an optional:
func Show(label: char8[..], queue: &Deque<int>) {
Print("{:8}", label);
for i in 0..queue.Length() {
Print(" {}", queue.Get(i) ?? -1);
}
PrintLine();
}
Position 0 is always the front, whichever end the values came in by.
Which to reach for
A vector when values only come and go at the end — a list you build and then read. A deque when both ends are in play — a queue, a window of the latest readings, a list of jobs where some jump the line. The deque's bookkeeping costs a little on every operation, which is why it is not simply the default.
The program
The whole lesson is one package in the Examples repository. Its comments explain every step.
// A vector is quick at its end and slow at its front: putting a value at position zero means
// shifting every other value along to make room.
//
// A `Deque<T>` — a double-ended queue, said "deck" — is quick at both ends. It remembers where its
// contents start as well as where they end, so adding at the front moves a marker, not the
// values. The everyday use is a queue: work joins at the back and is served from the front,
// which is exactly the pair of operations a vector is bad at.
import Allocator::{ Allocator, SystemAllocator };
import Collections::{ CollectionError, Deque };
import Io::{ Print, PrintLine };
func Show(label: char8[..], queue: &Deque<int>) {
Print("{:8}", label);
for i in 0..queue.Length() {
Print(" {}", queue.Get(i) ?? -1);
}
PrintLine();
}
func Main() -> ! CollectionError {
var system = SystemAllocator();
let allocator: Allocator = system;
var queue = Deque<int>(allocator);
// Tickets join at the back, in the order they arrive. Each push may need more room, and so
// can fail; `?` hands that failure to `Main`.
queue.PushBack(101)?;
queue.PushBack(102)?;
queue.PushBack(103)?;
Show("arrived", queue);
// An urgent ticket jumps the line by going in at the front.
queue.PushFront(900)?;
Show("urgent", queue);
// Serving takes from the front. Both pops return an optional, `int?`, since an empty deque
// has nothing to give.
PrintLine("served {}", queue.PopFront() ?? -1);
PrintLine("served {}", queue.PopFront() ?? -1);
// The newest arrival changes their mind and leaves from the back.
PrintLine("left {}", queue.PopBack() ?? -1);
Show("waiting", queue);
PrintLine("served {}", queue.PopFront() ?? -1);
match queue.PopFront() {
ticket? => PrintLine("served {}", ticket),
none => PrintLine("empty nobody is waiting")
}
// Which to reach for: a vector when values only come and go at the end, a deque when both
// ends are in play. The deque's bookkeeping costs a little per operation, which is why it
// is not simply the default.
}
Besides Io, its Rux.toml lists Allocator and Collections under [Dependencies].
Run it
cd Examples/Collections/Deque
rux run
arrived 101 102 103
urgent 900 101 102 103
served 900
served 101
left 103
waiting 102
served 102
empty nobody is waiting
Common mistakes
A pop may find nothing, so its type is
int?. let t: int = queue.PopFront(); fails with error: cannot assign 'int?' to 'int'. Unwrap it with ??, a match, or ?? return.PrintLine("{}", queue.PopFront()) fails with error: argument 2 to 'PrintLine' has type 'int?', but variadic parameter 'args' requires 'Display'. An optional cannot be printed as it is — decide what none should look like first.PushBack with PopBack is last in, first out — a stack, not a queue. A queue pairs PushBack with PopFront.Try it yourself
- Use the deque as a stack: push three values with
PushBackand pop them withPopBack. In which order do they come out? - Make a round robin: pop a ticket from the front, print it, and push it on the back, six times over three tickets.
- Keep only the latest three readings: push each new reading at the back, and pop from the front whenever the length passes three.