5

我有一个序列,(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 匹配的第一个元素。所有其余的元素都不会被修改并在 seq 中返回。

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

4

4 回答 4

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))
于 2013-05-10T11:24:52.150 回答
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

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

希望这可以帮助。

于 2013-05-10T11:20:24.587 回答
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)))))))
于 2013-05-11T09:44:11.787 回答
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))
于 2013-05-10T12:34:21.943 回答