Какие существуют способы обработки коллекций в Elixir?

В Elixir существует множество способов обработки коллекций, и каждый из них адаптирован под функциональный стиль программирования. Коллекции в Elixir включают списки (List), отображения (Map), ключевые списки (Keyword), кортежи (Tuple) и структуры (Struct). Основные методы их обработки основаны на использовании модулей Enum, Stream, рекурсии, сопоставления с образцом и других функциональных приёмов.

1. Использование модуля Enum (жадные операции)

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

Примеры:

Enum.map(\[1, 2, 3\], fn x -> x \* 2 end) # \[2, 4, 6\]
Enum.filter(\[1, 2, 3\], fn x -> rem(x, 2) == 0 end) # \[2\]
Enum.reduce(\[1, 2, 3\], 0, fn x, acc -> x + acc end) # 6
Enum.sort(\[3, 1, 2\]) # \[1, 2, 3\]

Особенности:

  • Все данные обрабатываются сразу.

  • Используется для небольших и средних коллекций.

  • Возвращает результат сразу после выполнения.

2. Использование модуля Stream (ленивые операции)

Stream позволяет создавать ленивые трансформации над коллекциями, которые выполняются только при необходимости. Особенно эффективен при работе с большими объемами данных или бесконечными потоками.

Примеры:

\[1, 2, 3, 4, 5\]
|> Stream.map(&(&1 \* 2))
|> Stream.filter(&rem(&1, 3) == 0)
|> Enum.to_list() # \[6\]

Также можно обрабатывать файлы:

File.stream!("large_file.txt")
|> Stream.map(&String.trim/1)
|> Enum.take(5)

Особенности:

  • Работает с ленивыми вычислениями.

  • Позволяет строить цепочки операций без промежуточных структур.

  • Обрабатывает данные по мере необходимости.

3. Рекурсия

Так как Elixir не имеет классических циклов, рекурсия является основным способом итерирования.

Пример:

defmodule MyModule do
def sum_list(\[\]), do: 0
def sum_list(\[head | tail\]), do: head + sum_list(tail)
end
MyModule.sum_list(\[1, 2, 3\]) # 6

Рекурсия бывает обычной и хвостовой (tail recursion). Хвостовая рекурсия предпочтительна, так как позволяет компилятору оптимизировать вызовы и избежать переполнения стека.

4. Pattern Matching (сопоставление с образцом)

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

Пример:

\[head | tail\] = \[1, 2, 3\]
\# head => 1, tail => \[2, 3\]

Используется совместно с case, with, function head и рекурсией.

5. Comprehensions (list comprehension)

Удобный способ фильтрации и преобразования коллекций через генераторы.

Пример:

for x <- 1..5, rem(x, 2) == 0, do: x \* x
\# \[4, 16\]

Можно использовать с множеством источников, включая списки, мапы, потоки.

6. Работа с Map, List, Tuple, Keyword

Каждая коллекция имеет свои особенности:

  • List — связанный список, быстрый доступ к голове, медленный к концу.

  • Map — ассоциативный массив (key-value), быстрый доступ по ключу.

  • Tuple — фиксированный по размеру набор значений.

  • Keyword — список пар {ключ, значение}, где ключ — атом.

Примеры:

map = %{a: 1, b: 2}
Map.get(map, :a) # 1
list = \[1, 2, 3\]
\[head | tail\] = list # head = 1, tail = \[2, 3\]
tuple = {:ok, "hello"}
elem(tuple, 1) # "hello"
keyword = \[a: 1, b: 2\]
Keyword.get(keyword, :b) # 2

7. Использование функций высшего порядка

Elixir позволяет передавать функции как аргументы, возвращать функции и строить гибкие структуры обработки данных.

Пример:

double = fn x -> x \* 2 end
Enum.map(\[1, 2, 3\], double) # \[2, 4, 6\]

8. Работа с reduce

Enum.reduce/3 и Stream.reduce/3 — универсальные функции, используемые для аккумулирования результата.

Пример:

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

Также удобно использовать для конструирования структур, подсчёта, сбора статистики и т. д.

9. with, case и cond

Для ветвления логики обработки внутри пайпов или при переборе коллекций.

with {:ok, val} <- Map.fetch(map, :key) do
IO.puts val
end

10. Композиция с оператором |> (pipe)

Позволяет писать цепочки обработки коллекций в читаемой форме.

1..10
|> Enum.filter(&rem(&1, 2) == 0)
|> Enum.map(&(&1 \* 3))
|> Enum.sum()

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