Spec-Zone.ru › Elixir 1.18

Исходный код Список ключевых слов и карты

Теперь давайте поговорим об ассоциативных структурах данных. Ассоциативные структуры данных могут сопоставить ключ с определенным значением. Разные языки называют их по-разному, например, словари, хеши, ассоциативные массивы и т.д.

В 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"]

Мы также можем использовать параметры для ограничения алгоритма разделения максимальным количеством частей, как показано ниже:

iex> String.split("1  2  3", " ", [trim: true, parts: 2])
["1", "2  3"]

[trim: true] и [trim: true, parts: 2] являются списками ключевых слов. Когда список ключевых слов является последним аргументом функции, мы можем опустить скобки и написать:

iex> String.split("1  2  3", " ", trim: true, parts: 2)
["1", "2  3"]

Как показано в примере выше, списки ключевых слов в основном используются в качестве необязательных аргументов функций.

Как следует из названия, списки ключевых слов — это просто списки. В частности, это списки, состоящие из кортежей из 2 элементов, где первый элемент (ключ) является атомом, а второй элемент может быть любым значением. Обе записи эквивалентны:

iex> [{:trim, true}, {:parts, 2}] == [trim: true, parts: 2]
true

Списки ключевых слов важны, потому что они обладают тремя особыми характеристиками:

  • Ключи должны быть атомами.
  • Ключи упорядочены, как указано разработчиком.
  • Ключи могут быть указаны более одного раза.

Например, мы используем тот факт, что ключи могут повторяться, когда импортируем функции в Elixir:

iex> import String, only: [split: 1, split: 2]
String
iex> split("hello world")
["hello", "world"]

В приведенном выше примере мы импортировали как split/1 , так и split/2 из модуля String, что позволяет нам вызывать их без указания имени модуля. Мы использовали список ключевых слов для перечисления импортируемых функций.

Поскольку списки ключевых слов являются списками, мы можем использовать все операции, доступные для списков. Например, мы можем использовать ++ для добавления новых значений в список ключевых слов:

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> [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-блоки и ключевые слова

Как мы видели, ключевые слова в основном используются в языке для передачи необязательных значений. Фактически, мы использовали ключевые слова в предыдущих главах. Давайте посмотрим на макрос if/2:

iex> if true do
...>   "This will be seen"
...> else
...>   "This won't"
...> end
"This will be seen"

В приведенном выше примере блоки do и else составляют список ключевых слов. Они представляют собой просто синтаксическую удобность поверх списков ключевых слов. Мы можем переписать вышесказанное так:

iex> if true, do: "This will be seen", else: "This won't"
"This will be seen"

Обратите особое внимание на оба синтаксиса. Во втором примере используются списки ключевых слов, точно так же, как в примере String.split/3, поэтому мы разделяем каждую пару «ключ-значение» запятыми, а каждый ключ следует за :. В do-блоках мы используем отдельные слова, такие как do, else, и end, и разделяем их новой строкой. Они полезны именно при написании блоков кода. В большинстве случаев вы будете использовать синтаксис блоков, но полезно знать, что они эквивалентны.

Тот факт, что синтаксис блоков эквивалентен ключевым словам, означает, что нам нужно всего несколько структур данных для представления языка, что в целом упрощает его. Мы вернемся к этому вопросу, когда будем обсуждать необязательный синтаксис и метапрограммирование.

Теперь давайте поговорим о картах.

Карты как пары «ключ-значение»

В тех случаях, когда вам нужно хранить пары «ключ-значение», карты — это структура данных, которую нужно использовать в 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, что и для списков ключевых слов:

iex> map = %{name: "John", age: 23}
%{name: "John", age: 23}

Когда ключ является атомом, мы также можем получить к нему доступ, используя синтаксис map.key:

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 предоставляет удобства для работы со вложенными структурами данных с помощью макросов get_in/1, 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"}
]

У нас есть список ключевых слов пользователей, где каждое значение представляет собой карту, содержащую имя, возраст и список языков программирования, которые каждый пользователь предпочитает. Если мы хотели получить доступ к возрасту пользователя «john», мы могли бы написать:

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» из списка языков программирования пользователя «Mary»:

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"}
]

Резюме

Существует две разные структуры данных для работы с хранилищами «ключ-значение» в Elixir. Наряду с модулем Access и сопоставлением по образцу, они предоставляют богатый набор инструментов для обработки сложных, потенциально вложенных, структур данных.

В заключение этой главы, помните, что вы должны:

  • Использовать списки ключевых слов для передачи необязательных значений функциям

  • Использовать карты для общих структур данных «ключ-значение»

  • Использовать карты при работе с данными, имеющими предопределенный набор ключей

Теперь давайте поговорим о модулях и функциях.

← Предыдущая страница Бинарные данные, строки и списки символов
Следующая страница → Модули и функции

Скачать версию ePub

Создано с помощью ExDoc (v0.36.1) для языка программирования Elixir

© 2012-2024 The Elixir Team
Licensed under the Apache License, Version 2.0.
https://hexdocs.pm/elixir/1.18.1/keywords-and-maps.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API