Deques
September 6, 2011
We represent a deque as a pair containing two normal lists, the front of the list and the back of the list, with the back of the list in reverse order, with the invariant that if either the front or the back is null, the other one is either null or a singleton. First some simple definitions:
(define dq-null (cons (list) (list)))
(define (dq-null? xs) (equal? xs dq-null))
It is sometimes useful to test whether a deque has only a single element:
(define (dq-singleton? xs)
(or (and (null? (car xs)) (pair? (cdr xs)))
(and (pair? (car xs)) (null? (cdr xs)))))
The constructors at both ends check for a null list, which implies that the other list is a singleton and requires special handling:
(define (dq-cons x xs)
(if (null? (car xs)) (cons (list x) (cdr xs))
(if (null? (cdr xs)) (cons (list x) (car xs))
(cons (cons x (car xs)) (cdr xs)))))
(define (dq-snoc x xs)
(if (null? (cdr xs)) (cons (car xs) (list x))
(if (null? (car xs)) (cons (cdr xs) (list x))
(cons (car xs) (cons x (cdr xs))))))
Fetching either end of the deque is just a matter of taking the car of the appropriate element, being careful to properly handle the case of a singleton deque and signalling an error if the deque is null:
(define (dq-head xs)
(if (pair? (car xs)) (caar xs)
(if (pair? (cdr xs)) (cadr xs)
(error 'dq-head "null deque"))))
(define (dq-last xs)
(if (pair? (cdr xs)) (cadr xs)
(if (pair? (car xs)) (caar xs)
(error 'dq-last "null deque"))))
The only tricky functions are the two functions that delete one of the ends of the queue. When both lists have sufficient elements, it is simple to delete the requested element. But if the deletion would make the list null, we have to be careful. One approach would move a single element from one list to the other when the other list becomes null, but some sequences of inserts and deletes would make that thrash, giving a linear update time. The approach used below splits the deque in two when one of the lists becomes null, which gives amortized constant-time updates and deletes:
(define (dq-tail xs)
(if (null? (car xs))
(if (pair? (cdr xs)) dq-null
(error 'dq-tail "null deque"))
(if (pair? (cdar xs)) (cons (cdar xs) (cdr xs))
(call-with-values
(lambda ()
(split (quotient (length (cdr xs)) 2) (reverse (cdr xs))))
(lambda (f b) (cons f (reverse b)))))))
(define (dq-init xs)
(if (null? (cdr xs))
(if (pair? (car xs)) dq-null
(error 'dq-init "null deque"))
(if (pair? (cddr xs)) (cons (car xs) (cddr xs))
(call-with-values
(lambda ()
(split (quotient (length (car xs)) 2) (reverse (car xs))))
(lambda (f b) (cons f (reverse b)))))))
Although they weren’t required in the definition of the deque library, it is handy to have functions that convert between deques and standard lists, both for testing and in production use:
(define (list->dq xs)
(call-with-values
(lambda () (split (quotient (length xs) 2) xs))
(lambda (f b) (cons f (reverse b)))))
(define (dq->list xs) (append (car xs) (reverse (cdr xs))))
We use the split function shown below, which we will add to the Standard Prelude along with its cousin split-while.
(define (split n xs)
(let loop ((n n) (xs xs) (zs (list)))
(if (or (zero? n) (null? xs))
(values (reverse zs) xs)
(loop (- n 1) (cdr xs) (cons (car xs) zs)))))
You can run the program at http://programmingpraxis.codepad.org/ll2dgmTM, where you will also find a modest test suite.
In Racket:
Sometimes I wish I could delete my comments: my implementation doesn’t work at all (but soon I’ll be back :-) )
A ruby version …
It cheats a bit by using the ruby array and its assorted functions … but easy.
I’m practically just using normal haskell functions, wrapping them in a nice coat of Deque-ness! Kind of cheating, I know!
And of course I forgot the ‘add’ functions:
JavaScript & NodeJS:
An implementation in Racket using doubly linked lists.
With better amortisation: splitting a list in two when it is empty and accessed.
I put a purely functional version up on github the other day https://github.com/ijp/pfds/blob/master/deques.sls
Clojure: immutable, all operations in constant time thanks to clojure vector’s properties
A Python implementation. Keep in mind that I’m a new Python programmer, so this should not be considered an optimal implementation :)
class deque:
def __init__(self):
self._data = []
def add_front(self, i):
self._data.insert(0, i)
def add_back(self, i):
self._data.append(i)
def remove_front(self):
return self._data.pop(0)
def remove_back(self):
return self._data.pop()
def front(self):
return self._data[0]
def back(self):
return self._data[-1]
def is_empty(self):
return len(self._data) == 0
def __str__(self):
return self._data.__str__()
I can’t edit my post, so it would be nice if the previous post could be deleted. Here’s one that’s correctly formatted on pastbin.
source
C++ implementation