我已经从Sc very simple one开始查看Scala中Fibonacci函数的一些实现,再到more complicated ones。
我不完全确定哪一个是最快的。我倾向于使用memoization的那些更快,但我想知道为什么Scala没有本机的memoization。
任何人都可以通过最好,最快(最干净)的方式来开发斐波那契函数吗?
答案 0 :(得分:21)
最快的版本是以某种方式偏离通常的添加方案的版本。非常快的计算方法类似于基于这些公式的快速二进制求幂:
F(2n-1) = F(n)² + F(n-1)²
F(2n) = (2F(n-1) + F(n))*F(n)
以下是使用它的一些代码:
def fib(n:Int):BigInt = {
def fibs(n:Int):(BigInt,BigInt) = if (n == 1) (1,0) else {
val (a,b) = fibs(n/2)
val p = (2*b+a)*a
val q = a*a + b*b
if(n % 2 == 0) (p,q) else (p+q,p)
}
fibs(n)._1
}
即使这不是非常优化(例如内部循环不是尾递归),它将胜过通常的附加实现。
答案 1 :(得分:15)
对我来说,最简单的定义递归内尾函数:
def fib: Stream[Long] = {
def tail(h: Long, n: Long): Stream[Long] = h #:: tail(n, h + n)
tail(0, 1)
}
这不需要为zip构建任何Tuple对象,并且在语法上很容易理解。
答案 2 :(得分:11)
Scala确实以Streams的形式进行了回忆。
val fib: Stream[BigInt] = 0 #:: 1 #:: fib.zip(fib.tail).map(p => p._1 + p._2)
scala> fib take 100 mkString " "
res22: String = 0 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584 4181 ...
Stream
是LinearSeq
,因此如果您正在进行大量IndexedSeq
类型的调用,您可能希望将其转换为fib(42)
。
但是我会质疑你的用例是什么样的fibbonaci功能。它将在不到100个术语中溢出Long,因此更大的术语对任何东西都没有多大用处。如果速度至关重要,您可以将较小的术语放在表格中并查看它们。所以计算的细节可能并不重要,因为对于较小的术语,它们都很快。
如果您真的想知道非常大的条款的结果,那么这取决于您是否只需要一次性值(使用Landei的解决方案),或者,如果您拨打了足够数量的电话,您可能需要预先计算整个批次。这里的问题是,例如,第100,000个元素长度超过20,000个数字。所以我们正在谈论千兆字节的BigInt值,如果你试图将它们保存在内存中,它们会使你的JVM崩溃。你可以牺牲准确性并使事情更易于管理。你可以有一个部分记忆策略(比如每100个记忆一次),它可以进行合适的记忆/速度权衡。对于什么是最快的,没有明确的答案:这取决于您的使用和资源。
答案 3 :(得分:7)
这可行。它需要O(1)空间O(n)时间来计算一个数字,但没有缓存。
object Fibonacci {
def fibonacci(i : Int) : Int = {
def h(last : Int, cur: Int, num : Int) : Int = {
if ( num == 0) cur
else h(cur, last + cur, num - 1)
}
if (i < 0) - 1
else if (i == 0 || i == 1) 1
else h(1,2,i - 2)
}
def main(args: Array[String]){
(0 to 10).foreach( (x : Int) => print(fibonacci(x) + " "))
}
}
答案 4 :(得分:2)
一个更简单的尾递归解,可以计算大的n值的Fibonacci。当n > 46
整数溢出发生时,Int版本更快但有限
def tailRecursiveBig(n :Int) : BigInt = {
@tailrec
def aux(n : Int, next :BigInt, acc :BigInt) :BigInt ={
if(n == 0) acc
else aux(n-1, acc + next,next)
}
aux(n,1,0)
}
答案 5 :(得分:2)
使用Stream
的答案(包括已接受的答案)非常简短且惯用,但它们并不是最快的。 Streams会记住它们的值(这在迭代解决方案中是不必要的),即使你没有保留对流的引用,也可以分配大量内存,然后立即进行垃圾收集。一个很好的选择是使用Iterator
:它不会导致内存分配,功能样式,简短和可读。
def fib(n: Int) = Iterator.iterate(BigInt(0), BigInt(1)) { case (a, b) => (b, a+b) }.
map(_._1).drop(n).next
答案 6 :(得分:0)
这已经得到了解答,但希望您会发现我的经验很有帮助。我在scala无限流中思考时遇到了很多麻烦。然后,我看了Paul Agron's presentation他给出了非常好的建议:(1)首先用基本列表实现你的解决方案,然后如果你要用参数化类型生成你的解决方案,用Int的第一个简单类型创建一个解决方案。
使用这种方法我想出了一个非常简单的(对我来说,易于理解的解决方案):
def fib(h: Int, n: Int) : Stream[Int] = { h #:: fib(n, h + n) }
var x = fib(0,1)
println (s"results: ${(x take 10).toList}")
为了达到上述解决方案,我首先根据Paul的建议创建了基于简单列表的“for-dummy”版本:
def fib(h: Int, n: Int) : List[Int] = {
if (h > 100) {
Nil
} else {
h :: fib(n, h + n)
}
}
请注意,我将列表版本短路了,因为如果我不这样做会永远运行..但是......谁在乎呢? ; ^)因为它只是一个探索性的代码。
答案 7 :(得分:0)
以下代码既快又能用高输入指数计算。在我的计算机上,它在不到两秒的时间内返回10 ^ 6:斐波纳契数。该算法采用功能样式,但不使用列表或流。相反,它基于等式\ phi ^ n = F_ {n-1} + F_n * \ phi,对于\ phi黄金比例。 (这是&#34; Binet公式&#34;的一个版本。)使用这个等式的问题是\ phi是不合理的(涉及5的平方根)所以它会因有限精度而发散如果使用Float-numbers天真地解释算术。但是,由于\ phi ^ 2 = 1 + \ phi,很容易用a和b整数形式a + b \ phi的数字实现精确计算,这就是下面的算法所做的。 (&#34; power&#34;函数在其中有一些优化,但实际上只是对这些数字的&#34; mult&#34; - 复制的迭代。)
type Zphi = (BigInt, BigInt)
val phi = (0, 1): Zphi
val mult: (Zphi, Zphi) => Zphi = {
(z, w) => (z._1*w._1 + z._2*w._2, z._1*w._2 + z._2*w._1 + z._2*w._2)
}
val power: (Zphi, Int) => Zphi = {
case (base, ex) if (ex >= 0) => _power((1, 0), base, ex)
case _ => sys.error("no negative power plz")
}
val _power: (Zphi, Zphi, Int) => Zphi = {
case (t, b, e) if (e == 0) => t
case (t, b, e) if ((e & 1) == 1) => _power(mult(t, b), mult(b, b), e >> 1)
case (t, b, e) => _power(t, mult(b, b), e >> 1)
}
val fib: Int => BigInt = {
case n if (n < 0) => 0
case n => power(phi, n)._2
}
编辑:一种更高效的实现,在某种意义上也更加惯用于基于Typelevel的Spire库进行数值计算和抽象代数。然后可以用更接近数学论证的方式来解释上述代码(我们不需要整个环结构,但我认为它在道德上是正确的并且包括它)。尝试运行以下代码:
import spire.implicits._
import spire.algebra._
case class S(fst: BigInt, snd: BigInt) {
override def toString = s"$fst + $snd"++"φ"
}
object S {
implicit object SRing extends Ring[S] {
def zero = S(0, 0): S
def one = S(1, 0): S
def plus(z: S, w: S) = S(z.fst + w.fst, z.snd + w.snd): S
def negate(z: S) = S(-z.fst, -z.snd): S
def times(z: S, w: S) = S(z.fst * w.fst + z.snd * w.snd
, z.fst * w.snd + z.snd * w.fst + z.snd * w.snd)
}
}
object Fibo {
val phi = S(0, 1)
val fib: Int => BigInt = n => (phi pow n).snd
def main(arg: Array[String]) {
println( fib(1000000) )
}
}