【发布时间】:2014-01-30 06:42:05
【问题描述】:
如果map 和doseq 有了孩子怎么办?我正在尝试编写像 Common Lisp 的 mapc 这样的函数或宏,但在 Clojure 中。这基本上是map 所做的,但仅用于副作用,因此它不需要生成一系列结果,也不会偷懒。我知道可以使用doseq 迭代单个序列,但是 map 可以迭代多个序列,依次对所有序列的每个元素应用一个函数。我也知道可以将map 包装在dorun 中。 (注意:这个问题在经过多次 cmet 和非常彻底的回答后已经被广泛编辑。最初的问题集中在宏上,但结果证明这些宏问题是外围问题。)
这很快(根据标准):
(defn domap2
[f coll]
(dotimes [i (count coll)]
(f (nth coll i))))
但它只接受一个集合。这接受任意集合:
(defn domap3
[f & colls]
(dotimes [i (apply min (map count colls))]
(apply f (map #(nth % i) colls))))
但相比之下它非常慢。我也可以写一个像第一个一样的版本,但有不同的参数 case [f c1 c2]、[f c1 c2 c3] 等,但最后,我需要一个处理任意数量集合的 case,就像最后一个例子一样,反正更简单。我也尝试了许多其他解决方案。
由于第二个示例与第一个示例非常相似,只是在循环中使用了apply 和map,我怀疑摆脱它们会加快速度。我试图通过将 domap2 编写为宏来做到这一点,但处理 & 之后的全部变量的方式一直让我感到困惑,如上图所示。
其他示例(15 或 20 个不同版本中的)、基准代码和几年前 Macbook Pro 上的时间(完整源代码here):
(defn domap1
[f coll]
(doseq [e coll]
(f e)))
(defn domap7
[f coll]
(dorun (map f coll)))
(defn domap18
[f & colls]
(dorun (apply map f colls)))
(defn domap15
[f coll]
(when (seq coll)
(f (first coll))
(recur f (rest coll))))
(defn domap17
[f & colls]
(let [argvecs (apply (partial map vector) colls)] ; seq of ntuples of interleaved vals
(doseq [args argvecs]
(apply f args))))
我正在开发一个使用 core.matrix 矩阵和向量的应用程序,但您可以在下面随意替换您自己的副作用函数。
(ns tst
(:use criterium.core
[clojure.core.matrix :as mx]))
(def howmany 1000)
(def a-coll (vec (range howmany)))
(def maskvec (zero-vector :vectorz howmany))
(defn unmaskit!
[idx]
(mx/mset! maskvec idx 1.0)) ; sets element idx of maskvec to 1.0
(defn runbench
[domapfn label]
(print (str "\n" label ":\n"))
(bench (def _ (domapfn unmaskit! a-coll))))
根据 Criterium 的平均执行时间,以微秒为单位:
domap1:12.317551 [剂量]
domap2: 19.065317 [dotimes]
domap3: 265.983779 [dotimes with apply, map]
domap7: 53.263230 [带有dorun的地图]
domap18: 54.456801 [带有 dorun 的地图,多个集合]
domap15:32.034993 [重复]
domap17: 95.259984 [doseq,使用 map 交错的多个集合]
编辑:dorun+map 可能是为多个大型惰性序列参数实现 domap 的最佳方式,但对于单个惰性序列,doseq 仍然是王道。执行与上述unmask! 相同的操作,但通过(mod idx 1000) 运行索引,并迭代(range 100000000),doseq 在我的测试中大约是dorun+map 的两倍(即(def domap25 (comp dorun map)) )。
【问题讨论】:
-
您的“如何编写将集合作为参数的可变参数 Clojure 宏”的实际问题完全丢失了。考虑只编辑与实际问题相关的部分。
-
感谢@A.Webb。将主要问题信息移至顶部。我希望人们会像你一样试图让我远离这个问题。我不介意,但到目前为止,在我看来,我的案子值得回答。我现在在“附录”部分添加了额外版本的
domap和计时。如您所见,dorun+map(domap7和domap8)比doseq(domap1)和dotimes(domap2和domap3)慢得多。 (我求助于dotimes,因为我想不出一种更有效的方法来并行遍历集合(请参阅domap15和domap17)。) -
我编辑并用domap18替换了domap8,并在新测试后替换了times。 domap8 使用了
apply (partial map f)。你提醒我,我可以直接说apply map f。 -
还有一点:我同意——我认为
dotimes会很慢。也许我只是没有在足够长的收藏中尝试过。应该有一种方法可以制作出与单集合doseq版本一样快的多集合版本。 -
自从我发布了这个问题并得到了回答,
run!已添加到核心语言中。它并没有达到我想要的效果,但它是相关的,值得了解。
标签: clojure