Идиоматический фрагмент последовательности в 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.