Какие существуют способы обработки коллекций в 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()
Эти способы позволяют строить чистый, декларативный и функциональный код для обработки любых коллекций — от маленьких списков до потоков из внешних источников.