Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

idiomatic way to only update the first elem matching a pred in a coll

Tags:

clojure

I have a seq, (def coll '([:a 20] [:b 30] [:c 50] [:d 90]))

I want to iterate through the seq, and modify only the first element that matches a predicate.

The predicate (def pred (fn [[a b]] (> b 30)))

(f pred (fn [[a b]] [a (+ b 2)]) coll) => ([:a 20] [:b 30] [:c 52] [:d 90])

f is the fn I want, which takes a pred, and a fn to apply to the first elem which matches the pred. All the rest of the elems are not modified and returned in the seq.

What is the idiomatic way to do the above?

like image 591
murtaza52 Avatar asked May 10 '13 10:05

murtaza52


2 Answers

One possible way is to split the collection with split-with, apply the function f to the first element of the second collection returned by split-with, and concat the elements together again.

(defn apply-to-first [pred f coll]
    (let [[h t] (split-with (complement pred) coll)]
        (concat h (list (f (first t))) (rest t))))

Note that the pred function in your example should probably look like this:

(def pred #(> (second %) 30))
like image 197
sloth Avatar answered Nov 07 '22 11:11

sloth


As with most problems, there is a number of ways to solve it. This is but one of them.

If you're running Clojure 1.5, give this a try:

(reduce
 (fn [acc [a b]]
   (if (pred b)
     (reduced (concat (:res acc) [[a (+ b 2)]] (rest (:coll acc))))
     (assoc acc
       :res (conj (:res acc) [a b])
       :coll (rest (:coll acc)))))
 {:coll coll :res []}
 coll)

;; ([:a 20] [:b 30] [:c 52] [:d 90])

The key in this algorithm is the use of the reduced (note the 'd') function - it essentially tells reduce to halt the iteration and return the result. From its doc string:

-------------------------
clojure.core/reduced
([x])
  Wraps x in a way such that a reduce will terminate with the value x

The code is a bit terse, but it should give you the basic idea.

Hope this helps.

like image 40
leonardoborges Avatar answered Nov 07 '22 10:11

leonardoborges