如何实现尾递归列表追加?

发布于 2024-09-02 03:43:54 字数 434 浏览 7 评论 0原文

像这样的简单追加函数(在 F# 中):

let rec app s t =
   match s with
      | [] -> t
      | (x::ss) -> x :: (app ss t)

当 s 变大时将会崩溃,因为该函数不是尾递归的。我注意到 F# 的标准追加函数不会因大列表而崩溃,因此必须以不同的方式实现。所以我想知道:append 的尾递归定义是什么样的?我想出了这样的方法:

let rec comb s t =
   match s with
      | [] -> t
      | (x::ss) -> comb ss (x::t)
let app2 s t = comb (List.rev s) t 

它有效,但看起来很奇怪。有没有更优雅的定义?

A simple append function like this (in F#):

let rec app s t =
   match s with
      | [] -> t
      | (x::ss) -> x :: (app ss t)

will crash when s becomes big, since the function is not tail recursive. I noticed that F#'s standard append function does not crash with big lists, so it must be implemented differently. So I wondered: How does a tail recursive definition of append look like? I came up with something like this:

let rec comb s t =
   match s with
      | [] -> t
      | (x::ss) -> comb ss (x::t)
let app2 s t = comb (List.rev s) t 

which works, but looks rather odd. Is there a more elegant definition?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

┊风居住的梦幻卍 2024-09-09 03:43:54

传统(非尾递归)

let rec append a b =
    match a, b with
    | [], ys -> ys
    | x::xs, ys -> x::append xs ys

使用累加器(尾递归)

let append2 a b =
    let rec loop acc = function
        | [] -> acc
        | x::xs -> loop (x::acc) xs
    loop b (List.rev a)

使用延续(尾递归)

let append3 a b =
    let rec append = function
        | cont, [], ys -> cont ys
        | cont, x::xs, ys -> append ((fun acc -> cont (x::acc)), xs, ys)
    append(id, a, b)

转换起来非常简单任何非尾递归函数都优于带有延续的递归函数,但我个人更喜欢累加器,因为它具有直接的可读性。

Traditional (not tail-recursive)

let rec append a b =
    match a, b with
    | [], ys -> ys
    | x::xs, ys -> x::append xs ys

With an accumulator (tail-recursive)

let append2 a b =
    let rec loop acc = function
        | [] -> acc
        | x::xs -> loop (x::acc) xs
    loop b (List.rev a)

With continuations (tail-recursive)

let append3 a b =
    let rec append = function
        | cont, [], ys -> cont ys
        | cont, x::xs, ys -> append ((fun acc -> cont (x::acc)), xs, ys)
    append(id, a, b)

Its pretty straight-forward to convert any non-tail recursive function to recursive with continuations, but I personally prefer accumulators for straight-forward readability.

一身软味 2024-09-09 03:43:54

除了 Juliet 发布的内容之外:

使用序列表达式
在内部,序列表达式生成尾递归代码,因此这工作得很好。

let append xs ys = 
  [ yield! xs
    yield! ys ]

使用可变 .NET 类型
David 提到 F# 列表可以发生变化 - 但这仅限于 F# 核心库(并且用户无法使用该功能,因为它破坏了功能概念)。您可以使用可变的 .NET 数据类型来实现基于突变的版本:

let append (xs:'a[]) (ys:'a[]) = 
  let ra = new ResizeArray<_>(xs)
  for y in ys do ra.Add(y)
  ra |> List.ofSeq

这在某些情况下可能很有用,但我通常会避免 F# 代码中的突变。

In addition to what Juliet posted:

Using sequence expressions
Internally, sequence expressions generate tail-recursive code, so this works just fine.

let append xs ys = 
  [ yield! xs
    yield! ys ]

Using mutable .NET types
David mentioned that F# lists can be mutated - that's however limited only to F# core libraries (and the feature cannot be used by users, because it breaks the functional concepts). You can use mutable .NET data types to implement a mutation-based version:

let append (xs:'a[]) (ys:'a[]) = 
  let ra = new ResizeArray<_>(xs)
  for y in ys do ra.Add(y)
  ra |> List.ofSeq

This may be useful in some scenarios, but I'd generally avoid mutation in F# code.

向地狱狂奔 2024-09-09 03:43:54

快速浏览一下 F# 源代码,尾部似乎在内部是可变的。一个简单的解决方案是在将第一个列表的元素连接到第二个列表之前反转第一个列表。加上反转列表,以递归方式实现尾部是微不足道的。

From a quick glance at the F# sources, it seems the tail is internally mutable. A simple solution would be to reverse the first list before consing its elements to the second list. That, along with reversing the list, are trivial to implement tail recursively.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文