Haskell - > F#:特纳的筛子

时间:2010-02-24 12:29:48

标签: haskell f# sieve

当我偶然发现一种名为Euler's Sieve的Eratosthenes筛子的改进版本时,我正在阅读不同的筛选算法。根据{{​​3}},在Haskell中实现了一个稍微不同的想法(称为Turner's Sieve)。

现在我正在尝试理解代码片段的确切内容,我认为我已经得到了它,但现在我想将代码转换为F#并且真的不知道从哪里开始。我主要担心的是,似乎没有“减去”两个序列的功能。

以下是代码:

import Data.OrdList (minus)

primes = euler [2..]
euler (p : xs) = p : euler (xs `minus` map (*p) (p : xs))

如何在F#中实现?它甚至可能吗?

4 个答案:

答案 0 :(得分:9)

如果你想计算像Haskell这样的无限列表的合并/差异之类的东西,那么LazyList类型(在F#power pack中找到)就会浮出水面。

它产生非常详细的代码,如下面的翻译:

#r "FSharp.PowerPack.dll"

//A lazy stream of numbers, starting from x
let rec numsFrom x = LazyList.consDelayed x (fun () -> numsFrom (x+1))

//subtracts L2 from L1, where L1 and L2 are both sorted(!) lazy lists
let rec lazyDiff L1 L2 =
    match L1,L2 with
        | LazyList.Cons(x1,xs1),LazyList.Cons(x2,xs2) when x1<x2 ->
            LazyList.consDelayed x1 (fun () -> lazyDiff xs1 L2)
        | LazyList.Cons(x1,xs1),LazyList.Cons(x2,xs2) when x1=x2 ->
            lazyDiff xs1 L2
        | LazyList.Cons(x1,xs1),LazyList.Cons(x2,xs2) when x1>x2 ->
            lazyDiff L1 xs2
        | _ -> failwith "Should not occur with infinite lists!"

let rec euler = function
    | LazyList.Cons(p,xs) as LL ->
        let remaining = lazyDiff xs (LazyList.map ((*) p) LL)
        LazyList.consDelayed p (fun () -> euler remaining)
    | _ -> failwith "Should be unpossible with infinite lists!"

let primes = euler (numsFrom 2)

> primes |> Seq.take 15 |> Seq.toList;;
val it : int list = [2; 3; 5; 7; 11; 13; 17; 19; 23; 29; 31; 37; 41; 43; 47]

请注意,我添加了两个failwith子句,以防止编译器抱怨不完整的匹配,即使我们知道计算中的所有列表都是(懒惰)无限的。

答案 1 :(得分:2)

您可以使用 seq 执行此操作。当你完成减去时, euler 本身与Haskell相同。

let rec minus xs ys =
  seq {
    match Seq.isEmpty xs, Seq.isEmpty ys with
    | true,_ | _,true -> yield! xs
    | _ ->
       let x,y = Seq.head xs, Seq.head ys
       let xs',ys' = Seq.skip 1 xs, Seq.skip 1 ys
       match compare x y with
       | 0 -> yield! minus xs' ys'
       | 1 -> yield! minus xs ys'
       | _ -> yield x; yield! minus xs' ys
  }

let rec euler s =
  seq {
    let p = Seq.head s
    yield p
    yield! minus (Seq.skip 1 s) (Seq.map ((*) p) s) |> euler
  }

let primes = Seq.initInfinite ((+) 2) |> euler

答案 2 :(得分:2)

通过序列,您可以通过坚持序列获得更多竞争力:

let rec euler s =
    seq {
        let s = Seq.cache s
        let p = Seq.head s
        yield p
        yield! minus (Seq.skip 1 s) (Seq.map ((*) p) s) |> euler
    }

答案 3 :(得分:2)

首先,必须要说的是,欧拉的素数筛不是“Eratosthenes筛子的改进版”,因为它在任何意义上的表现都比任何版本的Eratosthenes筛都要差得多:{{3} }

接下来,应该说使用LazyList的@cfem代码是一个忠实的,尽管你给出的Euler筛的版本的详细翻译,虽然它没有根据上面的链接仅筛选奇数的轻微改进。

应该注意的是,实施Euler筛没有任何意义,因为它比通过试验分区优化(TDO)找到质数更复杂和更慢,只是通过找到的素数到达正方形进行划分根据{{​​3}}测试的候选人数的根。

这个试验分区优化(TDO)筛选可以使用LazyList(引用FSharp.PowerPack.dll)在F#中实现:

let primesTDO() =
  let rec oddprimes =
    let rec oddprimesFrom n =
      if oddprimes |> Seq.takeWhile (fun p -> p * p <= n) |> Seq.forall (fun p -> (n % p) <> 0)
      then LazyList.consDelayed n (fun() -> oddprimesFrom (n + 2))
      else oddprimesFrom (n + 2)
    LazyList.consDelayed 3 (fun() -> oddprimesFrom 5)
  LazyList.consDelayed 2 (fun () -> oddprimes)

可以使用与以下相同形式的序列来实现:

let primesTDOS() =
  let rec oddprimes =
    let rec oddprimesFrom n =
      if oddprimes |> Seq.takeWhile (fun p -> p * p <= n) |> Seq.forall (fun p -> (n % p) <> 0)
      then seq { yield n; yield! oddprimesFrom (n + 2) }
      else oddprimesFrom (n + 2)
    seq { yield 3; yield! (oddprimesFrom 5) } |> Seq.cache
  seq { yield 2; yield! oddprimes }

序列版本比LazyList版本略快,因为它避免了调用时的一些开销,因为LazyList基于缓存序列。两者都使用一个内部对象,它表示到目前为止找到的素数的缓存副本,在LazyList的情况下自动缓存,在序列的情况下由Seq.cache自动缓存。要么能在大约两秒钟内找到前100,000个素数。

现在, Euler筛可以进行奇数筛分优化,并使用LazyList表示如下,由于知道输入列表参数是无限的并且比较匹配,因此消除了一个匹配情况简化了,我添加了一个运算符'^'来使代码更具可读性:

let primesE = //uses LazyList's from referenced FSharp.PowerPack.dll version 4.0.0.1
  let inline (^) a ll = LazyList.cons a (LazyList.delayed ll) //a consd function for readability
  let rec eulers xs =
    //subtracts ys from xs, where xs and ys are both sorted(!) infinite lazy lists
    let rec (-) xs ys =
      let x,xs',ys' = LazyList.head xs,LazyList.tail xs,LazyList.tail ys
      match compare x ( LazyList.head ys) with
        | 0 -> xs' - ys' // x == y
        | 1 -> xs - ys' // x > y
        | _ -> x^(fun()->(xs' - ys)) // must be x < y
    let p = LazyList.head xs
    p^(fun() -> (((LazyList.tail xs) - (LazyList.map ((*) p) xs)) |> eulers))
  let rec everyothernumFrom x = x^(fun() -> (everyothernumFrom (x + 2)))
  2^(fun() -> ((everyothernumFrom 3) |> eulers))

然而,必须注意的是,计算第1899个素数(16381)的时间分别对于primesTDO和primesTDOS约为0.2和0.16秒,而使用这个primesE为Euler的可怕性能约为2.5秒即使在这个小范围内,筛的时间也要超过十倍。除了糟糕的性能之外,primeE甚至无法将质数计算为3000,因为内存利用率会更差,因为它会记录快速增加的延迟执行函数,并且会增加找到的素数。

请注意,由于LazyList是一个值并且内置存储先前找到的元素,所以必须小心重复写入代码的时间,因此第二次相同的扫描将接近零时间;为了计时目的,最好使PrimeE成为PrimeE()中的函数,这样工作每次都从头开始。

总之,用包括F#在内的任何语言实施的Euler筛都只是一项有趣的智力练习,并没有实际用途,因为它比其他所有其他合理优化的筛子慢得多并且记忆力差得多。