Есть ли способ удалить элемент из вектора на основе индекса, на данный момент я использую subvec для разделения вектора и воссоздания его снова. Я ищу реверс assoc для векторов?
Clojure Удалить элемент из вектора в указанном месте
Ответы (7)
subvec
, вероятно, лучший способ. В документах Clojure говорится, что subvec
— это «O(1) и очень быстро, так как результирующий вектор имеет общую структуру с оригиналом и обрезка не выполняется». Альтернативой может быть обход вектора и построение нового с пропуском определенных элементов, что будет медленнее.
Удаление элементов из середины вектора — это не то, в чем векторы хороши. Если вам приходится делать это часто, рассмотрите возможность использования хэш-карты, чтобы вы могли использовать dissoc
.
Видеть:
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
обеспечивает логарифмическую конкатенацию времени и срезы. Предполагая, что вам нужна настойчивость, и учитывая то, о чем вы просите, логарифмическое решение времени является настолько быстрым, насколько это теоретически возможно. В частности, это намного быстрее, чем любое решение, использующее собственный subvec
clojure, поскольку шаг 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
быстро; в сочетании с переходными процессами это дает еще лучшие результаты.
Использование критерия для оценки:
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
.
APersistentVector$SubVector does not implement IEditableCollection
. Я предполагаю, что именно поэтому пример оборачивает вызов subvec в (vec (subvec ...))
, хотя в моих тестах с clojure 1.7, vec
в этом случае по-прежнему будет возвращать clojure.lang.APersistentVector$SubVector
, предположительно в качестве оптимизации с тех пор, но я не копался в источнике clojure для этого.
- person Ryan Wilson; 21.07.2015
vec
в вашем последнем примере на into []
работает с Clojure 1.8, но это абсолютно убивает производительность :(. Самое быстрое, что я могу получить, это (into (subvec v 0 i) (subvec v j))
, что примерно на 15% быстрее, чем ваша средняя версия using subvec
на моем компьютере…
- person Ahmed Fasih; 28.04.2016
Еще одна возможность, которая должна работать с любой последовательностью, а не бомбить, если индекс выходит за пределы допустимого диапазона...
(defn drop-index [col idx]
(filter identity (map-indexed #(if (not= %1 idx) %2) col)))
Возможно, будет быстрее получить нужные вам индексы.
(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"