哪个更好?:(减少+ ...)或(应用+ ...)?

时间:2009-08-02 16:15:55

标签: lisp clojure scheme

我应该使用

(apply + (filter prime? (range 1 20)))

(reduce + (filter prime? (range 1 20)))

编辑:这是优化工具包中的素数来源。

(defn prime?  [n]  
  (cond
    (or (= n 2) (= n 3))          true
    (or (divisible? n 2) (< n 2)) false
    :else                        
     (let [sqrt-n (Math/sqrt n)]
       (loop [i 3]
           (cond
              (divisible? n i) false
              (< sqrt-n i)     true
              :else            (recur (+ i 2)))))))

5 个答案:

答案 0 :(得分:18)

如果您在性能方面提出要求,reduce会更好一点:

(time (dotimes [_ 1e6] (apply + (filter even? (range 1 20)))))
"Elapsed time: 9059.251 msecs"
nil

(time (dotimes [_ 1e6] (reduce + (filter even? (range 1 20)))))
"Elapsed time: 8420.323 msecs"
nil

在这种情况下差异大约为7%,但YMMV取决于机器。

您尚未提供prime?函数的来源,因此我已将even?替换为谓词。请注意,您的运行时可能由prime?占主导地位,在这种情况下,reduceapply之间的选择更为重要。

如果你问哪个更“lispy”那么我会说reduce实现是可取的,因为你正在做的是函数编程意义上的减少/折叠。

答案 1 :(得分:13)

我认为reduce在可用时更可取,因为apply使用列表作为函数的参数,但当你有一个大数字 - 比如说,一百万 - 在列表中的元素中,您将构造一个带有一百万个参数的函数调用!这可能会导致Lisp的某些实现出现一些问题。

答案 2 :(得分:8)

(reduce op ...)是规范和(应用op ...)异常(特别是对于str和concat)。

答案 3 :(得分:7)

我希望申请实现一个可能很难看的懒惰列表,而且你永远不想假设你的列表不是懒惰的,因为你可能突然发现自己被大量的内存使用所困扰。

Reduce将逐个抓住它们并将结果汇​​总到一个整体中,而不是立即将整个列表记录下来。

答案 4 :(得分:6)

我将扮演魔鬼的拥护者并争辩apply

reduce是对fold(更确切地说是foldl)的左侧折叠,并且通常使用初始元素定义,因为折叠操作有两部分:< / p>

  • 初始(或“零”)值

  • 组合两个值的操作

为了找到一组数字的总和,使用+的自然方式为(fold + 0 values),或者在clojure中(reduce + 0 values)

这明确地显示了空列表的结果,这很重要,因为在这种情况下+返回0对我来说并不明显 - 毕竟,+是< em>二元运算符(所有fold需要或假设)。

现在,实际上,事实证明,clojure的+被定义为 more 而不是二元运算符。它将需要许多甚至零值。凉。但如果我们使用这个“额外”信息,那么向读者发出信号是友好的。 (apply + values)这样做 - 它说“我以一种奇怪的方式使用+,不仅仅是二元运算符”。这有助于人们(至少我)理解代码。

[有趣的是,为什么apply感觉更清楚。我认为部分原因是你要对读者说:“看,+旨在接受多个值(这是适用的用途),因此语言实现将包括零值的情况。” reduce应用于单个列表时不存在隐式参数。]

或者,(reduce + 0 values)也没关系。但是(reduce + values)在我身上引发了一种本能的反应:“嗯,+提供零吗?”。

如果您不同意,那么请在您投票或发布回复之前,确定关于(reduce * values)将返回哪个空列表?