В предыдущем посте мы уже узнали, как использовать списки и о важности этой структуры данных в Clojure. Теперь мы познакомимся с другой важной структурой данных: Вектор.
Что такое векторы?
Векторы, как и списки, — это неизменяемые постоянные коллекции данных, которые могут содержать любое значение. Их можно создавать с помощью функции vec, передавая коллекцию значений или передавая значения непосредственно между квадратными скобками.
;; (vec collection)
(vec '("Red" "Green" "Blue"))
;; returns => ["Red" "Green" "Blue"]
["Gold" "Silver" "Crystal"]
;; returns => ["Gold" "Silver" "Crystal"]
Векторы и списки
И векторы, и списки — это коллекции данных, но они сильно отличаются друг от друга тем, как каждый из них работает. Первое основное различие, которое вы, вероятно, заметили между ними, заключается в том, что списки записываются с использованием круглых скобок, а векторы — с использованием квадратных скобок.
'("List" 1 2 3)
;; returns => ("List" 1 2 3)
["Vector" 1 2 3]
;; returns => ["Vector" 1 2 3]
На первый взгляд кажется, что они делают одно и то же, но структура данных, лежащая в основе этих коллекций, различна и имеет много отличий в практическом использовании.
(class '("List" 1 2 3))
;; returns => clojure.lang.PersistentList
["Vector" 1 2 3]
;; returns => clojure.lang.PersistentVector
В то время как списки, как нам кажется, представляют собой непрерывные значения в памяти, которые хранят значение и ссылку на следующее значение, векторы — это непрерывные значения в памяти, элементы которых проиндексированы и к ним можно получить быстрый доступ по их индексной позиции с помощью функции nth.
;; (nth collection index)
(nth ["Charmander" "Pikachu" "Buterfree"] 0)
;; returns => "Charmander"
(nth ["Charmander" "Pikachu" "Buterfree"] 2)
;; returns => "Buterfree"
Изображение: «Представление векторов», созданное автором, является материалом с авторским левом под нелицензией.
Добавление значений
Еще одно большое различие между ними — это когда мы используем функцию conj для добавления элемента в коллекцию, потому что эта функция быстрее добавит элемент в коллекцию:
- В списках быстрее добавить новый элемент в начало, так как для этого не нужно проходить весь список, чтобы добавить его на другую позицию.
- В векторах элемент добавляется в последнюю позицию, так как функция просто получает последний индекс и добавляет его после.
Изображение: «Conj vector vs list», созданное автором, является объектом авторского права по нелицензионной лицензии.
Мы можем убедиться в этом на некоторых примерах:
(conj '("Charmander" "Pikachu" "Buterfree") "Zubat")
;; returns => ("Zubat" "Charmander" "Pikachu" "Buterfree")
(conj ["Charmander" "Pikachu" "Buterfree"] "Zubat")
;; returns => ["Charmander" "Pikachu" "Buterfree" "Zubat"]
Векторы и списки
Мы можем использовать nth для эффективного доступа к значению по его позиции, но мы также можем использовать любые функции, которые мы уже видели в списках. Их можно использовать для итерации по вектору, как мы это делаем со списками и другими коллекциями.
first
Функция first возвращает элемент в начале вектора.
(first ["Brock" "Misty" "Lt. Surge" "Erika"])
;; returns => "Brock"
second
Функция second возвращает второй элемент вектора.
(second ["Brock" "Misty" "Lt. Surge" "Erika"])
;; returns => "Misty"
last
Функция last возвращает элемент в конце вектора.
(last ["Brock" "Misty" "Lt. Surge" "Erika"])
;; returns => "Erika"
rest
Функции rest возвращают вектор в виде списка без первого элемента.
(rest ["Brock" "Misty" "Lt. Surge" "Erika"])
;; returns => ("Misty" "Lt. Surge" "Erika")
Когда следует использовать списки вместо векторов?
Если вы собираетесь постоянно добавлять и извлекать значения в начале или в конце коллекции, то список будет намного эффективнее вектора.
С другой стороны, если вам нужно добавлять и извлекать значения в середине или в индексированных позициях, то вектор будет намного эффективнее списка.
Вы должны учитывать это при выборе, какой из них вы будете использовать, так как в большинстве случаев векторы используются чаще, чем списки.
Увидимся в следующей части!
Наличие двух разных коллекций, которые выглядят так похоже, а работают так по-разному, может быть немного запутанным на начальном этапе, особенно если вы не знаете, как создаются эти структуры данных, но не волнуйтесь, это станет естественным, когда вы привыкнете к Clojure.