Search code examples
recursionstreamschemesicplazy-sequences

Generating a stream of all possible tuples from given streams using Scheme


I am trying to write a procedure stream-weighted-tuples that takes a weight procedure and any number of streams, to generate a stream of tuples. For instance,

(stream-weighted-tuples
  (lambda (t) (+ (car t) (cadr t) (caddr t))
  integers
  integers
  integers)

should generate a stream of (1 1 1) (1 1 2) (1 2 1) (2 1 1) (1 1 3) (1 2 2) (1 3 1) (2 1 2) (2 2 1) (3 1 1) (1 1 4) (1 2 3) ....

I was inspired by exercise 3.70 in SICP, which is about writing a procedure weighted-pairs that takes two streams and a weight procedure to generate a stream of pairs in the order according to weight. So basically, this is a generalization of the weighted-pairs procedure to take more than two streams.

I wrote the following version:

    (define (stream-weighted-tuples weight . streams)
      (cond ((null? streams)
             (error "No streams given -- STREM-WEIGHTED-TUPLES"))
            ((null? (cdr streams))
             (stream-map list (car streams)))
            (else
              (let ((s (car streams))
                    (rest (cdr streams)))
                (if (stream-null? s)
                    the-empty-stream  ; {} x S = {}
                    (stream-merge-weighted
                      weight
                      (stream-map (lambda (tuple)
                                    (cons (stream-car s) tuple))
                                  (apply stream-weighted-tuples
                                         (lambda (tuple)  ; partial weight
                                           (weight (cons (stream-car s)
                                                         tuple)))
                                         rest))
                      (apply stream-weighted-tuples
                             weight
                             (stream-cdr s)
                             rest)))))))

(which apparently doesn’t work). The idea was to merge 1. the stream resulting from consing the first element of the first stream with each element of the (recursively constructed) stream that consists of the tuples from the rest of the given streams, and 2. the stream that consists of the tuples of the stream-cdr of the first stream and the rest of the given streams.

This doesn’t work because generating stream 2 won’t halt in the case of an infinite stream (due to the same reason pairs procedure from exercise 3.68 doesn’t work).

This is stream-merge-weighted I implemented:

    (define (stream-merge-weighted weight s1 s2)
      (cond ((stream-null? s1) s2)
            ((stream-null? s2) s1)
            (else
              (let* ((s1car (stream-car s1))
                     (s2car (stream-car s2))
                     (s1car-weight (weight s1car))
                     (s2car-weight (weight s2car)))
                (cond ((<= s1car-weight s2car-weight)
                       (cons-stream
                         s1car
                         (stream-merge-weighted weight
                                                (stream-cdr s1)
                                                s2)))
                      ((> s1car-weight s2car-weight)
                       (cons-stream
                         s2car
                         (stream-merge-weighted weight
                                                s1
                                                (stream-cdr s2)))))))))

Is there a way to recursively construct this problem?


EDIT: What I meant by “tuple” in this question is actually a Scheme list that semantically represents a tuple.

For reference, I leave my implementation of stream primitives (which is identical to the ones in SICP):

    (define-syntax cons-stream
      (syntax-rules ()
        ((_ a b)
         (cons a (delay b)))))

    (define (stream-car stream)
      (car stream))
    (define (stream-cdr stream)
      (force (cdr stream)))

    (define (stream-null? stream)
      (null? stream))
    (define the-empty-stream '())

    (define (stream-map proc s)
      (if (stream-null? s)
          the-empty-stream
          (cons-stream
            (proc (stream-car s))
            (stream-map proc (stream-cdr s)))))

Solution

  • I managed to solve it using a recursion structure similar to that of the pairs procedure shown in SICP.

    The following stream-weighted-tuples works with recursively constructed stream of tuples of the cdr of streams (rest-tuples), and then uses the same recursion strategy to “pair” (unlike the original pair procedure, the other stream is already a stream of tuples) it with the car of streams (combine-with-rest).

        (define (stream-weighted-tuples weight . streams)
          (if (null? streams)
              (cons-stream '() the-empty-stream)
              (let* ((stream (car streams))
                     (rest (cdr streams))
                     (rest-tuples
                       (apply stream-weighted-tuples
                              (lambda (tuple)  ; partial weight
                                (weight (cons (stream-car stream) tuple)))
                              rest)))
                (let combine-with-rest ((stream stream)
                                        (rest-tuples rest-tuples))
                  (if (or (stream-null? stream)
                          (stream-null? rest-tuples))
                      the-empty-stream  ; {} x S = {}
                      (let ((s-car (stream-car stream))
                            (s-cdr (stream-cdr stream))
                            (r-car (stream-car rest-tuples))
                            (r-cdr (stream-cdr rest-tuples)))
                        (cons-stream (cons s-car r-car)
                                     (stream-merge-weighted
                                       weight
                                       (stream-map (lambda (t) (cons s-car t))
                                                   r-cdr)
                                       (stream-merge-weighted
                                         weight
                                         (stream-map (lambda (x) (cons x r-car))
                                                     s-cdr)
                                         (combine-with-rest s-cdr r-cdr))))))))))
    

    Note, that the weight procedure requires the cdr of tuples to be ordered independent to the cars, i.e., W(a, b, c) >= W(x, y, z) only if W(b, c) >= W(y, z).