Source Списки ключевых слов и карты
Теперь давайте поговорим об ассоциативных структурах данных. Ассоциативные структуры данных могут сопоставить ключ с определенным значением. Разные языки называют их по-разному, например, словари, хеши, ассоциативные массивы и т. д.
В Elixir у нас есть две основные ассоциативные структуры данных: списки ключевых слов и карты.
Списки ключевых слов
Списки ключевых слов — это структура данных, используемая для передачи опций функциям. Представьте, что вы хотите разбить строку чисел. Мы можем использовать String.split/2:
iex> String.split("1 2 3", " ")
["1", "2", "3"]
Однако, что произойдет, если между числами есть дополнительный пробел:
iex> String.split("1 2 3", " ")
["1", "", "2", "", "3"]
Как вы видите, теперь в наших результатах есть пустые строки. К счастью, функция String.split/3 позволяет установить опцию trim в значение true:
iex> String.split("1 2 3", " ", [trim: true])
["1", "2", "3"]
[trim: true] — это список ключевых слов. Кроме того, когда список ключевых слов является последним аргументом функции, мы можем опустить скобки и записать:
iex> String.split("1 2 3", " ", trim: true)
["1", "2", "3"]
Как показано в примере выше, списки ключевых слов в основном используются в качестве необязательных аргументов функций.
Как следует из названия, списки ключевых слов — это просто списки. В частности, это списки, состоящие из пар кортежей по 2 элемента, где первый элемент (ключ) — атом, а второй элемент может быть любым значением. Оба представления одинаковы:
iex> [{:trim, true}] == [trim: true]
true
Поскольку списки ключевых слов являются списками, мы можем использовать все операции, доступные для списков. Например, мы можем использовать ++ для добавления новых значений в список ключевых слов:
iex> list = [a: 1, b: 2] [a: 1, b: 2] iex> list ++ [c: 3] [a: 1, b: 2, c: 3] iex> [a: 0] ++ list [a: 0, a: 1, b: 2]
Вы можете получить значение из списка ключевых слов, используя синтаксис в квадратных скобках. Это также известно как синтаксис доступа, так как он определяется модулем Access:
iex> list[:a] 1 iex> list[:b] 2
В случае дублирующихся ключей, значения, добавленные в начало, являются теми, которые извлекаются:
iex> new_list = [a: 0] ++ list [a: 0, a: 1, b: 2] iex> new_list[:a] 0
Списки ключевых слов важны, потому что они обладают тремя особыми характеристиками:
- Ключи должны быть атомами.
- Ключи упорядочены, как указано разработчиком.
- Ключи могут быть заданы более одного раза.
Например, библиотека Ecto использует эти функции для предоставления элегантного DSL для написания запросов к базе данных:
query =
from w in Weather,
where: w.prcp > 0,
where: w.temp < 20,
select: w
Хотя мы можем сопоставлять шаблоны со списками ключевых слов, это не делается на практике, так как сопоставление шаблонов со списками требует совпадения количества элементов и их порядка:
iex> [a: a] = [a: 1] [a: 1] iex> a 1 iex> [a: a] = [a: 1, b: 2] ** (MatchError) no match of right hand side value: [a: 1, b: 2] iex> [b: b, a: a] = [a: 1, b: 2] ** (MatchError) no match of right hand side value: [a: 1, b: 2]
Кроме того, поскольку списки ключевых слов часто используются в качестве необязательных аргументов, они используются в ситуациях, когда не все ключи могут быть присутствуют, что сделало бы невозможным сопоставление шаблонов с ними. Короче говоря, не сопоставляйте шаблоны со списками ключевых слов.
Для управления списками ключевых слов Elixir предоставляет модуль Keyword. Однако помните, что списки ключевых слов — это просто списки, и как таковые они обеспечивают те же линейные характеристики производительности, что и они: чем длиннее список, тем дольше потребуется для поиска ключа, подсчета элементов и т. д. Если вам нужно хранить большое количество ключей в структуре данных ключ-значение, Elixir предлагает карты, которые мы скоро изучим.
do-блоки и ключевые слова
Как мы видели, ключевые слова в основном используются в языке для передачи необязательных значений. На самом деле, мы уже использовали ключевые слова в этом руководстве. Например, мы видели:
iex> if true do ...> "This will be seen" ...> else ...> "This won't" ...> end "This will be seen"
Случается, что do блоки — это всего лишь синтаксическая удобство поверх ключевых слов. Мы можем переписать вышеизложенное следующим образом:
iex> if true, do: "This will be seen", else: "This won't" "This will be seen"
Обратите пристальное внимание на оба синтаксиса. В формате списка ключевых слов мы разделяем каждую пару ключ-значение запятыми, и каждый ключ следует за :. В do-блоках мы избавляемся от двоеточий, запятых и разделяем каждое ключевое слово новой строкой. Они полезны именно потому, что устраняют избыточность при написании блоков кода. В большинстве случаев вы будете использовать синтаксис блоков, но полезно знать, что они эквивалентны.
Это играет важную роль в языке, поскольку позволяет синтаксису Elixir оставаться компактным, но при этом выразительным. Нам нужны всего несколько структур данных для представления языка, тема, к которой мы вернёмся, когда будем говорить об необязательном синтаксисе и подробно рассмотрим при обсуждении метапрограммирования.
Теперь, когда это улажено, давайте поговорим о картах.
Карты как пары ключ-значение
Всякий раз, когда вам нужно хранить пары ключ-значение, карты являются «идеальной» структурой данных в Elixir. Карта создается с помощью синтаксиса %{}.
iex> map = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> map[:a]
1
iex> map[2]
:b
iex> map[:c]
nil
По сравнению со списками ключевых слов, мы уже можем увидеть две отличия:
- Карты позволяют использовать любые значения в качестве ключей.
- Ключи карт не следуют никакому порядку.
В отличие от списков ключевых слов, карты очень полезны при сопоставлении шаблонов. Когда карта используется в шаблоне, она всегда будет соответствовать подмножеству данного значения:
iex> %{} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> %{:a => a} = %{:a => 1, 2 => :b}
%{2 => :b, :a => 1}
iex> a
1
iex> %{:c => c} = %{:a => 1, 2 => :b}
** (MatchError) no match of right hand side value: %{2 => :b, :a => 1}
Как показано выше, карта соответствует, если ключи в шаблоне существуют в данной карте. Следовательно, пустая карта соответствует всем картам.
Модуль Map предоставляет очень похожий API к модулю Keyword с удобными функциями для добавления, удаления и обновления ключей карт:
iex> Map.get(%{:a => 1, 2 => :b}, :a)
1
iex> Map.put(%{:a => 1, 2 => :b}, :c, 3)
%{2 => :b, :a => 1, :c => 3}
iex> Map.to_list(%{:a => 1, 2 => :b})
[{2, :b}, {:a, 1}]
Карты предопределенных ключей
В предыдущем разделе мы использовали карты как структуру данных ключ-значение, где ключи можно добавлять или удалять в любое время. Однако также обычно создавать карты с заранее определенным набором ключей. Их значения могут быть обновлены, но новые ключи никогда не добавляются и не удаляются. Это полезно, когда мы знаем форму данных, с которыми работаем, и если мы получаем другой ключ, это, вероятно, означает ошибку в другом месте.
Мы определяем такие карты, используя тот же синтаксис, что и в предыдущем разделе, за исключением того, что все ключи должны быть атомами:
iex> map = %{:name => "John", :age => 23}
%{name: "John", age: 23}
Как видно из напечатанного результата выше, Elixir также позволяет записывать карты с атомными ключами, используя тот же синтаксис key: value для списков ключевых слов.
Когда ключи являются атомами, особенно при работе с картами предопределенных ключей, мы также можем получить доступ к ним с помощью синтаксиса map.key.
iex> map = %{name: "John", age: 23}
%{name: "John", age: 23}
iex> map.name
"John"
iex> map.agee
** (KeyError) key :agee not found in: %{name: "John", age: 23}
Также есть синтаксис для обновления ключей, который также генерирует исключение, если ключ еще не определен:
iex> %{map | name: "Mary"}
%{name: "Mary", age: 23}
iex> %{map | agee: 27}
** (KeyError) key :agee not found in: %{name: "John", age: 23}
Эти операции обладают одним большим преимуществом — они генерируют исключение, если ключ не существует в карте, и компилятор даже может обнаруживать и предупреждать об этом, когда это возможно. Это делает их полезными для получения быстрой обратной связи и раннего обнаружения ошибок и опечаток. Это также синтаксис, используемый для реализации еще одной функции Elixir, называемой «Структуры», которую мы изучим позже.
Разработчики Elixir обычно предпочитают использовать синтаксис map.key и сопоставление шаблонов вместо функций в модуле Map при работе с картами, потому что они приводят к утвердительному стилю программирования. Эта статья в блоге от José Valim предоставляет представление и примеры того, как получить более лаконичное и быстрое программное обеспечение, написав утвердительный код на Elixir.
Вложенные структуры данных
Часто у нас будут карты внутри карт или даже списки ключевых слов внутри карт и так далее. Elixir предоставляет удобства для работы с вложенными структурами данных с помощью макросов put_in/2, update_in/2 и других, обеспечивая те же удобства, что и в императивных языках, сохраняя при этом неизменяемые свойства языка.
Представьте, что у вас есть следующая структура:
iex> users = [
john: %{name: "John", age: 27, languages: ["Erlang", "Ruby", "Elixir"]},
mary: %{name: "Mary", age: 29, languages: ["Elixir", "F#", "Clojure"]}
]
[
john: %{age: 27, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
У нас есть список ключевых слов пользователей, где каждое значение — это карта, содержащая имя, возраст и список языков программирования, которые каждый пользователь предпочитает. Если мы хотели получить доступ к возрасту Джона, мы могли бы написать:
iex> users[:john].age 27
Случается, что мы можем использовать тот же синтаксис для обновления значения:
iex> users = put_in users[:john].age, 31
[
john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#", "Clojure"], name: "Mary"}
]
Макрос update_in/2 похож, но позволяет передавать функцию, которая контролирует, как изменяется значение. Например, давайте удалим «Clojure» из списка языков Мэри:
iex> users = update_in users[:mary].languages, fn languages -> List.delete(languages, "Clojure") end
[
john: %{age: 31, languages: ["Erlang", "Ruby", "Elixir"], name: "John"},
mary: %{age: 29, languages: ["Elixir", "F#"], name: "Mary"}
]
Есть больше информации о put_in/2 и update_in/2, включая get_and_update_in/2, который позволяет извлечь значение и одновременно обновить структуру данных. Также есть put_in/3, update_in/3 и get_and_update_in/3, которые позволяют динамически получать доступ к структуре данных.
Заключение
Существует две разные структуры данных для работы с хранилищами ключ-значение в Elixir. Наряду с модулем Access и сопоставлением шаблонов, они предоставляют богатый набор инструментов для работы со сложными, потенциально вложенными структурами данных.
По завершении этого раздела важно помнить, что вы должны:
Использовать списки ключевых слов для передачи необязательных значений функциям
Использовать карты для общих структур данных ключ-значение
Использовать карты при работе с данными, которые имеют предопределенный набор ключей
Теперь давайте поговорим о модулях и функциях.
© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.16.3/keywords-and-maps.html