我写了一些代码来学习F#。 这是一个例子:
let nextPrime list=
let rec loop n=
match n with
| _ when (list |> List.filter (fun x -> x <= ( n |> double |> sqrt |> int)) |> List.forall (fun x -> n % x <> 0)) -> n
| _ -> loop (n+1)
loop (List.max list + 1)
let rec findPrimes num=
match num with
| 1 -> [2]
| n ->
let temp = findPrimes <| n-1
(nextPrime temp ) :: temp
//find 10 primes
findPrimes 10 |> printfn "%A"
我很高兴它能奏效!
我完全是递归
的初学者递归是一件很棒的事情。
我认为 findPrimes 效率不高。
如果可能,有人帮我将findPrimes重构为尾递归吗?
顺便说一下,有没有更有效的方法来找到第一个n素数?答案 0 :(得分:4)
关于问题的第一部分,如果要以递归方式编写递归列表构建函数,则应将中间结果列表作为额外参数传递给函数。在你的情况下,这将是
let findPrimesTailRecursive num =
let rec aux acc num =
match num with
| 1 -> acc
| n -> aux ((nextPrime acc)::acc) (n-1)
aux [2] num
递归函数aux将其结果收集在一个额外的参数中,方便地称为acc(如在acc-umulator中)。当你达到结束条件时,只需吐出累积的结果。我已将尾递归辅助函数包装在另一个函数中,因此函数签名保持不变。
正如您所看到的,对aux的调用是唯一的,因此也是最后一次调用n&lt;&gt; 1例。它现在是尾递归的,并将编译成一个while循环。
我把你的版本和我的版本计时,产生2000个素数。我的版本速度提高了16%,但仍然相当慢。为了生成质数,我喜欢使用命令式阵列筛。不是很实用,但非常(非常)快。
答案 1 :(得分:3)
为什么不简单地写:
let isPrime n =
if n<=1 then false
else
let m = int(sqrt (float(n)))
{2..m} |> Seq.forall (fun i->n%i<>0)
let findPrimes n =
{2..n} |> Seq.filter isPrime |> Seq.toList
或筛子(非常快):
let generatePrimes max=
let p = Array.create (max+1) true
let rec filter i step =
if i <= max then
p.[i] <- false
filter (i+step) step
{2..int (sqrt (float max))} |> Seq.iter (fun i->filter (i+i) i)
{2..max} |> Seq.filter (fun i->p.[i]) |> Seq.toArray
答案 2 :(得分:3)
另一种方法是使用额外的continuation参数来使findPrimes尾递归。这项技术总能奏效。它将避免堆栈溢出,但可能不会使您的代码更快。
另外,我将你的nextPrime函数更接近我使用的样式。
let nextPrime list=
let rec loop n = if list |> List.filter (fun x -> x*x <= n)
|> List.forall (fun x -> n % x <> 0)
then n
else loop (n+1)
loop (1 + List.head list)
let rec findPrimesC num cont =
match num with
| 1 -> cont [2]
| n -> findPrimesC (n-1) (fun temp -> nextPrime temp :: temp |> cont)
let findPrimes num = findPrimesC num (fun res -> res)
findPrimes 10
正如其他人所说,有更快的方法来产生素数。
答案 3 :(得分:2)
顺便说一下,有没有更有效的方法来找到前n个素数?
我在F#here中描述了一个快速任意大小的Eratosthenes筛子,它将结果累积到一个不断增长的ResizeArray
中:
> let primes =
let a = ResizeArray[2]
let grow() =
let p0 = a.[a.Count-1]+1
let b = Array.create p0 true
for di in a do
let rec loop i =
if i<b.Length then
b.[i] <- false
loop(i+di)
let i0 = p0/di*di
loop(if i0<p0 then i0+di-p0 else i0-p0)
for i=0 to b.Length-1 do
if b.[i] then a.Add(p0+i)
fun n ->
while n >= a.Count do
grow()
a.[n];;
val primes : (int -> int)
答案 4 :(得分:2)
我知道这有点晚了,答案已经被接受了。但是,我认为OP或者其他任何人都可以对一个好的一步一步指导使尾部递归。以下是一些肯定帮助我的技巧。我将使用除了素数之外的一个直接的例子,因为正如其他人所说,有更好的方法来产生素数。
考虑一个count函数的简单实现,它将创建一个从一些n
倒数的整数列表。这个版本不是尾递归的,所以对于长列表,你会遇到堆栈溢出异常:
let rec countDown = function
| 0 -> []
| n -> n :: countDown (n - 1)
(* ^
|... the cons operator is in the tail position
as such it is evaluated last. this drags
stack frames through subsequent recursive
calls *)
解决此问题的一种方法是使用参数化函数应用延续传递样式:
let countDown' n =
let rec countDown n k =
match n with
| 0 -> k [] (* v--- this is continuation passing style *)
| n -> countDown (n - 1) (fun ns -> n :: k ns)
(* ^
|... the recursive call is now in tail position *)
countDown n (fun ns -> ns)
(* ^
|... and we initialize k with the identity function *)
然后,将此参数化函数重构为专用表示。请注意,函数countDown'
实际上并没有倒计时。这是在n > 0
时构建延续的方式的工件,然后在n = 0
时进行评估。如果您有类似第一个示例的内容并且无法弄清楚如何使其尾递归,那么我建议您编写第二个示例,然后尝试对其进行优化以消除函数参数k
。这肯定会提高可读性。这是第二个例子的优化:
let countDown'' n =
let rec countDown n ns =
match n with
| 0 -> List.rev ns (* reverse so we are actually counting down again *)
| n -> countDown (n - 1) (n :: ns)
countDown n []