I want to write a function to interleave two given sequences. The function should work like this:
user=> (ext-interl '(1 2 3 4 5 6 7 8) '(a b c))
(1 a 2 b 3 c 4 a 5 b 6 c 7 a 8 b)
The process will end when it reaches the longer sequence.
My code is:
(defn ext-interl [l1 l2]
(lazy-seq
(let [ls1 (seq l1) ls2 (seq l2)]
(cond (and ls1 ls2)
(cons (first ls1) (cons (first ls2) (ext-interl (rest ls1) (rest ls2))))
ls1 ls1
ls2 ls2))))
But this code runs like:
user=> (ext-interl '(1 2 3 4 5 6 7 8) '(a b c))
(1 a 2 b 3 c 4 5 6 7 8)
How can I fix this code? Thank you!
Here are three versions: a simple, strict one; a lazy one based on clojure.core
combinators; and a lazy one based on the same combinators that takes an arbitrary number of inputs.
A sanity check on the lazy approaches.
(defn interleave-longer-strict [xs ys]
(take (* 2 (max (count xs) (count ys)))
(interleave (cycle xs) (cycle ys))))
This one is a lazy version based on map
, mapcat
, take-while
, iterate
, interleave
and cycle
:
(defn interleave-longer
"Lazy version of
(take (* 2 (max (count xs) (count ys)))
(interleave (cycle xs) (cycle ys)))"
[xs ys]
(map (fn [_ e] e)
(mapcat (fn [[xs ys]] [[xs ys] [xs ys]])
(take-while (fn [[xs ys]] (or xs ys))
(iterate (fn [[xs ys]] [(next xs) (next ys)])
[xs ys])))
(interleave (cycle xs) (cycle ys))))
To demonstrate that it is indeed lazy (NB. (range)
never returns – if you actually consume it up through Long/MAX_VALUE
, it'll just start returning clojure.lang.BigInt
s):
(take 30 (interleave-longer (range) (range 11)))
;= (0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 11 0 12 1 13 2 14 3)
(take 30 (interleave-longer (range 11) (range)))
;= (0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9 10 10 0 11 1 12 2 13 3 14)
And finally, a lazy version based on the same primitives plus apply
, repeat
and count
(applied to the varargs seq to establish how many inputs there are) that takes an arbitrary number of inputs:
(defn interleave-longest [& xss]
(map (fn [e & _] e)
(apply interleave (map cycle xss))
(mapcat (fn [xss] (repeat (count xss) xss))
(take-while (fn [xss] (some some? xss))
(iterate (fn [xss] (mapv next xss))
xss)))))
At the REPL:
(interleave-longest [:a :b :c :d] (range 11) '[x y z])
;= (:a 0 x :b 1 y :c 2 z :d 3 x :a 4 y :b 5 z :c 6 x :d 7 y :a 8 z :b 9 x :c 10 y)
(take 30 (interleave-longest [:a :b :c :d] (range) '[x y z]))
;= (:a 0 x :b 1 y :c 2 z :d 3 x :a 4 y :b 5 z :c 6 x :d 7 y :a 8 z :b 9 x)