在 Scala 中编写斐波那契函数的最快方法是什么?
I've looked over a few implementations of Fibonacci function in Scala starting from a very simple one, to the more complicated ones.
I'm not entirely sure which one is the fastest. I'm leaning towards the impression that the ones that uses memoization is faster, however I wonder why Scala doesn't have a native memoization.
Can anyone enlighten me toward the best and fastest (and cleanest) way to write a fibonacci function?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(8)
最快的版本是在某种程度上偏离通常的添加方案的版本。计算速度非常快,在某种程度上类似于基于这些公式的快速二进制求幂:
下面是一些使用它的代码:
尽管这不是非常优化(例如,内部循环不是尾递归),但它会击败通常的加法实现。
The fastest versions are the ones that deviate from the usual addition scheme in some way. Very fast is the calculation somehow similar to a fast binary exponentiation based on these formulas:
Here is some code using it:
Even though this is not very optimized (e.g. the inner loop is not tail recursive), it will beat the usual additive implementations.
对我来说,最简单的定义了一个递归内部尾部函数:
这不需要为 zip 构建任何 Tuple 对象,并且在语法上很容易理解。
for me the simplest defines a recursive inner tail function:
This doesn't need to build any Tuple objects for the zip and is easy to understand syntactically.
Scala 确实有 Streams 形式的记忆。
Stream
是一个LinearSeq
,因此如果您执行大量fib(42),您可能希望将其转换为
类型调用。IndexedSeq
不过,我会质疑您的斐波那契函数的用例是什么。少于 100 个术语就会溢出 Long,因此更大的术语没有多大用处。如果速度至关重要,您可以将较小的术语放在表格中并查找它们。因此,计算的细节可能并不重要,因为对于较小的项来说,它们都很快。
如果您确实想知道非常大的项的结果,那么这取决于您是否只想要一次性值(使用 Landei 的解决方案),或者如果您进行了足够数量的调用,您可能需要预先计算全部。这里的问题是,例如,第 100,000 个元素的长度超过 20,000 位。因此,我们谈论的是千兆字节的 BigInt 值,如果您尝试将它们保存在内存中,它们将使您的 JVM 崩溃。您可以牺牲准确性并使事情更易于管理。您可以采用部分记忆策略(例如,每 100 个术语记忆一次),从而实现适当的内存/速度权衡。对于最快的速度没有明确的答案:这取决于您的使用情况和资源。
Scala does have memoization in the form of Streams.
Stream
is aLinearSeq
so you might like to convert it to anIndexedSeq
if you're doing a lot offib(42)
type calls.However I would question what your use-case is for a fibbonaci function. It will overflow Long in less than 100 terms so larger terms aren't much use for anything. The smaller terms you can just stick in a table and look them up if speed is paramount. So the details of the computation probably don't matter much since for the smaller terms they're all quick.
If you really want to know the results for very big terms, then it depends on whether you just want one-off values (use Landei's solution) or, if you're making a sufficient number of calls, you may want to pre-compute the whole lot. The problem here is that, for example, the 100,000th element is over 20,000 digits long. So we're talking gigabytes of BigInt values which will crash your JVM if you try to hold them in memory. You could sacrifice accuracy and make things more manageable. You could have a partial-memoization strategy (say, memoize every 100th term) which makes a suitable memory / speed trade-off. There is no clear anwser for what is the fastest: it depends on your usage and resources.
这可行。计算一个数字需要 O(1) 空间 O(n) 时间,但没有缓存。
This could work. it takes O(1) space O(n) time to calculate a number, but has no caching.
使用
Stream
的答案(包括接受的答案)非常简短且惯用,但它们不是最快的。流会记住它们的值(这在迭代解决方案中不是必需的),即使您不保留对流的引用,也可能会分配大量内存,然后立即进行垃圾收集。一个好的替代方案是使用迭代器:它不会导致内存分配,功能强大,简短且可读。The answers using
Stream
(including the accepted answer) are very short and idiomatic, but they aren't the fastest. Streams memoize their values (which isn't necessary in iterative solutions), and even if you don't keep the reference to the stream, a lot of memory may be allocated and then immediately garbage-collected. A good alternative is to use anIterator
: it doesn't cause memory allocations, is functional in style, short and readable.一个更简单的尾递归解决方案,可以计算大 n 值的斐波那契数。当
n > 时,Int 版本速度更快,但受到限制。 46
发生整数溢出A little simpler tail Recursive solution that can calculate Fibonacci for large values of n. The Int version is faster but is limited, when
n > 46
integer overflow occurs这个问题已经得到了回答,但希望我的经验对您有所帮助。我很难理解 scala 无限流。然后,我观看了 Paul Agron 的演讲,他给出了非常好的建议:(1)实施你的首先使用基本列表创建解决方案,然后如果您要使用参数化类型泛化您的解决方案,请首先使用 Int 等简单类型创建解决方案。
使用这种方法,我想出了一个真正简单的(对我来说,易于理解的解决方案):
为了达到我首先创建的上述解决方案,根据保罗的建议,基于简单列表的“for-dummy's”版本
:我短路了列表版本,因为如果我不这样做,它将永远运行..但是..谁在乎呢? ;^) 因为它只是一段探索性的代码。
This has already been answered, but hopefully you will find my experience helpful. I had a lot of trouble getting my mind around scala infinite streams. Then, I watched Paul Agron's presentation where he gave very good suggestions: (1) implement your solution with basic Lists first, then if you are going to generify your solution with parameterized types, create a solution with simple types like Int's first.
using that approach I came up with a real simple (and for me, easy to understand solution):
To get to the above solution I first created, as per Paul's advice, the "for-dummy's" version, based on simple lists:
Notice that I short circuited the list version, because if i didn't it would run forever.. But.. who cares? ;^) since it is just an exploratory bit of code.
下面的代码既快速又能够以高输入索引进行计算。在我的计算机上,它会在不到两秒的时间内返回第 10^6 个斐波那契数。该算法采用函数式风格,但不使用列表或流。相反,它基于等式 \phi^n = F_{n-1} + F_n*\phi,其中 \phi 是黄金比例。 (这是“比奈公式”的一个版本。)使用这个等式的问题是 \phi 是无理数(涉及五的平方根),因此如果简单地使用浮点数解释,它会由于有限精度算术而发散。然而,由于 \phi^2 = 1 + \phi,对于 a 和 b 整数,使用 a + b\phi 形式的数字很容易实现精确计算,这就是下面的算法所做的。 (“power”函数有一些优化,但实际上只是这些数字的“mult”乘法的迭代。)
编辑:一个更高效并且在某种意义上也更惯用的实现是基于 Typelevel 的 Spire用于数值计算和抽象代数的库。然后,我们可以用一种更接近数学论证的方式解释上面的代码(我们不需要整个环结构,但我认为包含它在道德上是正确的)。尝试运行以下代码:
The code below is both fast and able to compute with high input indices. On my computer it returns the 10^6:th Fibonacci number in less than two seconds. The algorithm is in a functional style but does not use lists or streams. Rather, it is based on the equality \phi^n = F_{n-1} + F_n*\phi, for \phi the golden ratio. (This is a version of "Binet's formula".) The problem with using this equality is that \phi is irrational (involving the square root of five) so it will diverge due to finite-precision arithmetics if interpreted naively using Float-numbers. However, since \phi^2 = 1 + \phi it is easy to implement exact computations with numbers of the form a + b\phi for a and b integers, and this is what the algorithm below does. (The "power" function has a bit of optimization in it but is really just iteration of the "mult"-multiplication on such numbers.)
EDIT: An implementation which is more efficient and in a sense also more idiomatic is based on Typelevel's Spire library for numeric computations and abstract algebra. One can then paraphrase the above code in a way much closer to the mathematical argument (We do not need the whole ring-structure but I think it's "morally correct" to include it). Try running the following code: