Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Idiomatically insert items between two items in a sequence that fulfill a predicate?

Say I have a predicate that takes two items and returns true of false. I want to insert an item in between every consecutive pair in a sequence that returns true for the predicate. I've come up with a couple of solutions but I was wondering what would be idiomatic way to do it functionally in Clojure?

like image 927
jhowarth Avatar asked Oct 20 '11 04:10

jhowarth


3 Answers

My first draft would be something like

(defn insert-between [pred inter coll]
  (lazy-seq
   (when-let [s (seq coll)]
     (cons (first s)
           (mapcat (fn [[left right]]
                     (if (pred left right)
                       [inter right]
                       [right]))
                   (partition 2 1 s))))))


user> (insert-between < :less [1 6 7 4 3])
(1 :less 6 :less 7 4 3)

Seems to work, but I'm special-casing the first element in an ugly way and I think you could get around that. The solution could definitely be improved, anyway.

like image 89
amalloy Avatar answered Nov 03 '22 15:11

amalloy


This is my try at it:

(defn interpose-predicated [pred in coll]
  (if (next coll)
    (->> coll
         (partition 2 1)
         (mapcat (comp next #(if (apply pred %) (interpose in %) %)))
         (cons (first coll)))
    coll))
like image 39
mange Avatar answered Nov 03 '22 16:11

mange


It's the usual SO [clojure] race to come up with the most concise solution. :-) I usually don't win, but I learn a lot in the process. Anyway, here is my solution:

(defn interpose-p [[a b & _ :as s] d p]
  (when-not (empty? s)
    (if (and (not (nil? b)) (p a b))
      (cons a (cons d (interpose-p (rest s) d p)))
      (cons a (interpose-p (rest s) d p)))))

(interpose-p [1 2 3 2 1 2 3] "," <) 

(1 "," 2 "," 3 2 1 "," 2 "," 3)

Update: Even though the discussion is over, here is an updated solution taking into account everyone's comments. This time is should be fairly industrial strength assuming my understanding of lazy-seq is correct. It is templated off of the lazy-seq discussion here.

(defn interpose-p
  [pred coll sep]
  (let [f (fn [c]
            (when-let [[a b & _ :as s] (seq c)]
              (if (and b (pred a b))
                (list* a sep (interpose-p pred (rest s) sep))
                (list* a (interpose-p pred (rest s) sep)))))]
    (lazy-seq (f coll))))
like image 21
Julien Chastang Avatar answered Nov 03 '22 15:11

Julien Chastang