我有一个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中返回。
执行上述操作的惯用方法是什么?
答案 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))