Списки, векторы, map и множества

Знакомимся с четырьмя основными коллекциями Clojure и учимся выбирать подходящую.

Коллекция в Clojure — неизменяемая структура данных; любая «модификация» возвращает новую коллекцию, оставляя исходную нетронутой.

Четыре базовые коллекции

ТипЗаписьКогда использовать
Список (list)'(1 2 3)код, последовательная обработка с начала
Вектор (vector)[1 2 3]индексированный доступ, как массив
Отображение (map){:a 1 :b 2}пары ключ→значение, записи
Множество (set)#{1 2 3}уникальные элементы, проверка вхождения

Векторы

Вектор — самая частая коллекция, аналог массива/списка из других языков, но с быстрым доступом по индексу:

(def числа [10 20 30])
(nth числа 0)     ; => 10  (элемент по индексу)
(count числа)     ; => 3
(conj числа 40)   ; => [10 20 30 40]  (новый вектор, исходный цел)
числа             ; => [10 20 30]  (не изменился!)

Вывод:

10
3
[10 20 30 40]
[10 20 30]

Отображения (map)

Map хранит пары «ключ → значение» и заменяет в Clojure роль объектов:

(def книга {:название "Clojure" :год 2007})
(get книга :год)          ; => 2007
(:название книга)         ; => "Clojure"
(assoc книга :автор "Hickey") ; => новый map с автором
(keys книга)              ; => (:название :год)

Вывод:

2007
"Clojure"
{:название "Clojure", :год 2007, :автор "Hickey"}
(:название :год)

Множества

Множество хранит только уникальные элементы и быстро отвечает, есть ли что-то внутри:

(def цвета #{:красный :зелёный})
(contains? цвета :красный) ; => true
(conj цвета :красный)      ; => #{:красный :зелёный}  (дубликат игнорируется)
(conj цвета :синий)        ; => #{:красный :зелёный :синий}

Вывод:

true
#{:красный :зелёный}
#{:красный :зелёный :синий}

Как работает под капотом

Списки реализованы как односвязные: дешево добавлять в начало, дорого — обращаться по индексу. Векторы — как деревья с большим ветвлением, поэтому доступ по индексу почти константный. Map и set построены на хэш-деревьях. Важно: все они неизменяемы, и любая операция вроде conj или assoc создаёт новую версию, не трогая старую.

Частые ошибки

  • Ждать, что conj изменит коллекцию. Он возвращает новую; старая остаётся прежней. Сохраняйте результат.
  • Путать, куда conj добавляет. В вектор — в конец, в список — в начало.
  • Использовать список там, где нужен индексный доступ. Для этого есть вектор.

Итоги

  • Четыре коллекции: список, вектор, map, множество — каждая для своей задачи.
  • Все коллекции неизменяемы: операции возвращают новую структуру.
  • Вектор — для индексного доступа, map — для записей, set — для уникальности.
  • Keyword отлично подходит на роль ключей в map.
Проверьте себя
1. Как записывается вектор в Clojure?
A(1 2 3)
B[1 2 3]
C{1 2 3}
D#{1 2 3}
2. Что вернёт (conj [1 2 3] 4)?
A[1 2 3] (без изменений)
B[4 1 2 3]
C[1 2 3 4]
DОшибку, вектор изменять нельзя
3. Какая коллекция хранит только уникальные элементы?
AСписок
BВектор
CMap
DМножество (set)