Deque endpoint invariant Created 2026-10-24 Updated 2026-11-05
In an endpoint-based double-ended queue represented by a doubly linked list, the first and last references are both null exactly when the double-ended queue is empty. Otherwise the first node has null predecessor, the last has null successor, and the finite successor chain is acyclic.
The two directions must agree: if node has successor , then has predecessor , and conversely for a nonnull predecessor. Thus following successors and following predecessors visit the same linked list nodes in reverse order.
Removing the only linked list node must clear both endpoints. Clearing only the endpoint used by that operation would leave one traversal apparently empty and the reverse traversal still reaching the removed node. The representation invariant makes that error visible before it affects later operations.
Double-ended queue Created 2026-10-20 Updated 2026-11-05
A double-ended queue supports insertion and removal at both ends. It combines the access patterns of a stack and a queue without promising fast arbitrary indexing. For example, appending
a,b at the right and then c at the left produces c,a,b; removing at the right returns b. A circular array or a doubly linked list can implement the abstraction, with different allocation and indexing costs. Python's deque provides approximately constant-time end operations, unlike repeatedly inserting at the start of an ordinary list.[ref]https://docs.python.org/3/library/collections.html#collections.deque. Python documentation, deque objects
Take the abstract state to be a finite sequence of integers, initially empty. Let denote the entry state, the empty sequence, and concatenation. A Scala trait presents the interface; its comments give the mathematical preconditions and postconditions:The double-ended queue has no abstract capacity limit in this first specification. Both removals require nonemptiness; the query leaves the state unchanged, an instance of command-query separation.
trait DoubleEndedQueue {
// Pre: true. Post: result == s0.isEmpty and s == s0.
def isEmpty: Boolean
// Pre: true. Post: s == [x] ++ s0.
def addLeft(x: Int): Unit
// Pre: s0.nonEmpty. Post: result == s0.head, s == s0.tail.
def getLeft(): Int
// Pre: true. Post: s == s0 ++ [x].
def addRight(x: Int): Unit
// Pre: s0.nonEmpty. Post: result == s0.last, s == s0.init.
def getRight(): Int
}A fixed-size array cannot implement unbounded successful insertion. Either specify a bounded double-ended queue with an extra not-full insertion precondition, or retain the unbounded interface and specify a possible full-capacity failure. Silently overwriting an endpoint would change the stated behavior. Here use the bounded contract, with a fixed positive capacity
MAX and abstract state . Both additions require ; the other postconditions remain unchanged. The class declaration in part (c) is interpreted as implementing this explicitly revised contract, rather than as a behavioral subtype of the unrestricted contract in part (a).The concrete state is an array
a of length MAX, a first-position index head, and an occupied-entry count size. Its representation invariant isThe abstraction function is for . Unoccupied slots are unconstrained. Storing size distinguishes empty and full states even though their two circular endpoint positions coincide. MAX=1 is valid. Using a circular buffer avoids shifting entries and makes every endpoint operation constant time.