将序列的尾递归副本复制到F#中的列表

时间:2010-08-17 13:51:00

标签: f# tail-recursion

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

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

4 个答案:

答案 0 :(得分:3)

你说这只是一个练习,但指出我对

的回答是有用的

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

并重申,如果可能,您应该支持更多的应用/声明性构造。 E.g。

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

是表达特定功能的一种不错且高效的方式。

那就是说,如果我不说“永远不会创建那么大的名单”,我会感到疏忽。对于海量数据,您需要数组或seq。

答案 1 :(得分:1)

在我使用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

答案 2 :(得分:1)

你的函数是正确的尾递归,所以递归调用本身不是溢出堆栈的东西。相反,问题是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应用到你的初始序列。

答案 3 :(得分:0)

请尝试以下代码。

警告:在运行此代码之前,您需要在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"