Chapter 3
3.1 Exercise 3.1
3.2 Exercise 3.2
3.3 Exercise 3.3
3.4 Exercise 3.4
3.5 Exercise 3.5
3.6 Exercise 3.6
3.7 Exercise 3.7
3.8 Exercise 3.8
3.9 Exercise 3.9
3.10 Exercise 3.10
3.11 Exercise 3.11
3.12 Exercise 3.12
3.13 Exercise 3.13
3.14 Exercise 3.14
3.15 Exercise 3.15
3.16 Exercise 3.16
3.17 Exercise 3.17
3.18 Exercise 3.18
3.19 Exercise 3.19
3.20 Exercise 3.20
3.21 Exercise 3.21
3.22 Exercise 3.22
3.23 Exercise 3.23
3.24 Exercise 3.24
3.25 Exercise 3.25
3.26 Exercise 3.26
3.27 Exercise 3.27
3.28 Exercise 3.28
3.29 Exercise 3.29
3.30 Exercise 3.30
3.31 Exercise 3.31
3.32 Exercise 3.32
3.33 Exercise 3.33
3.34 Exercise 3.34
3.35 Exercise 3.35
3.36 Exercise 3.36
3.37 Exercise 3.37
3.38 Exercise 3.38
3.39 Exercise 3.39
3.40 Exercise 3.40
3.41 Exercise 3.41
3.42 Exercise 3.42
3.43 Exercise 3.43
3.44 Exercise 3.44
3.45 Exercise 3.45
3.46 Exercise 3.46
3.47 Exercise 3.47
3.48 Exercise 3.48
3.49 Exercise 3.49
3.50 Exercise 3.50
3.51 Exercise 3.51
3.52 Exercise 3.52
3.53 Exercise 3.53
3.54 Exercise 3.54
3.55 Exercise 3.55
3.56 Exercise 3.56
3.57 Exercise 3.57
3.58 Exercise 3.58
3.59 Exercise 3.59
3.60 Exercise 3.60
3.61 Exercise 3.61
3.62 Exercise 3.62
3.63 Exercise 3.63
3.64 Exercise 3.64
3.65 Exercise 3.65
3.66 Exercise 3.66
3.67 Exercise 3.67
3.68 Exercise 3.68
3.69 Exercise 3.69
3.70 Exercise 3.70
3.71 Exercise 3.71
3.72 Exercise 3.72
3.73 Exercise 3.73
3.74 Exercise 3.74
3.75 Exercise 3.75
3.76 Exercise 3.76
3.77 Exercise 3.77
3.78 Exercise 3.78
3.79 Exercise 3.79
3.80 Exercise 3.80
3.81 Exercise 3.81
3.82 Exercise 3.82

3.23 Exercise 3.23

In order to make a deque with O(1) insertion and deletion operations, we need to use a new data structure: A doubly-linked list. This allows us to add and remove from the deque at both ends in constant time. Otherwise, for example, it would be impossible to remove the item at the rear of the queue without traversing through the entire deque to get a pointer to the item before it.

I’ve chosen to represent a doubly-linked list as a list of lists. Each inner list represents a node, and has three values: The value of the node, the pointer to the next item in the list, and the pointer to the previous item in the list. Nodes on the ends of the deque will have nil pointers in one (or both) of these places.

I’ve chosen to implement a deque as a mutable object with pointers to the front and rear, like in the last example. The code is not especially difficult, but it is a bit longer than usual examples.

There are a few things to note about it, in particular the special cases that occur when adding nodes to an empty deque or removing the last node. However, there is a symmetry between the operations that occur at the front and rear ends of the deque.

(define (make-deque)
  (let ((front-ptr '()))
    (let ((rear-ptr front-ptr))
      (define (set-next-ptr from to)
        (set-car! (cdr from) to))
      (define (set-prev-ptr from to)
        (set-car! (cddr from) to))
      (define (empty-deque?)
        (null? front-ptr))
      (define (front-deque)
        (if (empty-deque?)
            (error "FRONT-DEQUE called on empty deque")
            (car front-ptr)))
      (define (rear-deque)
        (if (empty-deque?)
            (error "REAR-DEQUE called on empty deque")
            (car rear-ptr)))
      (define (front-insert-deque! item)
        (let ((new-item (list item front-ptr nil)))
          (if (empty-deque?)
              (begin
                (set! front-ptr new-item)
                (set! rear-ptr new-item))
              (begin
                (set-prev-ptr front-ptr new-item)
                (set! front-ptr new-item)))))
      (define (rear-insert-deque! item)
        (let ((new-item (list item nil rear-ptr)))
          (if (empty-deque?)
              (begin
                (set! front-ptr new-item)
                (set! rear-ptr new-item))
              (begin
                (set-next-ptr rear-ptr new-item)
                (set! rear-ptr new-item)))))
      (define (front-delete-deque!)
        (if (empty-deque?)
            (error "FRONT-DELETE-DEQUE! called on empty deque")
            (begin
              (set! front-ptr (cadr front-ptr))
              (if (null? front-ptr)
                  (set! rear-ptr front-ptr)
                  (set-prev-ptr front-ptr nil)))))
      (define (rear-delete-deque!)
        (if (empty-deque?)
            (error "REAR-DELETE-DEQUE! called on empty deque")
            (begin
              (set! rear-ptr (caddr rear-ptr))
              (if (null? rear-ptr)
                  (set! front-ptr rear-ptr)
                  (set-next-ptr rear-ptr nil)))))
      (define (dispatch m)
        (cond ((eq? m 'empty-deque?) empty-deque?)
              ((eq? m 'front-deque) front-deque)
              ((eq? m 'rear-deque) rear-deque)
              ((eq? m 'front-insert-deque!) front-insert-deque!)
              ((eq? m 'rear-insert-deque!) rear-insert-deque!)
              ((eq? m 'front-delete-deque!) front-delete-deque!)
              ((eq? m 'rear-delete-deque!) rear-delete-deque!)
              (else
               (error "unknown message" m))))
      dispatch)))

At this point, I think it should be mentioned that I dislike using the calling conventions for these mutable data objects. I prefer dealing with functions that take objects as parameters. However, it isn’t difficult to create those functions on top of an object like this.

(define (empty-deque? dq)
  ((dq 'empty-deque?)))
(define (front-deque dq)
  ((dq 'front-deque)))
(define (rear-deque dq)
  ((dq 'rear-deque)))
(define (front-insert-deque! dq item)
  ((dq 'front-insert-deque!) item))
(define (rear-insert-deque! dq item)
  ((dq 'rear-insert-deque!) item))
(define (front-delete-deque! dq)
  ((dq 'front-delete-deque!)))
(define (rear-delete-deque! dq)
  ((dq 'rear-delete-deque!)))