惯用的方法只更新匹配coll中的pred的第一个elem

时间:2013-05-10 10:34:46

标签: clojure

我有一个seq,(def coll '([:a 20] [:b 30] [:c 50] [:d 90]))

我想迭代seq,并只修改与谓词匹配的第一个元素。

谓词(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是我想要的fn,它采用pred和fn应用于匹配pred的第一个elem。所有其余的元素都没有修改并在seq中返回。

执行上述操作的惯用方法是什么?

4 个答案:

答案 0 :(得分:5)

一种可能的方法是使用split-with拆分集合,将函数f应用于split-with返回的第二个集合的第一个元素,以及concat个元素再一次。

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

请注意,示例中的pred函数可能如下所示:

(def pred #(> (second %) 30))

答案 1 :(得分:4)

与大多数问题一样,有很多方法可以解决它。这只是其中之一。

如果您正在运行Clojure 1.5,请尝试一下:

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

该算法的关键是使用reduced(注意'd')函数 - 它实质上告诉reduce停止迭代并返回结果。从其文档字符串:

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

代码有点简洁,但它应该给你基本的想法。

希望这有帮助。

答案 2 :(得分:4)

这个函数不难“从头开始”递归写入。这不仅是一个很好的学习练习,它还可以产生最佳解决方案:它尽可能地懒惰,并且绝对最小的计算量。到目前为止,只有一个问题的答案是懒惰的,并且在更新发生之前,所有项目都会调用pred两次:一次在take-while,一次在drop-while,部分split-with

(defn update-first [pred f coll]
  (lazy-seq
   (when-let [coll (seq coll)]
     (if (pred (first coll))
       (cons (f (first coll))
             (rest coll))
       (cons (first coll)
             (update-first pred f (rest coll)))))))

答案 3 :(得分:0)

要保持简单:找到第一个元素,找到它的索引并使用assoc来“更新”索引处的元素:

(let [e (first (filter pred coll))
      ind (.indexOf coll e)] 
  (assoc (vec coll) ind ((fn [[a b]] [a (+ b 2)]) e) ))

Dominic关于pred的说明适用:

(def pred #(> (second %) 30))