在Clojure中预先添加到矢量的惯用方法是什么?

时间:2010-11-04 10:28:09

标签: vector clojure append prepend

在列表之前很容易:

user=> (conj '(:bar :baz) :foo)
(:foo :bar :baz)

附加到矢量很简单:

user=> (conj [:bar :baz] :foo) 
[:bar :baz :foo]

如何(惯用)前置向量,同时取回向量? 这不起作用,因为它返回seq,而不是vector:

user=> (cons :foo [:bar :baz])     
(:foo :bar :baz)

这很难看(IMVHO):

user=> (apply vector (cons :foo [:bar :baz])) 
[:foo :bar :baz]

注意:我基本上只想要一个可以附加和前置的数据结构。附加到大型列表应该会有很大的性能损失,所以我想到了矢量..

5 个答案:

答案 0 :(得分:70)

矢量不是为前置而设计的。你只有O(n)前置:

user=> (into [:foo] [:bar :baz])
[:foo :bar :baz]

您想要的最有可能是finger tree

答案 1 :(得分:17)

我知道这个问题已经过时了,但没有人对差异说过什么 列表,因为你说你真的只想要一些你可以追加的东西 并且在前面,它听起来像差异列表可能会帮助你。 它们在Clojure中似乎并不受欢迎,但它们非常简单 实施并且比手指树复杂得多,所以我做了一个 微小的差异列表库,刚才(甚至测试过它)。这些 在O(1)时间内连接(前置或附加)。转换差异 列表返回列表应该花费你O(n),这是一个很好的权衡,如果 你做了很多连接。如果你没有做很多事情 连接,然后只是坚持列表,对吧? :)

以下是这个小型库中的函数:

dl:差异列表实际上是一个自己连接的函数 带有参数的内容并返回结果列表。每次 你产生一个差异列表,你正在创建一个小功能 就像数据结构一样。

dlempty:由于差异列表只是将其内容汇总到了 参数,空差异列表与身份相同 功能

undl:由于列表有什么不同,您可以转换一个 差异列表只是通过用nil调用它到正常列表,所以这个 功能不是真的需要;这只是为了方便。

dlcons:将一个项目汇总到列表的前面 - 不完全 必要的,但是进行是一个足够普遍的操作,它只是一个 单行(就像所有的功能一样)。

dlappend:汇总了两个差异列表。我认为它的定义是 最有趣的 - 看看吧! :)

现在,这是一个很小的库 - 5个单行函数,可以给你一个O(1) 追加/前置数据结构。不错,嗯?啊,Lambda的美丽 演算...

(defn dl
  "Return a difference list for a list"
  [l]
  (fn [x] (concat l x)))

; Return an empty difference list
(def dlempty identity)

(defn undl
  "Return a list for a difference list (just call the difference list with nil)"
  [aDl]
  (aDl nil))

(defn dlcons
  "Cons an item onto a difference list"
  [item aDl]
  (fn [x] (cons item (aDl x))))

(defn dlappend
  "Append two difference lists"
  [dl1 dl2]
  (fn [x] (dl1 (dl2 x))))

你可以看到它的实际效果:

(undl (dlappend (dl '(1 2 3)) (dl '(4 5 6))))

返回:

(1 2 3 4 5 6)

这也会返回同样的事情:

((dl '(1 2 3)) '(4 5 6))

享受不同的名单!

<强>更新

以下是一些可能更难理解的定义,但我认为更好:

(defn dl [& elements] (fn [x] (concat elements x)))
(defn dl-un [l] (l nil))
(defn dl-concat [& lists] (fn [x] ((apply comp lists) x)))

这可以让你说出这样的话:

(dl-un (dl-concat (dl 1) (dl 2 3) (dl) (dl 4)))

哪会返回

(1 2 3 4)

答案 2 :(得分:2)

正如用户optevo在指纹树下的评论中所说,你可以使用实现RRB树的clojure/core.rrb-vector lib:

  

RRB-Trees构建在Clojure的PersistentVectors上,添加了对数时间连接和切片。除了缺少vector-of函数外,ClojureScript支持相同的API。

我决定将此作为单独的答案发布,因为我认为这个库值得这样做。它支持ClojureScript,它由Michał Marczyk维护,他在Clojure社区中因实现各种数据结构而闻名。

答案 3 :(得分:1)

我建议使用便利功能built into the Tupelo Library。例如:

(append [1 2] 3  )   ;=> [1 2 3  ]
(append [1 2] 3 4)   ;=> [1 2 3 4]

(prepend   3 [2 1])  ;=> [  3 2 1]
(prepend 4 3 [2 1])  ;=> [4 3 2 1]
相比之下,使用原始Clojure很容易出错:

; Add to the end
(concat [1 2] 3)    ;=> IllegalArgumentException
(cons   [1 2] 3)    ;=> IllegalArgumentException
(conj   [1 2] 3)    ;=> [1 2 3]
(conj   [1 2] 3 4)  ;=> [1 2 3 4]

; Add to the beginning
(conj     1 [2 3] ) ;=> ClassCastException
(concat   1 [2 3] ) ;=> IllegalArgumentException
(cons     1 [2 3] ) ;=> (1 2 3)
(cons   1 2 [3 4] ) ;=> ArityException

答案 4 :(得分:1)

如果您不担心使用准引号,则此解决方案实际上非常优雅(对于“优雅”的某些定义):

> `[~:foo ~@[:bar :baz]]

[:foo :bar :baz]

我实际上有时在真实代码中使用此代码,因为声明性语法使它易于阅读,恕我直言。