Fork me on GitHub

Enum

Некоторое содержимое этого урока может оказаться устаревшим.
С момента последнего обновления перевода в оригинальный урок были внесены небольшие изменения.

Набор алгоритмов для операций с перечислениями.

Содержание

Enum

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

Этот урок содержит только небольшую часть доступных функций, однако мы можем изучить их самостоятельно. Давайте проведем небольшой эксперимент в IEx.

iex> Enum.__info__(:functions) |> Enum.each(fn({function, arity}) ->
...>   IO.puts "#{function}/#{arity}"
...> end)
all?/1
all?/2
any?/1
any?/2
at/2
at/3
...

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

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

all?

При использовании all?/2, как и большей части Enum в целом, мы передаем функцию, которая будет применяться к элементам коллекции. В случае с all?/2, если хотя бы один элемент коллекции не вернет true в качестве результата, то результатом будет false:

iex> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 3 end)
false
iex> Enum.all?(["foo", "bar", "hello"], fn(s) -> String.length(s) > 1 end)
true

any?

И наоборот, any?/2 вернет true, если хотя бы один элемент в коллекции выполнится в true:

iex> Enum.any?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 5 end)
true

chunk

Если требуется разбить коллекцию на меньшие группы, метод chunk/2 — это то, что нужно:

iex> Enum.chunk([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]

Также есть несколько дополнительных опций, которые вы можете посмотреть в официальной документации: chunk/2.

chunk_by

Если нужно разделить коллекцию по какому-то другому признаку кроме количества, можно воспользоваться функцией chunk_by/2. Она принимает перечисление и функцию. Когда возвращаемое функцией значение изменяется, начинается формирование новой группы:

iex> Enum.chunk_by(["one", "two", "three", "four", "five"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"]]
iex> Enum.chunk_by(["one", "two", "three", "four", "five", "six"], fn(x) -> String.length(x) end)
[["one", "two"], ["three"], ["four", "five"], ["six"]]

map_every

Порой недостаточно просто разбить коллекцию на части. Если коллекция упорядочена должным образом, можно воспользоваться функцией map_every/3, позволяющей пройтись только по интересующим нас элементам, начиная с первого:

# Применяем функцию к каждому 3-му элементу
iex> Enum.map_every([1, 2, 3, 4, 5, 6, 7, 8], 3, fn x -> x + 1000 end)
[1001, 2, 3, 1004, 5, 6, 1007, 8]

each

Когда нужно пройти по коллекции без создания нового значения, используется метод each/2:

iex> Enum.each(["one", "two", "three"], fn(s) -> IO.puts(s) end)
one
two
three
:ok

Замечание: Метод each/2 возвращает атом :ok.

map

Когда нужно применить функцию к каждому элементу и создать новую коллекцию, можно воспользоваться функцией map/2:

iex> Enum.map([0, 1, 2, 3], fn(x) -> x - 1 end)
[-1, 0, 1, 2]

min

Функция min/1 возвращает минимальное значение коллекции:

iex> Enum.min([5, 3, 0, -1])
-1

min/2 делает то же самое, но позволяет указать значение по умолчанию в анонимной функции:

iex> Enum.min([], fn -> :foo end)
:foo

max

Функция max/1 возвращает максимальное значение коллекции:

iex> Enum.max([5, 3, 0, -1])
5

max/2 делает то же самое, но как и в примере с min/2, позволяет передать значение по умолчанию:

Enum.max([], fn -> :bar end)
:bar

reduce

С помощью функции reduce/3 можно объединить все элементы коллекции в единое значение. Для этого можно передать в функцию опциональное значение-аккумулятор (10 в данном примере). Если аккумулятор не передан, используется первый элемент в перечислении:

iex> Enum.reduce([1, 2, 3], 10, fn(x, acc) -> x + acc end)
16

iex> Enum.reduce([1, 2, 3], fn(x, acc) -> x + acc end)
6

iex> Enum.reduce(["a","b","c"], "1", fn(x,acc)-> x <> acc end)
"cba1"

sort

Для сортировки коллекций доступна не одна, а целых две функции sort.

sort/1 использует встроенный в Erlang механизм сравнения:

iex> Enum.sort([5, 6, 1, 3, -1, 4])
[-1, 1, 3, 4, 5, 6]

iex> Enum.sort([:foo, "bar", Enum, -1, 4])
[-1, 4, Enum, :foo, "bar"]

sort/2 позволяет передать собственную функцию для сравнения элементов:

# с нашей функцией
iex> Enum.sort([%{:val => 4}, %{:val => 1}], fn(x, y) -> x[:val] > y[:val] end)
[%{val: 4}, %{val: 1}]

# без неё
iex> Enum.sort([%{:count => 4}, %{:count => 1}])
[%{count: 1}, %{count: 4}]

uniq

Для удаления дубликатов из перечислений можно использовать uniq/1:

iex> Enum.uniq([1, 2, 3, 2, 1, 1, 1, 1, 1])
[1, 2, 3]


Поделиться