简体   繁体   English

将seq的尾递归复制到F#中的列表

[英]Tail recursive copy of a seq to a list in F#

I am trying to build a list from a sequence by recursively appending the first element of the sequence to the list: 我试图通过将序列的第一个元素递归地添加到列表中来从序列中构建列表:

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 ([])

Two problems: 两个问题:

. getting a Stack overflow which means my tail recusive ploy sucks 出现堆栈溢出,这意味着我的尾部回缩策略很烂

and

. why is the code 100x faster when I put |> Seq.cache here let newS = s |> Seq.skip(1)|> Seq.cache . 为什么在我把|> Seq.cache这里时,代码快100倍, let newS = s |> Seq.skip(1)|> Seq.cache

(Note this is just a little exercise, I understand you can do Seq.toList etc.. ) (请注意,这只是一个小练习,我了解您可以执行Seq.toList等。)

Thanks a lot 非常感谢

One way that works is ( the two points still remain a bit weird to me ): 一种可行的方法是(这两点对我来说仍然很奇怪):

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

You say it's just an exercise, but it's useful to point to my answer to 您说这只是一种练习,但指向我的回答很有用

While or Tail Recursion in F#, what to use when? F#中的While或Tail Recursion,什么时候使用?

and reiterate that you should favor more applicative/declarative constructs when possible. 并重申在可能的情况下,您应该支持更多的应用性/声明性结构。 Eg 例如

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

is a nice and performant way to express your particular function. 是表达您特定功能的一种好方法。

That said, I'd feel remiss if I didn't also say "never create a list that big". 就是说,如果我不说“永远不要创建那么大的列表”,我会感到很失落。 For huge data, you want either array or seq. 对于海量数据,您需要数组或序列。

In my short experience with F# it is not a good idea to use Seq.skip 1 like you would with lists with tail. 以我对F#的短暂经验,像使用带尾的列表那样使用Seq.skip 1不是一个好主意。 Seq.skip creates a new IEnumerable/sequence and not just skips n. Seq.skip创建一个新的IEnumerable/sequence ,而不仅仅是跳过n。 Therefore your function will be A LOT slower than List.toSeq . 因此,您的功能将比List.toSeq慢很多。 You should properly do it imperative with 您应该正确地做到这一点

s.GetEnumerator()

and iterates through the sequence and hold a list which you cons every single element. 并遍历序列,并保留一个列表,供您约束每个单个元素。

In this question 在这个问题上

Take N elements from sequence with N different indexes in F# 从F#中具有N个不同索引的序列中获取N个元素

I started to do something similar to what you do but found out it is very slow. 我开始做与您相似的事情,但是发现它非常慢。 See my method for inspiration for how to do it. 请参阅我的方法以获取灵感。

Addition: I have written this: 另外:我写了这个:

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

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

    List.rev res

And found out that the build in method actually does something very similar (including the reverse part). 并发现该内置方法实际上做了一些非常相似的事情(包括相反的部分)。 It do, however, checks whether the sequence you have supplied is in fact a list or an array. 但是,它会检查您提供的序列实际上是列表还是数组。

You will be able to make the code entirely functional: (which I also did now - could'nt resist ;-)) 您将能够使代码完全起作用:(我现在也这样做了-无法抗拒;-))

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

Your function is properly tail recursive, so the recursive calls themselves are not what is overflowing the stack. 您的函数是正确的尾部递归,因此递归调用本身并不是堆栈溢出的内容。 Instead, the problem is that Seq.skip is poorly behaved when used recursively, as others have pointed out. 相反,问题是,如其他人所指出的,当递归使用Seq.skip时表现不佳。 For instance, this code overflows the stack on my machine: 例如,此代码溢出了我计算机上的堆栈:

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

Perhaps you can see the vague connection to your own code, which also eventually takes the head of a sequence which results from repeatedly applying Seq.skip 1 to your initial sequence. 也许您会看到与您自己的代码的模糊连接,这最终也占据了序列的开头,该序列是由于将Seq.skip 1重复应用于初始序列而产生的。

Try the following code. 请尝试以下代码。

Warning: Before running this code you will need to enable tail call generation in Visual Studio. 警告:在运行此代码之前,您将需要在Visual Studio中启用尾调用。 This can be done through the Build tab on the project properties page. 这可以通过项目属性页上的“构建”选项卡来完成。 If this is not enabled the code will StackOverflow processing the continuation. 如果未启用此功能,则代码将由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"

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM