【问题标题】:Tail recursive copy of a seq to a list in F#将序列的尾递归副本复制到 F# 中的列表
【发布时间】:2011-03-31 00:13:27
【问题描述】:

我正在尝试通过递归地将序列的第一个元素附加到列表中来从序列中构建一个列表:

open System


let s = seq[for i in 2..4350 -> i,2*i]

let rec copy s res = 
     if  (s|>Seq.isEmpty)  then 
         res
     else
         let (a,b) = s |> Seq.head
         Console.WriteLine(string a)
         let newS = s |> Seq.skip(1)|> Seq.cache
         let newRes = List.append res ([(a,b)])
         copy newS newRes



copy s ([])

两个问题:

。得到一个堆栈溢出,这意味着我的尾部递归策略很糟糕

。为什么当我将|> Seq.cache 放在这里let newS = s |> Seq.skip(1)|> Seq.cache 时,代码会快100 倍。

(注意这只是一个小练习,我知道你可以做 Seq.toList 等。)

非常感谢

一种可行的方法是(这两点对我来说仍然有点奇怪):

let toList (s:seq<_>) =

    let rec copyRev res (enum:Collections.Generic.IEnumerator<_*_>) = 
         let somethingLeft = enum.MoveNext()
         if  not(somethingLeft)  then 
             res
         else
             let curr = enum.Current
             Console.WriteLine(string curr)
             let newRes = curr::res
             copyRev newRes enum

    let enumerator = s.GetEnumerator()
    (copyRev ([]) (enumerator)) |>List.rev

【问题讨论】:

  • 让 newRes = curr :: res - 小改进

标签: f# tail-recursion


【解决方案1】:

你说这只是一个练习,但指向我的答案很有用

While or Tail Recursion in F#, what to use when?

并重申您应该尽可能支持更多的应用性/声明性构造。例如

let rec copy2 s = [
    for tuple in s do
        System.Console.WriteLine(string(fst tuple))
        yield tuple
    ]

是表达您的特定功能的一种很好且高效的方式。

也就是说,如果我不说“永远不要创建这么大的列表”,我会感到失职。对于海量数据,您需要数组或序列。

【讨论】:

  • 谢谢布赖恩。不打算在现实生活中使用这个列表:),只是试图做一个我想要完成的事情的简单例子。 (我会改变seq的大小,让事情更合理)
  • 好吧,现在我认为您关于 StackOverflow 的问题毫无意义。 :)
  • arrg,你骗了我!!! ,我已经将更改反转为足以溢出但合理的东西:)
【解决方案2】:

根据我对 F# 的短暂经验,使用 Seq.skip 1 不是一个好主意,就像使用带有尾部的列表一样。 Seq.skip 创建一个新的 IEnumerable/sequence 而不仅仅是跳过 n。因此,您的函数将比List.toSeq 慢很多。你应该正确地做到这一点

s.GetEnumerator()

并遍历序列并保存一个列表,其中包含每个元素。

在这个问题上

Take N elements from sequence with N different indexes in F#

我开始做与您类似的事情,但发现它非常慢。请参阅我的方法以获取灵感。

补充:我写过这个:

let seqToList (xs : seq<'a>) =
    let e = xs.GetEnumerator()
    let mutable res = []

    while e.MoveNext() do
        res <- e.Current :: res

    List.rev res

发现内置方法实际上做了一些非常相似的事情(包括反向部分)。但是,它会检查您提供的序列实际上是列表还是数组。

您将能够使代码完全正常运行:(我现在也这样做了 - 无法抗拒 ;-))

let seqToList (xs : seq<'a>) =
        Seq.fold (fun state t -> t :: state) [] xs |> List.rev

【讨论】:

  • 感谢您的评论。还在想堆栈溢出为什么会发生?
  • 我帮不了你。我是 F# 的新手。
  • @jlezard 我认为 lassespeholt 实际上正处于堆栈溢出的轨道上。如果 Seq.skip 每次都按照建议创建一个新的可枚举,那么当您到达序列末尾时,copys 参数是一个非常嵌套的 IEnumerable 对象,而对 MoveNext 的链式调用可能就是溢出了。
【解决方案3】:

您的函数是正确的尾递归,因此递归调用本身并不是堆栈溢出的原因。相反,问题在于Seq.skip 在递归使用时表现不佳,正如其他人所指出的那样。例如,这段代码溢出了我机器上的堆栈:

let mutable s = seq { 1 .. 20001 }
for i in 1 .. 20000 do
  s <- Seq.skip 1 s
let v = Seq.head s

也许您可以看到与您自己的代码的模糊联系,这也最终占据了序列的头部,这是由于重复将Seq.skip 1 应用于您的初始序列。

【讨论】:

    【解决方案4】:

    试试下面的代码。

    警告:在运行此代码之前,您需要在 Visual Studio 中启用尾调用生成。这可以通过项目属性页面上的构建选项卡来完成。如果未启用,代码将 StackOverflow 处理继续。

    open System
    open System.Collections.Generic
    
        let s = seq[for i in 2..1000000 -> i,2*i]
    
        let rec copy (s : (int * int) seq) = 
            use e = s.GetEnumerator()
            let rec inner cont =
                if e.MoveNext() then 
                    let (a,b) = e.Current
                    printfn "%d" b
                    inner (fun l -> cont (b :: l))
                else cont []
            inner (fun x -> x)
    
        let res = copy s 
        printfn "Done"
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-08-10
      • 1970-01-01
      • 1970-01-01
      • 2016-05-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多