【问题标题】:Why is this F# code slower than the C# equivalent?为什么这个 F# 代码比等效的 C# 代码慢?
【发布时间】:2014-11-11 21:40:36
【问题描述】:

我正在再次处理 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# 一。

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 - 我知道我的解决方案可能会更好,在这种情况下,我只是想知道一个更好的蛮力解决方案怎么会比原始解决方案慢得多。

【问题讨论】:

  • 请注意,解也必须是 2520 的倍数。因此,您的增量可以是 2520,您将要测试的数字要少得多。即使使用简单的蛮力解决方案,它也会很快完成。此外,您不需要测试 1 到 10 的整除性。
  • 不是C# / F# Performance comparison 的完全相同的副本。但它解释了为什么 F# 比 C# 慢
  • @L.B 真的吗?你要来这里对表演发表粗鲁的评论吗?我从未声称这是一个特别好的解决方案。不是每个人都天生具有出色的编码天赋,也许这就是我努力学习的原因?

标签: c# f#


【解决方案1】:

您可以使用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)

【讨论】:

  • 就是这样。实际上,List.forall 在我的测试中与Seq.forall 有很大的不同。就像 100 和 10 一样。
  • @mikez - 是的takeWhile 会提前中断,每次都会将该计数与整个除数序列的长度进行比较,因此每次调用都会遍历它一到两次。跨度>
  • 是的,我在写完评论后就意识到了这一点,所以我删除了它。
  • +1。这个简单的更改将我机器上的运行时间减少到大约 2.5 秒。这正是我想要的。
【解决方案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 秒)。

编辑:看到 Lee 的回答后,我尝试了 List.forall,它甚至比数组更快(约 5 秒)。

【讨论】:

    【解决方案3】:

    嗯,应该是这样的

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

    我认为您可以将其重写为更简单的递归函数,这将更类似于您原来的 c# 实现。

    【讨论】:

      猜你喜欢
      • 2011-08-31
      • 2023-03-05
      • 1970-01-01
      • 1970-01-01
      • 2013-02-15
      • 2018-11-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多