Spec-Zone.ru › Elixir 1.17

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

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

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

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

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

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 при работе с картами, так как это приводит к утвердительному стилю программирования. Эта статья в блоге Хосе Валима предоставляет информацию и примеры о том, как получить более лаконичное и быстрое программное обеспечение, написав утвердительный код на 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"}
]

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

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

Есть больше информации о get_in/1, pop_in/1 и других, включая get_and_update_in/2, позволяющий извлечь значение и обновить структуру данных одновременно. Также есть get_in/3, put_in/3, update_in/3, get_and_update_in/3, pop_in/2, которые позволяют динамически обращаться к структуре данных.

Заключение

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

По завершении этой главы помните, что следует:

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

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

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

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

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

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

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

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

Spec-Zone.ru

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