【问题标题】:Conversion to tail recursion转换为尾递归
【发布时间】:2011-06-30 06:46:13
【问题描述】:

大家好,我正在尝试熟悉函数式编程(尤其是 F#),但在构建尾递归函数时遇到了困难。我很擅长将基本递归(函数基本上每次调用一次调用自身)转换为尾递归,但我现在的情况稍微复杂一些。

在我的例子中,函数必须接受一个列表作为参数。调用该函数时,我必须从列表中删除第一个元素,然后使用列表的其余部分进行递归。然后我需要将我以某种方式删除的第一个元素应用于递归的结果。接下来,我删除第二个元素并做同样的事情(注意:当我说“删除第二个元素”时,即从原始列表中,因此递归传递的列表也包括第一个元素)。我对列表的第三个、第四个等元素执行相同的操作。

有没有办法将上述情况转换为尾递归函数?也许嵌套的尾递归函数???感谢您的任何回答。


好的,这是我的基本代码。这个特殊的是一个排列生成器(不过,我不太关心排列部分 - 这是我想要关注的递归):

let permutationsOther str =
  match str with
  | value :: [] ->
    [[value]]
  | _ ->
    let list = (List.map (fun a -> // This applies the remove part for every element a
      let lst = (List.filter (fun b -> b <> a) str) // This part removes element a from the list
      let permutedLst = permutations lst // recursive call
      consToAll a permutedLst // constToAll this is my own function which performs "cons" operation with a and every element in the list permutedLst
    ) str)
    List.reduce (fun acc elem -> elem @ acc) list // flatten list of lists produce by map into a single list

我希望这已经足够清楚 - 如果需要,我很乐意提供澄清。

顺便说一句,我找到了一种方法来重写这个特定的函数,使它只使用一个递归,但这只是一个侥幸,而不是一个明智的决定。但是,这让我很受鼓舞,可能有一个通用的方法可以将多次递归变成单次递归,但我还没有找到。

【问题讨论】:

  • 根据您的描述,您的函数进行了多次递归调用,这(IIRC)意味着您无法将代码转换为尾递归。您需要提出不同的递归公式才能在此处使用尾递归。
  • 噢——这就是我所害怕的。也许我会找到一种方法来“重新解释”我需要的东西——也许递归然后循环遍历列表——谁知道呢?感谢您的回复。
  • 那是完全错误的。您可以肯定地将其变成尾递归的东西。您只需要对功能的结构方式进行创意。从表面上看,您可以使用扫描而不是递归来做同样的事情。肯,你为什么不发布你当前的实现(或类似的东西)让我们玩一玩?
  • 我不太确定我知道你的函数应该做什么。你可以展示非尾递归版本作为示例吗?
  • Desco,他自己在问如何使用尾递归,那么他如何提供示例?

标签: f# functional-programming tail-recursion


【解决方案1】:

转换为CPS 应该可以解决问题:

注意 1: 示例的来源是直接在浏览器中输入的,因此可能包含错误 :(。但我希望它可以展示总体思路。

注意 2: consToAll 函数也应转换为 CPS:consToAll: 'T -> 'T list list -> ('T list list -> 'R) -> ' R

let remove x l = List.filter ((<>) x) l // from original post: should duplicates also be removed ???

let permute l =
    let rec loop k l = 
        match l with
        | [] -> k []
        | [value] -> k [[value]]
        | _ -> filter l [] l (fun r -> r |> List.reduce (fun acc elem -> elem @ acc) |> k )
    and filter l acc orig fk = 
        match l with
        | [] -> fk acc
        | x::xs ->
            remove x orig 
                |> loop (fun res ->
                    consToAll x res (fun rs -> filter xs (rs::acc) orig fk)                    
                    )
    loop id l

【讨论】:

  • 感谢您的回答 - 我需要一点时间才能掌握这个概念,但它看起来不错!关于“应该删除重复项”,我一直假设该列表不包含重复项,但后来提出了一个基于索引的 remove 函数,用于当重复项变得重要时
  • 我有一个问题:当您定义remove 时,您可以为谓词写(),因为运算符只是具有特殊名称的函数,对吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-09-27
  • 1970-01-01
  • 2023-01-13
  • 2019-09-14
  • 2016-01-06
  • 1970-01-01
相关资源
最近更新 更多