我正在使用F#完成Project Euler难题(https://projecteuler.net/)。事实证明,许多谜题需要生成素数,这是一项昂贵的计算。
为了让我的生活更轻松,我想创建一个可以生成素数的模块,还可以将它们写入文本文件,这样我就可以在下次需要时查看它们。
这是我的主要生成器模块:
module Utilities
open System
open System.IO
open System.Reflection
module Primes =
//This is where the cache file is
let private cachePath =
Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)
+ "\PrimeCache.txt"
//Parses the file to an int list
let private readCache : int list =
let text = File.ReadAllText(cachePath).Trim()
text.Split([|','|])
|> Seq.filter(fun w -> w |> String.length > 0)
|> Seq.map(fun w -> Int32.Parse(w))
|> Seq.toList
//Writes an int list to the file
let writeCache cache =
let text = String.Join(",", cache |> Seq.map(fun n -> n.ToString()))
File.WriteAllText(cachePath, text)
//In-memory cache
let mutable cache = []
cache <- readCache
let private appendElement element list =
element::(list |> List.rev) |> List.rev
let private isPrimeInner (n : int) =
if n < 2 then false
else
let len = cache.Length
//Factor can't be greater than the square root
let factorLimit = Convert.ToInt32(Math.Ceiling(Math.Sqrt(Convert.ToDouble(n))))
let mutable i = 0
let mutable stop = false
let mutable isPrime = true
while stop = false do
//Get the next element from cache
let p = cache.[i]
i <- i+1
//Don't go past last index of cache
if i >= len
then stop <- true
else ()
//Don't check primes > sqrt(n)
if p > factorLimit
then stop <- true
else ()
//If its divisible by any prime, its not a prime
if n % p = 0
then
stop <- true
isPrime <- false
else ()
//Add primes to cache
if isPrime
then cache <- appendElement n cache
else ()
isPrime
let isPrime (n : int) : bool =
let result = isPrimeInner n
result
let getPrimes : int seq =
seq {
for p in cache do
yield p
let next = if cache.Length > 0
then cache.[cache.Length-1]+1
else 2
for n = next to Int32.MaxValue do
if isPrimeInner n
then yield n
else ()
}
let saveCache : unit =
writeCache cache
以下是我目前正在处理的问题的代码。 (实际问题是总计2,000,000以下的所有素数,但我现在测试1000):
module Problem10
let getAnswer =
let limit = 1000
let primes = Utilities.Primes.getPrimes
|> Seq.takeWhile(fun p -> p < limit)
let result = primes |> Seq.sum
Utilities.Primes.saveCache
result
这将给出正确答案,但会将空缓存文件保存回磁盘。如果我在调试器中逐步执行它,那么在saveCache
内设置的任何断点之前,对isPrimeInner
的调用是最先击中的事情之一。如果我将调用代码切换为:
module Problem10
let getAnswer =
let limit = 1000
let primes = Utilities.Primes.getPrimes
|> Seq.takeWhile(fun p -> p < limit)
let result = primes |> Seq.sum
let x = Utilities.Primes.cache
Utilities.Primes.writeCache x
result
缓存将使用新的素数正确保存。
我在C#之前看到过这种行为,当时误用了迭代器块。我不明白这两个版本的调用代码在运行时是如何不同的。我在这里做错了什么?