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?
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))
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With