Spec-Zone.ru › Elixir 1.16

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 и сопоставлением шаблонов, они предоставляют богатый набор инструментов для работы со сложными, потенциально вложенными структурами данных.

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

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

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

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

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

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

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

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

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

Spec-Zone.ru

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