【问题标题】:How do I write this Clojure function so that it doesn't blow out the stack?我如何编写这个 Clojure 函数,以免堆栈溢出?
【发布时间】:2012-01-30 01:53:45
【问题描述】:

我是 Clojure 的新手,我认为到目前为止我编写代码的方法不符合“Clojure 之道”。至少,我一直在编写函数,这些函数不断导致大值的 StackOverflow 错误。我已经了解了如何使用 recur,这是向前迈出的一大步。但是,如何使像下面这样的函数适用于像 2500000 这样的值?

(defn fib [i]
  (if (>= 2 i)
    1
    (+ (fib (dec i))
       (fib (- i 2)))))

在我看来,该函数是斐波那契生成器的“简单”实现。我已经看到了其他更优化的实现,但在它们的作用方面不太明显。 IE。当您阅读函数定义时,您不会“哦,斐波那契”。

任何指针将不胜感激!

【问题讨论】:

    标签: recursion clojure fibonacci


    【解决方案1】:
     ;;from "The Pragmatic Programmers Programming Clojure"
     (defn fib [] (map first (iterate (fn [[a b]][b (+ a b)])[0N 1N])))
     (nth (fib) 2500000)
    

    【讨论】:

      【解决方案2】:

      您需要有一个关于您的功能如何运作的心智模型。假设您自己执行函数,每次调用都使用纸屑。第一个废品,你写(fib 250000),然后你看到“哦,我需要计算(fib 249999)(fib 249998),最后把它们相加”,所以你记下它并开始两个新的废品。你不能扔掉第一个,因为当你完成其他计算时,它还有事要做。你可以想象这个计算需要大量的废料。

      另一种方法不是从顶部开始,而是从底部开始。您将如何手动执行此操作?您将从第一个数字开始,1, 1, 2, 3, 5, 8 ...,然后总是添加最后两个,直到您完成 i 次。您甚至可以在每个步骤中丢弃除最后两个数字之外的所有数字,这样您就可以重复使用大多数废料。

      (defn fib [i]
        (loop [a 0
               b 1
               n 1]
          (if (>= n i)
              b
              (recur b
                     (+ a b)
                     (inc n)))))
      

      这也是一个相当明显的实现,但如何,而不是什么。当您可以简单地写下定义并自动将其转换为有效的计算时,它似乎总是很优雅,但编程就是这种转换。如果某些东西自动转换,那么这个特定问题已经解决(通常以更一般的方式)。

      思考“我将如何在纸上一步一步地做到这一点”通常会导致良好的实施。

      【讨论】:

      • 谢谢!我会考虑这个。 :)
      • 嘿,只是想回来寻求帮助:如果我将大于 92 的值传递给您的函数,我会收到错误消息。 ArithmeticException 整数溢出 clojure.lang.Numbers.throwIntOverflow (Numbers.java:1374) 我错过了什么吗?
      • @bitops:您可能会注意到(fib 92) 是一个相当大的数字。事实上,它需要接近 64 位。 Clojure 应该自动切换到 BigNums,所以这可能是一个错误或一些速度优化设置。
      • @bitops:我偶然发现了一个explanation。 Clojure 默认不再进行自动升级(从 1.3 版开始)。为此,您需要使用撇号版本的算术运算符,在这种情况下为 +'
      【解决方案3】:

      以“普通”方式实现的斐波那契生成器,就像在序列的定义中一样,总是会炸毁你的堆栈。对fib 的两次递归调用都不是尾递归的,这样的定义无法优化。

      不幸的是,如果您想编写一个适用于大数字的高效实现,您将不得不接受这样一个事实,即数学符号无法像我们希望的那样干净利落地转换为代码。

      例如,可以在clojure.contrib.lazy-seqs 中找到非递归实现。可以在Haskell wiki 上找到解决此问题的各种方法。了解函数式编程的基础知识应该不难理解。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2022-07-17
        • 2010-11-30
        • 2013-06-28
        • 2016-08-01
        • 2011-09-21
        • 2014-04-13
        • 2020-03-08
        • 2012-08-24
        相关资源
        最近更新 更多