为什么这个F#代码比C#代码慢?

时间:2014-11-11 21:40:36

标签: c# f#

我正在再次解决Project Euler问题(在我学习C#之前做了23个第一个问题)而且我对我解决问题5的解决方案的表现感到非常困惑。

内容如下:

  

2520是可以除以每个数字的最小数字   从1到10没有任何余数。

     

可被所有人整除的最小正数是多少   从1到20的数字?

现在,我在C#中使用了令人难以置信的原始蛮力解决方案,在大约25秒内解决了这个问题。

var numbers = Enumerable.Range(1, 20);
int start = 1;
int result;
while (true)
{
   if (numbers.All(n => start % n == 0))
   {
       result = start;
       break;
   }
   start++;
}

现在我的F#解决方案也使用暴力强制,但至少它会有更多的歧视,所以它“应该”在我的脑海中运行得更快,但它在约45秒时出现,所以它的速度几乎是C#one。

let p5BruteForce =
    let divisors = List.toSeq ([3..20] |> List.rev)
    let isDivOneToTwenty n =
        let dividesBy = 
            divisors |> Seq.takeWhile(fun x -> n % x = 0)                
        Seq.length dividesBy = Seq.length divisors

    let findNum n =
        let rec loop n = 
            match isDivOneToTwenty n with
            | true -> n
            | false -> loop (n + 2)
        loop n
    findNum 2520
P.S - 我知道我的解决方案可能会更好,在这种情况下,我只是想知道一个更好的蛮力解决方案可能比原始解决方案慢得多。

3 个答案:

答案 0 :(得分:11)

您可以使用List.forall代替转换为seq,然后执行Seq.length

let divisors = [3..20] |> List.rev
let isDivOneToTwenty n = divisors |> List.forall (fun d -> n % d = 0)

Seq.length将需要遍历整个序列以确定元素的数量,而forall可以在元素未通过谓词时立即返回。

您也可以将findNum写为:

let rec findNum n = if isDivOneToTwenty n then n else findNum (n + 2)

答案 1 :(得分:2)

即使是更直接的翻译,例如

let numbers = { 1..20 }
let rec loop start =
    if numbers |> Seq.forall (fun n -> start % n = 0) 
    then start
    else loop (start + 1)
loop 1

需要一分半钟(你的C#版本在我的机器上也需要25秒)。数字序列似乎是将其更改为数组([| 1..20 |])并使用Array.forall将其降至8秒的罪魁祸首。使用数组的C#版本需要20秒(使用我自己的数组专用ForAll方法而不是Enumerable.All需要17秒。

编辑:在看到李的回答之后,我尝试了List.forall,它比阵列(~5秒)更快。

答案 2 :(得分:0)

好吧,它必须是这一点

            divisors |> Seq.takeWhile(fun x -> n % x = 0)                
    Seq.length dividesBy = Seq.length divisors

我认为你可以将它重写为一个更简单的递归函数,它与你原来的c#实现更相似。