现在有没有一种方法可以根据索引从向量中删除一个元素,我目前正在使用subvec将向量拆分并重新创建。我正在寻找向量的关联操作的反向操作?
有没有一种类似于assoc(关联)的反向操作,可以根据索引从向量中删除元素呢?目前,我使用subvec将向量分割并重新创建以实现此功能。
subvec
可能是最好的方式。Clojure文档中说subvec
是"O(1)非常快,因为结果向量与原始向量共享结构并且没有进行修剪"。另一种选择是遍历向量并在跳过某些元素时构建新向量,这会更慢。
从向量中间删除元素并不是向量擅长的事情。如果您经常需要这样做,请考虑使用哈希映射,以便可以使用dissoc
。
参见:
subvec
subvec
,官方网站指向此处。(defn vec-remove
"remove elem in coll"
[pos coll]
(into (subvec coll 0 pos) (subvec coll (inc pos))))
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 是最快的。
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]
以下是具体步骤:
(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)
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 ms
,953,565863 µs
,314,387437 µs
。。 我假设这就是为什么示例在
(vec(subvec ...))中包装subvec调用的原因,但在我使用Clojure 1.7进行测试时,那种情况下的
vec仍然会返回
clojure.lang.APersistentVector $ SubVector`,可能是优化,但我还没有深入研究Clojure源代码。 - Ryan Wilsonvec
替换为into []
可以在Clojure 1.8中使用,但这会极大地降低性能 :(. 我所能得到的最快速度是(into (subvec v 0 i) (subvec v j))
,在我的电脑上比你的“使用subvec”中间版本快约15%... - Ahmed Fasih获取所需索引可能更快。
(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"
另一个可能性是使用任何序列,并且不会因为索引超出范围而失败...
(defn drop-index [col idx]
(filter identity (map-indexed #(if (not= %1 idx) %2) col)))
(defn remove-n [n coll]
(let [indexed (map-indexed vector coll)
cleaned (remove #(= n (first %)) indexed)]
(map second cleaned)))