当我偶然发现一种名为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#中实现?它甚至可能吗?
答案 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筛都只是一项有趣的智力练习,并没有实际用途,因为它比其他所有其他合理优化的筛子慢得多并且记忆力差得多。