F#用实际值替换变量导致无限循环(递归函数)

时间:2012-06-07 21:49:23

标签: recursion f# infinite-loop

我最近开始使用F#并实现了一个非常基本的递归函数,它代表了Eratosthenes的Sieve。我提出了以下工作代码:

static member internal SieveOfEratosthenesRecursive sequence accumulator =
    match sequence with
    | [] -> accumulator
    | head::tail -> let rest = tail |> List.filter(fun number -> number % head <> 0L)
                    let newAccumulator = head::accumulator
                    Prime.SieveOfEratosthenesRecursive rest newAccumulator

这个函数实际上没有内存效率,所以我试图消除变量“rest”和“newAccumulator”。我想出了以下代码

static member internal SieveOfEratosthenesRecursive sequence accumulator =
    match sequence with
    | [] -> accumulator
    | head::tail -> tail    |> List.filter(fun number -> number % head <> 0L) 
                            |> Prime.SieveOfEratosthenesRecursive (head::accumulator)

据我所知,我读过Prime.SieveOfEratosthenesRecursive的教程将使用过滤后的 tail 作为第一个参数和一个由 head :: accumulator 组成的列表来调用作为第二个。但是,当我尝试使用减少的变量用法运行代码时,程序会在无限循环中陷入陷阱。为什么会发生这种情况?我做错了什么?

2 个答案:

答案 0 :(得分:1)

  

据我所知,我读过Prime.SieveOfEratosthenesRecursive的教程将使用筛选后的tail作为第一个参数调用,并将head::accumulator列为第二个参数。

你有这个倒退。

在第一个版本中,您传递的是rest,然后是newAccumulator;在第二个版本中,您有效地传递了newAccumulator然后rest。即,你已经改变了论点。

Prime.SieveOfEratosthenesRecursive (head::accumulator)是一个部分函数应用程序,其中您将(head::accumulator)作为第一个参数(sequence)。这个部分函数应用程序产生一个一元函数(期望accumulator),您将在代码的第一个版本中传递(|>)所谓的rest

更改SieveOfEratosthenesRecursive的参数顺序是最简单的解决方案,但我会考虑以下惯用语:

static member internal SieveOfEratosthenesRecursive sequence accumulator =
    match sequence with
    | [] -> accumulator
    | head::tail ->
        tail
        |> List.filter(fun number -> number % head <> 0L) 
        |> Prime.SieveOfEratosthenesRecursive <| (head::accumulator)

static member internal SieveOfEratosthenesRecursive sequence accumulator =
    let inline flipzip a b = b, a
    match sequence with
    | [] -> accumulator
    | head::tail ->
        tail
        |> List.filter(fun number -> number % head <> 0L) 
        |> flipzip (head::accumulator)
        ||> Prime.SieveOfEratosthenesRecursive

FWIW,在这里删除restnewAccumulator作为命名变量不会影响你的内存使用量。

答案 1 :(得分:1)

第二个函数中的最后一个调用相当于:

Prime.SieveOfEratosthenesRecursive newAccumulator rest

你切换两个参数的位置。由于newAccumulator在每次递归调用后变大,因此您永远不会达到空列表的基本情况。

经验法则是最后使用最频繁变化的参数:

let rec sieve acc xs =
    match xs with
    | [] -> acc
    | x::xs' -> xs' |> List.filter (fun y -> y % x <> 0L) 
                    |> sieve (x::acc)

可以使用function关键字缩短上述功能:

let rec sieve acc = function
    | [] -> acc
    | x::xs' -> xs' |> List.filter (fun y -> y % x <> 0L) 
                    |> sieve (x::acc)

使用管道(|>)运算符只会使函数更具可读性,它根本不会影响内存使用。