Search code examples
clojureinterleave

Extension of interleave in clojure


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!


Solution

  • 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 simple strict approach

    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))))
    

    Combinator-based lazy approach

    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.BigInts):

    (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)
    

    Combinator-based lazy approach with varargs

    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)