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_every
Если требуется разбить коллекцию на меньшие группы, метод chunk_every/2
— это то, что нужно:
iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]
Также есть несколько опций, которые вы можете посмотреть в официальной документации: chunk_every/4
.
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
, позволяет передать значение по умолчанию:
iex> Enum.max([], fn -> :bar end)
:bar
filter
Функция filter/2
позволяет отфильтровать коллекцию, оставив в ней лишь те элементы, для которых результат выполнения указанной функции равен true
.
iex> Enum.filter([1, 2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
[2, 4]
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}]
Для удобства sort/2
позволяет нам передавать:asc
или :desc
в качестве второго параметра:
Enum.sort([2, 3, 1], :desc)
[3, 2, 1]
uniq
Для удаления дубликатов из перечислений можно использовать uniq/1
:
iex> Enum.uniq([1, 2, 3, 2, 1, 1, 1, 1, 1])
[1, 2, 3]
uniq_by
uniq_by/2
также убирает дубликаты из перечислений, но и принимает функцию проверки на уникальность.
iex> Enum.uniq_by([%{x: 1, y: 1}, %{x: 2, y: 1}, %{x: 3, y: 3}], fn coord -> coord.y end)
[%{x: 1, y: 1}, %{x: 3, y: 3}]
Перечисление с использованием оператора захвата (&)
Многие функции модуля Enum в Elixir принимают анонимные функции в качестве аргумента для работы с каждым итерабельным элементом переданного перечислимого.
Эти анонимные функции часто сокращаются с помощью оператора захвата (&).
Вот несколько примеров, которые показывают, как оператор захвата может быть реализован с помощью модуля Enum. Каждый из примеров даёт один и тот же результат.
Использование оператора захвата с анонимными функциями
Ниже приведен пример стандартного синтаксиса при передаче анонимной функции в Enum.map/2
.
iex> Enum.map([1,2,3], fn number -> number + 3 end)
[4, 5, 6]
Теперь реализуем оператор захвата (&); захват каждого итерабельного элемента из списка чисел ([1,2,3]) и присвоение значения каждого итерабельного элемента переменной &1, когда она передается через функцию отображения.
iex> Enum.map([1,2,3], &(&1 + 3))
[4, 5, 6]
Мы можем улучшить это, назначив вместо предыдущей анонимной функции с оператором захвата новую переменную, и вызвать эту переменную из функции Enum.map/2
.
iex> plus_three = &(&1 + 3)
iex> Enum.map([1,2,3], plus_three)
[4, 5, 6]
Использование оператора захвата с именованными функциями
Сперва мы создаем именованную функцию и вызываем ее внутри анонимной функции, определенной в Enum.map/2
.
defmodule Adding do
def plus_three(number), do: number + 3
end
iex> Enum.map([1,2,3], fn number -> Adding.plus_three(number) end)
[4, 5, 6]
Затем улучшим эту функцию, используя оператора захвата.
iex> Enum.map([1,2,3], &Adding.plus_three(&1))
[4, 5, 6]
Для краткости мы можем напрямую вызвать именованную функцию без явного захвата переменной.
iex> Enum.map([1,2,3], &Adding.plus_three/1)
[4, 5, 6]
Caught a mistake or want to contribute to the lesson? Edit this lesson on GitHub!