Data.Sequence 与 Data.DList 将数据附加到列表末尾
我正在编写一些需要经常附加到列表末尾的代码。我知道使用“++”效率很低。因此,我通过附加到头部来向后构建列表,然后在完成后反转它。我认为这是一种常见的初学者策略。
我宁愿一开始就按照正确的顺序构建它 - 但这意味着切换到新的数据结构。我正在考虑将 Data.Sequence 或 Data.DList 用于我的容器。我的列表由严格的 int 对组成,我不需要随机访问它。 Data.Sequence 和 Data.DList 的相对优点是什么?我还应该考虑其他容器吗?
I'm writing some code that needs to frequently append to the end of a list. I know that using "++" is inefficient. So instead I build up the list backwards by appending to the head, and then reverse it when I'm done. I gather that this a common beginner tactic.
I would rather build it up in the correct order to begin with - but that means switching to a new data structure. I'm considering using Data.Sequence or Data.DList for my container. My list consists of strict int pairs, and I don't need random access to it. What are the relative merits of Data.Sequence and Data.DList, and are there other containers I should consider?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
使用
Data.Sequence
还是DList
取决于您将如何使用结果列表。当您构建序列(例如在 Writer 计算中)以在最后转换为列表并使用它时,DList
非常有用。但是,如果您需要使用中间结果,例如:那么
DList
就非常糟糕,因为它每次都需要重新计算脊柱。在这种情况下,Data.Sequence
是更好的选择。如果您需要从序列中删除元素,Data.Sequence
也更好。但也许您甚至不需要做出这个决定。在计算结束时反转列表在 ML 和 Scheme 等严格的函数式语言中很常见,但在 Haskell 中却不常见。以
map
的两种编写方式为例:在严格的语言中,
map_ii
会很糟糕,因为它使用线性堆栈空间,而map_i
> 是尾递归。但由于 Haskell 很懒,所以map_i
效率较低。map_ii
可以消耗输入的一个元素并产生输出的一个元素,而map_i
在产生任何输出之前消耗整个输入。尾递归并不是 Haskell 中高效实现的圣杯。当生成像列表这样的数据结构时,您实际上想要协同递归;也就是说,在构造函数的应用程序下进行递归调用(例如上面的
fx : map_ii f xs
)。因此,如果您发现自己在尾递归函数之后发生逆转,请看看是否可以将整个过程分解为核心递归函数。
Whether to use
Data.Sequence
orDList
depends on how you are going to be using the resulting list.DList
is great when you are building up a sequence, say in aWriter
computation, to convert to a list at the end and use it. However, if you need to use the intermediate results, like, say:then
DList
is pretty bad, because it needs to recompute the spine each time.Data.Sequence
is a better choice in this situation.Data.Sequence
is also better if you need to remove elements from the sequence.But maybe you don't even need to make this decision. Reversing lists at the end of a computation is common in strict functional languages like ML and Scheme, but not in Haskell. Take, for example, these two ways of writing
map
:In a strict language,
map_ii
would be horrible because it uses linear stack space, whereasmap_i
is tail recursive. But because Haskell is lazy,map_i
is the inefficient one.map_ii
can consume one element of the input and yield one element of the output, whereasmap_i
consumes the whole input before yielding any output.Tail recursion isn't the holy grail of efficient implementation in Haskell. When producing a data structure like a list, you actually want to be co-recursive; that is, make the recursive call underneath an application of a constructor (eg.
f x : map_ii f xs
above).So if you find yourself reversing after a tail-recursive function, see if you can factor the whole lot into a corecursive function.
我做了一个简单的标准比较:
结果:
结论:使用
Data.Sequence
。它具有最大的灵活性,但性能仅比DList
低一点 - 它可能不值得。I did a simple criterion comparison:
With the result:
Conclusion: Use
Data.Sequence
. It has the most flexibility while just a notch lower performance thanDList
- it's probably not worth it.