我正在和Haskell一起练习以了解语言(这太棒了)所以我去项目euler做了问题#2花了很长时间(~30-40s我不喜欢'知道要完成多长时间。我想知道为什么花了这么长时间,所以我尝试了同样的F#,C#Javascript和Python。 F#和C#花了几个ms来完成和javascript一样但是python花了比Haskell更多的时间。这是为什么?这些是我的实现
的Haskell
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
genFibs n maxVal =
if fib n < maxVal then
fib(n):(genFibs (n+1) maxVal)
else []
totalSum = sum (filter even (genFibs 1 4000000))
main = print totalSum
F#
let rec fib n =
match n with
| n when n < 2 -> 1
| n -> fib (n-1) + fib (n-2)
let rec genFibos n max =
match fib(n) < max with
| true -> fib(n)::(genFibos (n + 1) max)
| false -> []
genFibos 1 4000000
|> Seq.where (fun n -> n % 2 = 0)
|> Seq.sum
C#
static int Fib(int n)
{
return n < 2 ? 1 : Fib(n - 1) + Fib(n - 2);
}
public static int EvenFibs()
{
var n = 1;
var totalSum = 0;
var num = 1;
while(num < 4000000)
{
num = Fib(n);
if (num % 2 == 0) totalSum += num;
n += 1;
}
return totalSum;
}
的Python
knownVals = { }
def fib(n):
if n not in knownVals:
knownVals[n] = 1 if n < 2 else fib(n-1) + fib(n-2)
return knownVals[n]
n = 1
stillCounting = True
totalSum = 0
while stillCounting:
num = fib(n)
if num > 4000000:
stillCounting = False
else:
if num % 2 == 0:
totalSum += num
n += 1
print(totalSum)
的Javascript
(function () {
function fib(n) {
return n < 2 ? 1 : fib(n - 1) + fib(n - 2);
}
var totalSum = 0;
var num = 1;
var n = 1;
while(num < 4000000)
{
num = fib(n);
if (num % 2 == 0) totalSum += num;
n += 1;
}
alert(totalSum);
})();
那么有人可以解释为什么Haskell和Python很慢而且F#和C#在这方面很快以及如何增强它?任何帮助将不胜感激!
编辑:更正了Haskell代码,使用memoization实现Python更好的实现
答案 0 :(得分:3)
你的Haskell实现完全错误:它永远不会完成,因为:
fib n = (fib n - 1) + (fib n - 2)
与:
相同fib n = (fib n) - 1 + ((fib n) - 2)
分歧。
正确的实施:
fib 0 = 1
fib 1 = 1
fib n = fib (n-1) + fib (n-2)
genFibs n maxVal
| fib n < maxVal = fib n : genFibs (n+1) maxVal
| otherwise = []
totalSum = sum $ filter even $ genFibs 1 4000000
main = print totalSum
在我的机器上运行大约1秒钟:
$time ./fibo
4613732
real 0m1.334s
user 0m1.324s
sys 0m0.009s
python解决方案的问题在于解释器没有为双递归调用引入任何类型的优化或memoization。这意味着算法需要指数时间来计算答案。提出多项式时间算法非常容易:
def fibo(n):
prev, cur = 0, 1
for i in range(n):
prev, cur = cur, prev + cur
return cur
结果是:
In [8]: %%timeit
...: tot = 0
...: n = 0
...: while True:
...: num = fibo(n)
...: if num > 4000000:
...: break
...: elif num % 2 == 0:
...: tot += num
...: n += 1
...:
10000 loops, best of 3: 54.8 µs per loop
我对F#和C#的速度的猜测是编译器引入了某种形式的memoization以避免指数增长。虽然问题可能仍然太小,无法注意到函数调用的指数增长。如果你试图将4000000增加到400亿或更多,你绝对可以检查这是否属实。
答案 1 :(得分:1)
尝试记忆它
known_fibs={}
def fib(n):
if n not in known_fibs:
known_fibs[n] = 1 if n < 2 else fib(n-1) + fib(n-2)
return known_fibs[n]
答案 2 :(得分:1)
你想知道所有时间的去向,对吧?不要把它当作测量时间。可以把它想象成在很大一部分时间内找到堆栈中的代码行,而不管总数。这是一个例子:How to improve performance of this code?
许多剖析器陷入了陷阱,包括忽略阻塞的时间,认为代码行无关紧要,认为“自我时间”的作用,并认为测量必须准确
答案 3 :(得分:0)
Fibonacci函数的递归定义是一种可怕的方法来解决这个问题。这是一个基本上即时运行的JavaScript实现:
function evenFibSum(limit) {
var
pf, f, t, sum;
for (pf = 1, f = 2, sum = 0; f <= limit; t = f, f += pf, pf = t)
if (!(f & 1)) sum += f;
return sum;
}
console.log(evenFibSum(4000000));
在处理整个列表时递归生成每个值正在做太多工作,并且迭代计算值非常简单。记住递归定义有所帮助,但对我来说似乎不必要地复杂化。