如何尽可能有效地将一个元素追加到列表中
CLOJURE 代表勇敢和真实 的第 4 章有一个练习:创建一个 append
函数,将新条目附加到列表中。
最有效的方法是什么?
根据我对数据类型的一般理解,如果 conj 将元素添加到列表中,那就意味着一致地附加到列表要么是愚蠢的,要么选择使用列表类型是愚蠢的。
无论如何,我写的解决方案是这样的
(defn append
[lst item]
(into '() (conj (into '() lst) item)))
嗯,这实际上与我相信的相同
(defn append
[lst item]
(reverse (conj (reverse lst) item)))
,所以可能成本很高,因为我将列表反转了两次?
我能想到的另一种解决方案是
(defn append
[lst item]
(apply list (conj (apply vector lst) item)))
但它们似乎都遍历了值序列两次,所以我不明白为什么任何一个应该比另一个更好。
是否有完成任务的正确方法?
At the end of CLOJURE for the BRAVE and TRUE's Chapter 4 there's an exercise: make an append
function that appends a new entry to a list.
What's the most efficient way to do so?
From what I understand of datatypes in general, if conj
prepends elements to a list, that simply means that consistently appending to a list is either silly or the choice of using a list type was silly.
Anyway, the solution I've written is this
(defn append
[lst item]
(into '() (conj (into '() lst) item)))
Well, that's actually the same as
(defn append
[lst item]
(reverse (conj (reverse lst) item)))
I believe, so probably is costly because I reverse the list twice?
Another solution I could think of is
(defn append
[lst item]
(apply list (conj (apply vector lst) item)))
But they all seem to traverse the sequence of values twice, so I don't see why any one should be better than another.
Is there the proper way to accomplish the task?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
conj
在列表的开头而不是末尾添加新项目是有原因的。因为你必须遍历列表才能在其末尾添加一些内容。这不是很有效。这是因为链表的性质。这就是为什么在 Lisp 中你将新项目cons
放入列表中,并在最后反转
它。这样,您在构建列表时只需遍历该列表一次。在 Clojure 中,如果要在末尾添加到列表,不使用列表而是使用向量类型更为惯用。在向量上,
conj
将向右添加到末尾。但假设您想要遍历一个列表一次并添加到末尾。
实际上是这样的:
但正如我所说,如果你想在末尾重复添加,不要使用列表,而是使用向量和 conj 到末尾。
There is a reason, why
conj
adds a new item at the start of the list and not at its end. Because you have to traverse the list to add something at its end. This is not very efficient. And it is because of the nature of a linked list. That's why in lisp youcons
new items onto a list and at the very endreverse
it. This way, you traverse the list just once while building it.In Clojure, if you want to add to a list at the end, it is more idiomatic not to use a list but instead the vector type. On a vector,
conj
adds right to the end.But let's say, you want to traverse a list once and add to the end.
That is actually:
but as I said, if you want to add repeatedly at the end, don't use a list, but a vector and
conj
to the end.为了避免双重遍历,您可以使用经典的递归方法。像这样:
所以,它只是从头开始重建列表。
为了使其在 clojure 中的性能更好,您可以通过瞬态集合对其进行优化:
但是,是的,正如另一个答案所建议的那样,您应该为每个特定任务仔细选择正确的数据结构,因此在实践中您会想要使用向量。
从
concat
变体开始,它是免费的(因为它是惰性的),但它有一个已知的陷阱,即应用大量堆叠惰性函数时出现堆栈溢出:to avoid the double traversal, you could use the classic recursive approach. Something like this:
so, it just rebuilds the list from scratch.
To make it's performance better in clojure, you can optimize it with transient collection:
but yes, as another answer proposes, you should carefully pick the proper data structure for every specific task, so in practice you would want to use vector.
As of
concat
variant, it comes for free (since it is lazy), but it has this known pitfall, which is stack overflow on applying a lot of stacked lazy functions: