Hi, Racket Discourse.
I am reimplementing some logic for my iterations over multiset-relations in terms of streams for to see what there is to see.
The two basic operations to which I need access, are the conjunction (product) and disjunction (interleaving) of streams. The latter is simple enough, being an oft shown example, but I am wondering if I am overthinking the product.
Also, is it okay to slap the stream-lazy
expressions in there as I do and call it a day?
My previous implementation uses generators, so I had very little concern for actually "sequencing" things beyond yield
-ing at the appropriate time.
#lang racket
(require
"shortnames.rkt")
(provide
stream-disj
stream-conj)
(define (interleave x y)
(stream-lazy
(if (stream-empty? x)
y
(stream-cons
(stream-first x) (interleave y (stream-rest x))))))
(define (stream-disj streams)
(foldl interleave empty-stream (reverse streams)))
(define-unique stnl)
; just my own nonsense producing:
; stnl : (gensym)
; stnl? : == stnl
(define (cycle-stream stm)
(stream-lazy
(stream-cons stnl (stream-append stm (cycle-stream stm)))))
(define cycle-front-to-back
(match-lambda
[(list inf ... fin) `(,@(map cycle-stream inf) ,fin)]))
(define (stream-conj streams #:combine [combine values])
(if (null? streams)
empty-stream
(let loop ([prev streams]
[stms (cycle-front-to-back streams)])
(stream-lazy
(for/fold ([tick #true]
[prod (list)]
[stms (list)]
#:result
(cond
[(not prod) empty-stream]
[else
(define curr (reverse prod))
(stream-cons
(combine curr) (loop curr (reverse stms)))]))
([stm (in-list stms)]
[old (in-list prev)]
#:break (not prod))
(cond
[(not tick)
(values tick (cons old prod) (cons stm stms))]
[(stream-empty? stm)
(values tick #false stms)]
[(stnl? (stream-first stm))
(define stm′ (stream-rest stm))
(define new (stream-first stm′))
(values
tick (cons new prod) (cons (stream-rest stm′) stms))]
[else
(define new (stream-first stm))
(values
(not tick) (cons new prod) (cons (stream-rest stm) stms))]))))))
(stream->list
(stream-disj
(list (stream 1 2 3)
(stream 4 5)
(stream 6 7 8))))
(stream->list
(stream-conj
(list (stream 1 2 3)
(stream 4 5)
(stream 6 7 8))))
;=>
#|
'(1 4 2 6 3 5 7 8)
'((1 4 6)
(2 4 6)
(3 4 6)
(1 5 6)
(2 5 6)
(3 5 6)
(1 4 7)
(2 4 7)
(3 4 7)
(1 5 7)
(2 5 7)
(3 5 7)
(1 4 8)
(2 4 8)
(3 4 8)
(1 5 8)
(2 5 8)
(3 5 8))
|#
Edit: like the stream-lazy
I added now, is that strictly necessary? I would think so, but am unsure of the "extent" to which it applies.
Edit: best of three
Edit: I didn't know you could match
on streams! Go figure.
; much easier to read at a glance
(match stm
[_ #:when (not tick)
(values tick (cons old prod) (cons stm stms))]
[(stream)
(values tick #false stms)]
[(stream* (? tock?) new rest) ; I renamed `stnl` to `tock` because it's cute
(values tick (cons new prod) (cons rest stms))]
[(stream* new rest)
(values (not tick) (cons new prod) (cons rest stms))])
; than this, I would hazard
(cond
[(not tick)
(values tick (cons old prod) (cons stm stms))]
[(stream-empty? stm)
(values tick #false stms)]
[(tock? (stream-first stm))
(define stm′ (stream-rest stm))
(define new (stream-first stm′))
(values tick (cons new prod) (cons (stream-rest stm′) stms))]
[else
(define new (stream-first stm))
(values (not tick) (cons new prod) (cons (stream-rest stm) stms))])