【问题标题】:how to make these simple functions tail recursive in f#如何在 f# 中使这些简单的函数尾递归
【发布时间】:2012-02-21 17:27:24
【问题描述】:

我有这两个功能

//Remove all even indexed elements from a list and return the rest
let rec removeEven l =
match l with
| x0::x1::xs -> x1::removeEven (xs)
| [] -> []
| [_] -> []

//combine list members into pairs
let rec combinePair l =
match l with
| x0::x1::xs -> (x0,x1) :: combinePair(xs)
| [] -> []
| [_] -> []

那工作。

但我现在想,我还不如学习一点关于尾递归的知识,我很难掌握它。

这就是为什么我认为如果我可以得到一些帮助来制作我自己的尾递归函数,也许它会变得更清楚它是如何工作的,而不是阅读我可能不理解的地方的示例以及我自己的代码(请记住,我是一个完整的 f# 新手 :))

当然欢迎任何其他关于我的代码的建设性 cmets!

【问题讨论】:

  • 我认为是正确的。它应该删除偶数索引,而不是偶数元素,在这种情况下,1 具有索引 0,2 具有索引 1
  • 为什么removeEven [1;2] 返回[2]?我在回答中复制了它的行为,但它似乎应该被称为 returnEvenremoveOdd 或其他名称。
  • 抱歉删除我的评论。我改写了。那么 even 指的是索引吗?好的。
  • @Daniel :代码顶部的注释一直都这么说。 ;-]
  • @ildjarn:谁读代码 cmets?!? :-)

标签: list recursion f# tail-recursion


【解决方案1】:

在 F# 中使函数尾递归的一种典型方法是使用列表(在本例中为 acc)来累积结果并反转它以获得正确的顺序:

let removeEven l =
    let rec loop xs acc =
        match xs with        
        | [] | [_] -> acc
        | _::x1::xs' -> loop xs' (x1::acc)
    loop l [] |> List.rev

let combinePair l =
    let rec loop xs acc =
        match xs with        
        | [] | [_] -> acc
        | x0::x1::xs' -> loop xs' ((x0, x1)::acc)
    loop l [] |> List.rev

由于我们只是在每次递归调用 loop 后返回结果,因此这些函数是尾递归的。

你的函数看起来很不错,但我还有几个 cmets:

  • 缩进在 F# 中很重要。我更喜欢 match... withlec rec 声明后面几个空格。
  • 模式匹配案例应遵循一致的顺序。最好先从基本案例开始。
  • 只要有fun t -> match t with 的模式,function 关键字自然会用于缩短函数。
  • 最好去掉不必要的括号,尤其是在具有一个参数的函数中。

应用上面的cmets,你的函数变成如下:

// Remove all even indexed elements from a list and return the rest
let rec removeEven = function
    | [] | [_] -> []
    | _::x1::xs -> x1::removeEven xs

// Combine list members into pairs
let rec combinePair = function
    | [] | [_] -> []
    | x0::x1::xs -> (x0, x1)::combinePair xs

【讨论】:

  • 真的很好! thx 很多,这正是我想要的。不完全确定我理解“loop l [] |> List.rev”行的作用。我的意思是我知道它应该反转列表但是为什么循环 l []?什么时候可以到达那个记录代码?
  • | [] -> [] | [_] -> [] 也可以折叠成| [] | [_] -> [],节省两行。
  • @ildjarn:您的建议已被采纳 :)。
  • @PNS:该行与List.rev (loop l []) 相同。我们从一个空列表开始累积。当输入列表的元素少于 2 个时,我们返回累加器列表并将其反转。
【解决方案2】:

如果您需要使用更多内存的速度较慢、可维护性较差的方式来执行此操作,则可以使用延续。

let removeEven items = 
  let rec loop f = function
    | _::h::t -> loop (fun acc -> f (h::acc)) t
    | [] | [_] -> f []
  loop id items

但是,嘿,它是尾递归的。

【讨论】:

  • 我认为结果需要反转
  • @TysonWilliams 不,先生。它没有。
  • 哦,是的。你是正确的。对不起,我错了。 (我知道我应该在发表评论之前对其进行测试:P)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-15
  • 1970-01-01
  • 2011-07-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多