为什么这种天真的筛子实施如此缓慢

时间:2014-07-03 08:14:39

标签: f# sieve-of-eratosthenes

这是我的代码

let primes =
    let rec primesRec remain = 
        let prime = Seq.head remain
        seq { yield Seq.head remain; yield! primesRec (Seq.filter (fun n -> n % prime <> 0) remain)  }
    primesRec (Seq.initInfinite (fun i -> i + 2))

首先,我知道这不应该像筛子一样有效,但我认为它应该比它快得多。

Seq.take 100 primes |> List.ofSeq

已经花费了大量的时间(<1s),并且它完全冻结了1000(因为,我不想再等了)。

所以,据我所知,这个序列是通过获取剩余的第一个元素然后递归生成其他元素但过滤其余元素来构造的。 我错误地认为这是复杂性的二次方?它只是通过所有现有素数到它必须生成的每个新素数。

我知道,因为我想要第1000个素数,它实际上是该素数大小的二次方,大约是8000,但是仍然应该是大约10亿次迭代。不应该在一秒钟内执行吗?

1 个答案:

答案 0 :(得分:3)

对于每次迭代,它都没有记住remain seq,它会再次计算所有素数prime

关于存在100M迭代的理论可能是正确的,但请记住它不仅仅是做一些模运算:它创建序列和迭代器并分配内存并将内容推送到堆栈等等

我通过如下缓存remain seq来提高性能。但您也可以使用List代替seq来解决此问题。

let primes =
    let rec primesRec remain = 
        let prime = Seq.head remain
        seq { yield Seq.head remain; yield! primesRec (Seq.filter (fun n -> n % prime <> 0) (Seq.cache remain))  }
    primesRec (Seq.initInfinite (fun i -> i + 2))