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

发布于 2024-09-14 13:01:39 字数 1173 浏览 6 评论 0原文

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

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

两个问题:

。出现堆栈溢出,这意味着我的尾部递归策略很

糟糕 为什么当我输入 |> 时,代码速度快了 100 倍? Seq.cache 此处 let newS = s |>序列跳过(1)|> Seq.cache

(注意这只是一个小练习,我知道你可以做 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

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.

(Note this is just a little exercise, I understand you can do Seq.toList etc.. )

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

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

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

发布评论

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

评论(4

蓝戈者 2024-09-21 13:01:39

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

F# 中的 while 或尾递归,何时使用什么?

并重申您应该尽可能支持更多应用性/声明性结构。 Eg

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

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

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

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?

and reiterate that you should favor more applicative/declarative constructs when possible. E.g.

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.

谁许谁一生繁华 2024-09-21 13:01:39

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

s.GetEnumerator()

并迭代序列并保存一个包含每个元素的列表。

在这个问题中

从序列中取出 N 个元素 N 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

In my short experience with F# it is not a good idea to use Seq.skip 1 like you would with lists with tail. Seq.skip creates a new IEnumerable/sequence and not just skips n. Therefore your function will be A LOT slower than 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#

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
み青杉依旧 2024-09-21 13:01:39

您的函数是正确的尾递归,因此递归调用本身并不是堆栈溢出的原因。相反,问题在于 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 所产生的序列的头部你的初始序列。

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. 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.

幻想少年梦 2024-09-21 13:01:39

尝试以下代码。

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

Try the following code.

Warning: Before running this code you will need to enable tail call generation in 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.

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