1
votes

I would like to create a lazy sequence that repeats elements from another collection. It should generate one of each element before repeating. And the order of elements must be random.

Here's what it should behave like:

=> (take 10 x)
(B A C B A C A B C C)

This seems to work:

(def x (lazy-seq (concat (lazy-seq (shuffle ['A 'B 'C])) x)))

However it is using two lazy-seq's. Is there a way to write this sort of lazy sequence by using just one lazy-seq?

If this cannot be done with one lazy-seq, how do values get generated? Since my collection has only three items, I expect the inner lazy-seq to be calculated completely in the first chunk.

Before coming up with the sequence above I tried the one below:

=> (def x (lazy-seq (concat (shuffle ['A 'B 'C]) x)))

=> (take 10 x)
(C A B C A B C A B C)

I would appreciate any explanation why this one doesn't randomize each batch.

2

2 Answers

3
votes

How about just repeating the sequence and then mapcatting over it with the shuffle function? Here is an example:

(take 10 (mapcat shuffle (repeat ['A 'B 'C])))
;=> (B C A B A C B A C B)
1
votes

I would appreciate any explanation why this one doesn't randomize each batch.

Your (shuffle '[a b c]) is evaluated only once, when beginning of the sequence is realized, then the shuffled list is used again and again.

Here is a little test:

user> (defn test-fnc [coll]
        (do (print "boom!")
            (shuffle coll)))
;; => #'user/test-fnc
user> (def x (lazy-seq (concat (test-fnc '[a b c]) x)))
;; => #'user/x
user> (take 10 x)
;; => (boom!b c a b c a b c a b)
user> (take 10 x)
;; => (b c a b c a b c a b)

You also can use lazy-cat instead:

user> (def x (lazy-cat (shuffle '[a b c]) x))
;; => #'user/x

but is won't solve your problem :-)

This happens because of lazy-seq:

Takes a body of expressions that returns an ISeq or nil, and yields a Seqable object that will invoke the body only the first time seq is called, and will cache the result and return it on all subsequent seq calls.


If you rewrite your definition in terms of function calls:

(defn x [] (lazy-seq (concat (shuffle ['A 'B 'C]) (x))))

it will work:

user> (take 10 (x))
;; => (C B A C A B B A C B)

because there will be different lazy seq on every call, not the same cached seq that repeats itself.