Идиоматический фрагмент последовательности в Clojure
В Python существует удобный способ получения частей списка под названием "slicing":
a = [1,2,3,4,5,6,7,8,9,10] # ≡ a = range(1,10)
a[:3] # get first 3 elements
a[3:] # get all elements except the first 3
a[:-3] # get all elements except the last 3
a[-3:] # get last 3 elements
a[3:7] # get 4 elements starting from 3rd (≡ from 3rd to 7th exclusive)
a[3:-3] # get all elements except the first 3 and the last 3
Играя с clojure.repl/doc
в Clojure, я нашел эквиваленты для всех из них, но я не уверен, что они идиоматичны.
(def a (take 10 (iterate inc 1)))
(take 3 a)
(drop 3 a)
(take (- (count a) 3) a)
(drop (- (count a) 3) a)
(drop 3 (take 7 a))
(drop 3 (take (- (count a) 3) a))
Мой вопрос: как отрезать последовательности в Clojure? Другими словами, каков правильный способ возврата разных частей последовательности?
Ответы
Ответ 1
Вы можете упростить все, используя count
, используя вместо этого drop-last
take-last
или drop-last
:
(def a (take 10 (iterate inc 1)))
(take 3 a) ; get first 3 elements
(drop 3 a) ; get all elements except the first 3
(drop-last 3 a) ; get all elements except the last 3
(take-last 3 a) ; get last 3 elements
(drop 3 (take 7 a)) ; get 4 elements starting from 3
(drop 3 (drop-last 3 a)) ; get all elements except the first and the last 3
И, как было предложено в комментариях ниже, вы можете использовать макрос ->>
для "нитки" нескольких операций вместе. Например, две последние строки также могут быть написаны так:
(->> a (take 7) (drop 3)) ; get 4 elements starting from 3
(->> a (drop-last 3) (drop 3)) ; get all elements except the first and the last 3
Я думаю, что эти два метода очень читабельны, если вы применяете только две операции к списку, но когда у вас есть длинная строка, такая как " take
, " map
, " filter
, " drop
, first
использовать макрос " ->>
сделать код намного проще читать и, вероятно, еще проще писать.
Ответ 2
Python понятие последовательности очень отличается от Clojure's.
В Python,
- последовательность представляет собой конечное упорядоченное множество, индексированное неотрицательными числами; а также
- список - это изменяемая последовательность, в которую вы можете добавить срезы или удалить фрагменты.
В Clojure,
- последовательность - это интерфейс, поддерживающий
first
, rest
и cons
; - список представляет собой неизменную последовательную коллекцию с
cons
(или rest
), добавляющими (или удаляющими) first
элементы (так или иначе, возвращающие списки, так измененные).
Ближайшей вещью в Clojure к списку Python является вектор. Как предлагает Adam Sznajder, вы можете нарезать его с помощью subvec
, хотя вы не можете добавлять или удалять фрагменты, как вы можете в Python.
subvec
- это быстрая операция с постоянным временем, в то время как drop
заставляет вас платить за количество subvec
элементов (take
за плату за элементы, которые вы проходите, но это те, которые вас интересуют).
Ваши примеры становятся...
(def a (vec (range 1 (inc 10))))
(subvec a 0 3)
; [1 2 3]
(subvec a 3)
; [4 5 6 7 8 9 10]
(subvec a 0 (- (count a) 3))
; [1 2 3 4 5 6 7]
(subvec a (- (count a) 3))
; [8 9 10]
(subvec a 3 (+ 3 4))
; [4 5 6 7]
(subvec a 3 (- (count a) 3))
; [4 5 6 7]
Ответ 3
Существует функция subvec
. К сожалению, он работает только с векторами, поэтому вам придется преобразовать последовательность:
http://clojuredocs.org/clojure_core/clojure.core/subvec
Ответ 4
Нарезка последовательности - это немного "запах кода" - последовательность в целом предназначена для последовательного доступа к элементам.
Если вы собираетесь делать много разрезов/конкатенаций, есть гораздо лучшие структуры данных, в частности, проверка реализации вектора RRB-Tree:
Это поддерживает очень эффективные subvec
и catvec
.