【问题标题】:foldl vs foldr: which should I prefer?foldl vs foldr:我应该更喜欢哪个?
【发布时间】:2014-09-12 11:16:48
【问题描述】:

我记得当我展示我写给教授的一些代码时,他不经意间说,

这并不重要,但值得注意的是,fold* 在 SML/NJ 中比 fold*' 更有效,因此如果可能,您应该更喜欢它而不是 fold*

我忘记了fold*foldr 还是foldl。我知道这是在实践中可能不会产生太大影响的微优化之一,但我希望养成在有选择时使用更高效的习惯。

哪个是哪个?我的猜测是,这是特定于 SML/NJ 的,并且 MLton 将足够聪明,可以将两者优化到相同的机器代码,但其他编译器的答案很高兴知道。

【问题讨论】:

    标签: sml smlnj


    【解决方案1】:

    foldl 是尾递归的,而 foldr 不是。虽然您可以通过反转列表(即尾递归)以尾递归方式执行 foldr,然后执行 foldl

    只有在折叠大量列表时才有意义。

    【讨论】:

    • SML 基础库文档没有解决实现细节,包括foldlfoldr 中的尾递归,因此无法知道foldr 是否按照@987654328 实现@ 或不。根据文档,唯一的区别在于f(xn(...f(x1, init))f(x1(...f(xn, init))。还值得注意的是,SML 基础库为数组提供了foldlifoldri,据推测,唯一的区别是数组索引的迭代顺序。这不会影响尾递归。其他语言(例如 Racket)的空间要求确实有所不同。
    • 其实foldr可能是尾递归的,带有一些累加函数。例子。找到满足谓词p 的元素。积累fn (e, acc) => if p e then SOME e else acc
    • @beroal:不。 foldr 是否为尾递归并不取决于它的参数。是否尾递归取决于递归调用是否在尾位置,不是(递归调用的结果传递给累加函数,在调用函数之前先计算参数)。
    • @newacct:是的。但是如果累加函数是内联的……
    【解决方案2】:

    更喜欢将给定输入转换为预期输出的那个。

    如果两者都产生相同的输出(例如求和),并且如果处理list,则从左侧折叠会更有效,因为折叠可以从头元素开始,而从右侧折叠首先需要步行在计算第一个中间结果之前找到最后一个元素的列表。

    对于数组和类似的随机访问数据结构,可能不会有太大区别。

    始终选择左右更好的编译器优化将要求编译器确定左右在所有可能的输入上是等价的。由于foldlfoldr 将函数作为参数,所以这要求有点高。

    【讨论】:

      【解决方案3】:

      我将在此处保留已接受的答案,但我有机会与我的教授交谈,他的回答实际上是相反的,因为我忘记了我的问题的一部分。有问题的代码正在建立一个列表,他说:

      在可能的情况下,首选foldr 而不是foldl,因为如果您通过在折叠期间附加元素来构建列表,它会在最后为您节省reverse

      举个简单的例子:

      - val ls = [1, 2, 3];
      val ls = [1,2,3] : int list
      - val acc = (fn (x, xs) => x::xs);
      val acc = fn : 'a * 'a list -> 'a list
      - foldl acc [] ls;
      val it = [3,2,1] : int list
      - foldr acc [] ls;
      val it = [1,2,3] : int list
      

      O(n) 保存反向可能比此问题的答案中提到的foldlfoldr 之间的其他区别更重要。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2012-06-18
        • 1970-01-01
        • 1970-01-01
        • 2011-02-10
        • 1970-01-01
        • 2013-12-13
        • 1970-01-01
        • 2021-09-23
        相关资源
        最近更新 更多