Clojure:在指定位置从向量中删除项目

39

现在有没有一种方法可以根据索引从向量中删除一个元素,我目前正在使用subvec将向量拆分并重新创建。我正在寻找向量的关联操作的反向操作?

有没有一种类似于assoc(关联)的反向操作,可以根据索引从向量中删除元素呢?目前,我使用subvec将向量分割并重新创建以实现此功能。

9个回答

35

subvec可能是最好的方式。Clojure文档中说subvec是"O(1)非常快,因为结果向量与原始向量共享结构并且没有进行修剪"。另一种选择是遍历向量并在跳过某些元素时构建新向量,这会更慢。

从向量中间删除元素并不是向量擅长的事情。如果您经常需要这样做,请考虑使用哈希映射,以便可以使用dissoc

参见:


33
(defn vec-remove
  "remove elem in coll"
  [pos coll]
  (into (subvec coll 0 pos) (subvec coll (inc pos))))

除了选择你想要的mapv索引之外,这个方案的性能比其他所有建议都要好。但是我认为这个方案更简单,并且在我的基准测试中,性能只慢了大约2%。 - Jason

18
user=> (def a [1 2 3 4 5])
user=> (time (dotimes [n 100000] (vec (concat (take 2 a) (drop 3 a)))))
"Elapsed time: 1185.539413 msecs"
user=> (time (dotimes [n 100000] (vec (concat (subvec a 0 2) (subvec a 3 5)))))
"Elapsed time: 760.072048 msecs"

没错 - subvec 是最快的。


9
clojure.core.rrb-vector 是一个向量库,提供对数时间的串联和切片功能。如果您需要持久性,并且考虑到您的需求,对数时间解决方案是理论上最快的。具体而言,它比使用 clojure 的本地 subvec 的任何解决方案都要快得多,因为 concat 步骤会使任何此类解决方案变为线性时间。
(require '[clojure.core.rrb-vector :as fv])
(let [s (vec [0 1 2 3 4])]
  (fv/catvec (fv/subvec s 0 2) (fv/subvec s 3 5)))
; => [0 1 3 4]

5
我发现这个方案非常好:

以下是具体步骤:

(defn index-exclude [r ex] 
   "Take all indices execpted ex" 
    (filter #(not (ex %)) (range r))) 


(defn dissoc-idx [v & ds]
   (map v (index-exclude (count v) (into #{} ds))))

(dissoc-idx [1 2 3] 1 2)


'(1)

1
对于删除单个项目,这比使用subvec慢,但对于删除多个索引,这非常好。 - Torbjørn

4

subvec非常快;结合transients使用效果更佳。

使用criterium进行基准测试:

user=> (def len 5)
user=> (def v (vec (range 0 5))
user=> (def i (quot len 2))
user=> (def j (inc i))

; using take/drop
user=> (bench
         (vec (concat (take i v) (drop j v))))
;              Execution time mean : 817,618757 ns
;     Execution time std-deviation : 9,371922 ns

; using subvec
user=> (bench
         (vec (concat (subvec v 0 i) (subvec v j len))))
;              Execution time mean : 604,501041 ns
;     Execution time std-deviation : 8,163552 ns

; using subvec and transients
user=> (bench
         (persistent!
          (reduce conj! (transient (vec (subvec v 0 i))) (subvec v j len))))
;              Execution time mean : 307,819500 ns
;     Execution time std-deviation : 4,359432 ns

加长长度后速度提升更显著;同样的测量,当len等于10000时,平均值为:1,368250 ms953,565863 µs314,387437 µs

2
你使用的Clojure版本是哪个? 回溯4年,subvec与transient不兼容存在一个[open bug](http://dev.clojure.org/jira/browse/CLJ-787),因为`APersistentVector $ SubVector未实现IEditableCollection。 我假设这就是为什么示例在(vec(subvec ...))中包装subvec调用的原因,但在我使用Clojure 1.7进行测试时,那种情况下的vec仍然会返回clojure.lang.APersistentVector $ SubVector`,可能是优化,但我还没有深入研究Clojure源代码。 - Ryan Wilson
我认为是1.6.0版本。它在本回答写作几个月前发布。 - omiel
你能否更新到Clojure 1.8版本?正如@RyanWilson所提到的,你的瞬态示例在1.8中无法工作。 - Ahmed Fasih
3
在你的最后一个示例中,将vec替换为into []可以在Clojure 1.8中使用,但这会极大地降低性能 :(. 我所能得到的最快速度是(into (subvec v 0 i) (subvec v j)),在我的电脑上比你的“使用subvec”中间版本快约15%... - Ahmed Fasih

3

获取所需索引可能更快。

(def a [1 2 3 4 5])

(def indexes [0 1 3 4])

(time (dotimes [n 100000] (vec (concat (subvec a 0 2) (subvec a 3 5)))))
"Elapsed time: 69.401787 msecs"

(time (dotimes [n 100000] (mapv #(a %) indexes)))
"Elapsed time: 28.18766 msecs"

2

另一个可能性是使用任何序列,并且不会因为索引超出范围而失败...

(defn drop-index [col idx]
  (filter identity (map-indexed #(if (not= %1 idx) %2) col)))

0
也许不是最快的,但也许是最贴近口语的?
(defn remove-n [n coll]
  (let [indexed (map-indexed vector coll)
        cleaned (remove #(= n (first %)) indexed)]
    (map second cleaned)))

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接