r将此F#代码转换为尾部递归

r将此F#代码转换为尾部递归,f#,tail-recursion,F#,Tail Recursion,我写了一些代码来学习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

我写了一些代码来学习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"
let nexttime list=
让rec循环n=
匹配
||当(list |>list.filter(fun x->x double |>sqrt |>int))|>list.forall(fun x->n%x 0))->n
|循环(n+1)
循环(List.max List+1)
让rec找到times num=
匹配num
| 1 -> [2]
|n->
让temp=findTime打印fn“%A”
我很高兴它能正常工作

我完全是递归的初学者

递归是一件奇妙的事情

我认为FindTime效率不高

如果可能的话,有人帮我将FindTime重构为尾部递归吗

顺便说一句,有没有更有效的方法找到前n个素数?

为什么不简单地写:

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
让isPrime n=
如果n Seq.forall(乐趣i->n%i0)
设findPrimes n=
{2..n}|>Seq.filter isPrime |>Seq.toList
或筛子(非常快):

让generatePrimes最大=
设p=Array.create(max+1)为true
让rec过滤器i步骤=
如果我过滤(i+i)i)
{2..max}|>Seq.filter(乐趣i->p[i])|>Seq.toArray

关于问题的第一部分,如果要递归编写递归列表构建函数tail,应将中间结果列表作为额外参数传递给函数。在你的情况下,这可能是

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的调用是在n1情况下发生的唯一调用,因此也是最后一个调用。它现在是尾部递归的,将编译成while循环


我已经为你和我的版本计时,生成2000个素数。我的版本快了16%,但仍然相当慢。为了生成素数,我喜欢使用命令式数组筛选。不是很实用,但是非常(非常)快。

另一种方法是使用额外的continuation参数使FindTime尾部递归。这种技术总是有效的。它将避免堆栈溢出,但可能不会使代码更快

另外,我将您的NextTime函数放得更接近我将使用的样式

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
let nexttime list=
让rec循环n=if list |>list.filter(fun x->x*x list.forall(fun x->n%x 0)
然后n
else循环(n+1)
循环(1+List.head列表)
让rec查找dpromesc num cont=
匹配num
|1->cont[2]
|n->findPrimesC(n-1)(乐趣温度->下一时间温度::温度|>cont)
让findPrimes num=findPrimesC num(乐趣-恢复->恢复)
查找时间10
正如其他人所说,有更快的方法生成素数

顺便问一下,有没有更有效的方法找到前n个素数

我在F#中描述了一种快速的任意大小的埃拉托斯烯筛,它将其结果累积到一个不断增长的
重设数组中

> 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)
>让素数=
设a=调整数组大小[2]
让我们成长=
设p0=a[a.Count-1]+1
设b=Array.create为true
一个do中的di
让rec循环一次=
如果i int)

我知道这有点晚了,答案已经被接受了。然而,我相信一个好的循序渐进的指导,使一些东西尾部递归可能是感兴趣的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 *)
然后,将此参数化函数重构为专用表示形式。请注意,函数
倒计时“
实际上并不是倒计时。这是在
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 []

我不确定他是否真的那么担心获得最佳成绩;我认为他对理解递归更感兴趣。“GenerateTime”非常好。但是,我现在无法理解。我以后再研究。谢谢尹朱,谢谢。顺便说一句,blogspot.com在中国不可用。它被屏蔽了,就像youtube和twitter一样。
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 []