Next: , Previous: , Up: streams examples   [Index]


1.15.14.5 Reducing two passes to one

The final example is a lazy dictionary, where definitions and uses may occur in any order; in particular, uses may precede their corresponding definitions. This is a common problem.

Many programming languages allow procedures to be used before they are defined. Macro processors must collect definitions and emit uses of text in order. An assembler needs to know the address that a linker will subsequently give to variables. The usual method is to make two passes over the data, collecting the definitions on the first pass and emitting the uses on the second pass. But Chris Reade shows how streams allow the dictionary to be built lazily, so that only a single pass is needed. Consider a stream of requests:

(define requests
  (stream
    '(get 3)
    '(put 1 "a")    ; use follows definition
    '(put 3 "c")    ; use precedes definition
    '(get 1)
    '(get 2)
    '(put 2 "b")    ; use precedes definition
    '(put 4 "d")))  ; unused definition

We want a procedure that will display cab, which is the result of (get 3), (get 1), and (get 2), in order. We first separate the request stream into gets and puts:

(define (get? obj) (eq? (car obj) 'get))

(define-stream (gets strm)
  (stream-map cadr (stream-filter get? strm)))

(define-stream (puts strm)
  (stream-map cdr  (stream-remove get? strm)))

Now, run-dict inserts each element of the puts stream into a lazy dictionary, represented as a stream of key/value pairs (an association stream), then looks up each element of the gets stream with stream-assoc:

(define-stream (run-dict requests)
  (let ([dict (build-dict (puts requests))])
    (stream-map (rsec stream-assoc dict)
      (gets requests))))

(define (stream-assoc key dict)
    (cond [(stream-null? dict) #f]
          [(equal? key (car (stream-car dict)))
           (stream-car dict)]
          [else (stream-assoc key
                  (stream-cdr dict))]))

dict is created in the let, but nothing is initially added to it. Each time stream-assoc performs a lookup, enough of dict is built to satisfy the lookup, but no more. We are assuming that each item is defined once and only once. All that is left is to define the procedure that inserts new items into the dictionary, lazily:

(define-stream (build-dict puts)
  (if (stream-null? puts)
      stream-null
    (stream-cons
      (stream-car puts)
      (build-dict (stream-cdr puts)))))

Now we can run the requests and print the result:

(stream-for-each display
  (stream-map cadr (run-dict requests)))

The (put 4 "d") definition is never added to the dictionary because it is never needed.


Next: , Previous: , Up: streams examples   [Index]