Clojure 返回映射/列表/序列中满足谓词的第一项
我正在寻找一个函数,它返回序列中的第一个元素,fn的计算结果为true。例如:Clojure 返回映射/列表/序列中满足谓词的第一项,clojure,Clojure,我正在寻找一个函数,它返回序列中的第一个元素,fn的计算结果为true。例如: (first-map (fn [x] (= x 1)) '(3 4 1)) 上面的伪函数应该返回1(列表中的最后一个元素)。Clojure有类似的东西吗 user=> (defn find-first [f coll] (first (filter f coll))) #'user/find-first user=> (find-first #(= % 1) [3 4
(first-map (fn [x] (= x 1)) '(3 4 1))
上面的伪函数应该返回1(列表中的最后一个元素)。Clojure有类似的东西吗
user=> (defn find-first
[f coll]
(first (filter f coll)))
#'user/find-first
user=> (find-first #(= % 1) [3 4 1])
1
编辑:一个并发代码。:)否。它不适用于整个列表。由于
过滤器的惰性,仅对第一个匹配的元素进行过滤我认为一些是该工作的最佳工具:
(some #(if (= % 1) %) '(3 4 1))
在你的例子中,这个成语是
(some #{1} [1 2 3 4])
工作原理:#{1}是一个集合文字。集合也是一个函数,如果集合中存在参数,则计算其参数,否则计算为零。任何集合元素都是一个“truthy”值(除了布尔值false,但这在集合中是罕见的)some
返回针对第一个集合成员计算的谓词的返回值,该成员的结果是真实的。使用drop而不是filter
应该解决f
对于分块序列的“过度应用”:
(defn find-first [f coll]
(first (drop-while (complement f) coll)))
;;=> #'user/find-first
(find-first #(= % 1) [3 4 1])
;;=> 1
我尝试了本线程中提到的几种方法(JDK 8和Clojure 1.7),并进行了一些基准测试:
repl> (defn find-first
[f coll]
(first (filter f coll)))
#'cenx.parker.strategies.vzw.repl/find-first
repl> (time (find-first #(= % 50000000) (range)))
"Elapsed time: 5799.41122 msecs"
50000000
repl> (time (some #{50000000} (range)))
"Elapsed time: 4386.256124 msecs"
50000000
repl> (time (reduce #(when (= %2 50000000) (reduced %2)) nil (range)))
"Elapsed time: 993.267553 msecs"
50000000
结果表明,reduce
方式可能是clojure 1.7中最有效的解决方案。为(first(filter pred coll))
习惯用法添加了一个有效的快捷方式,称为seek
该实现避免了(first(filter))
和(some#(when(pred))
替代方案的问题。也就是说,它能有效地处理分块序列,并能很好地处理nil?
和false?
谓词
补丁:
(defn seek
"Returns first item from coll for which (pred item) returns true.
Returns nil if no such item is present, or the not-found value if supplied."
{:added "1.9" ; note, this was never accepted into clojure core
:static true}
([pred coll] (seek pred coll nil))
([pred coll not-found]
(reduce (fn [_ x]
(if (pred x)
(reduced x)
not-found))
not-found coll)))
示例:
(seek odd? (range)) => 1
(seek pos? [-1 1]) => 1
(seek pos? [-1 -2] ::not-found) => ::not-found
(seek nil? [1 2 nil 3] ::not-found) => nil
最终,该修补程序被拒绝:
经审查,我们决定不将其包括在内。使用线性搜索(尤其是嵌套线性搜索)会导致性能低下-通常最好使用其他类型的数据结构,这就是为什么以前没有包含此功能的原因~亚历克斯·米勒2017年5月12日下午3:34
为什么不干脆(首先(filter#(=%1)”(3 4 1))
?@4e6因为这会将函数应用于列表中的每个元素,这在大列表中可能不受欢迎。Map是懒惰的,所以我不认为它会。不过你应该对此进行测试。谢谢@kotarak,我担心这会处理集合中的所有元素,而在大列表中可能不受欢迎。也许我需要创建一个te一个递归函数,直到找到满足条件的元素。@Matthew模分块序列。f
可能会根据分块大小应用到更多元素。使用过滤器或一些
会占用更多资源。或者,reduced
可以缩短整个计算过程,而不必花费时间更少的资源:(reduce#(when(=250000000)(reduced%2))nil(range))
@xando您是否使用了类似的东西来证明它?user=>(def input(range))#'user/input user=>(time(some#{50000000}input))“运行时间:3909.789411毫秒”50000000 user=>(time(reduce#(when(=250000000)(reduced%2))nil input))“运行时间:198616.007581毫秒"50000000
这些恰好是我得到的结果。@xando事实上你一开始就说一些
使用了更多的资源,减少的
可以简化整个计算,同时占用更少的资源。只有在使用纯范围
或其他可以直接减少自身。此外,所有备选方案都明显短路(范围
是一个无限序列)。这不会受到分块序列效应的影响。但在1
为nil
或false
.YMMV.等等……1怎么能等于nil?:)的情况下不起作用,马修指定元素应该“求真”,因此我认为行为是需要的。请考虑<代码> f <代码> > <代码>(包含{ 0)假“false } %”< /代码>。他说“评估fn为真”。非常有趣。谢谢你测试这些,xando。如果从中获取数字会更好,但我的猜测是,通过搜索序列中的一个项目,JVM有机会优化代码。如果您可以使用整个元素进行搜索,这是很有用的,但是如果部分索引,一些
将不会返回复合元素(如hashmaps)。(->[nil nil nil nil 42 nil nil](某些标识))
42冷溶液:)。我测试以确保懒惰<代码>(查找第一个#(do(println%)(偶数?)[1 3 2 5 7 9])
,返回2,不打印5、7或9。根据Jira通知单,该修补程序被拒绝。他们不想鼓励人们使用低效的线性搜索。我觉得有道理:)