Enum
Um conjunto de algoritmos para fazer enumeração em coleções.
Visão Geral
O módulo Enum
inclui mais de 70 funções para trabalhar com enumeráveis. Todas as coleções que aprendemos na lição anterior, com exceção das tuplas, são enumeráveis.
Esta lição abrangerá apenas um subconjunto das funções disponíveis, no entanto, podemos examiná-las pessoalmente. Vamos fazer um pequeno experimento no 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
...
A enumeração é o núcleo da programação funcional. Se combinada com outras características de Elixir, a enumeração pode ser um recurso incrivelmente poderoso para os desenvolvedores.
Funções
Para obter uma lista completa de funções, visite a documentação oficial do Enum
; para enumeração preguiçosa use o módulo Stream
.
all?
Ao usar all?/2
, e muitas das funções de Enum
, provemos uma função para aplicar nos elementos da nossa coleção. No caso do all?/2
, a coleção inteira deve ser avaliada como true
, caso contrário false
será retornado:
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?
Diferente da anterior, any?/2
retornará true
se ao menos um elemento for true
:
iex> Enum.any?(["foo", "bar", "hello"], fn(s) -> String.length(s) == 5 end)
true
chunk_every
Se você necessita quebrar sua coleção em pequenos grupos, chunk_every/2
é a função que você provavelmente está buscando:
iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]
Há algumas opções para chunk_every/4
porém não vamos entrar nelas, revise a documentação oficial para esta função para aprender mais.
chunk_by
Se necessita agrupar uma coleção baseado em algo diferente do tamanho, podemos usar a função chunk_by/2
. Ela recebe um enumerável e uma função, e quando o retorno desta função muda, um novo grupo é iniciado e começa a criação do próximo:
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
Algumas vezes quebrar uma coleção em blocos não é o suficiente para fazer exatamente o que você precisa. Nesse caso, map_every/3
pode ser muito útil para tratar apenas itens específicos da sua coleção (nth
), sempre atingindo o primeiro:
# Apply function every three items
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
Pode ser necessário iterar sobre uma coleção sem produzir um novo valor, para este caso podemos usar each/2
:
iex> Enum.each(["one", "two", "three"], fn(s) -> IO.puts(s) end)
one
two
three
:ok
Nota: A função each/2
retorna um átomo :ok
.
map
Para aplicar uma função a cada elemento e produzir uma nova coleção use a função map/2
:
iex> Enum.map([0, 1, 2, 3], fn(x) -> x - 1 end)
[-1, 0, 1, 2]
min
min/1
retorna o valor mínimo de uma coleção:
iex> Enum.min([5, 3, 0, -1])
-1
min/2
faz o mesmo, porém permite especificar um valor mínimo padrão através de uma função quando o valor está vazio.
iex> Enum.min([], fn -> :foo end)
:foo
max
max/1
retorna o valor máximo de uma coleção:
iex> Enum.max([5, 3, 0, -1])
5
max/2
é para max/1
o que min/2
é para min/1
:
iex> Enum.max([], fn -> :bar end)
:bar
filter
A função filter/2
nos permite filtrar uma coleção para incluir apenas os elementos que são avaliados como true
provendo uma função como argumento.
iex> Enum.filter([1, 2, 3, 4], fn(x) -> rem(x, 2) == 0 end)
[2, 4]
reduce
Com reduce/3
podemos transformar nossa coleção em um único valor, para fazer isto aplicamos um acumulador opcional (10
neste exemplo) que será passado a nossa função; se não prover um acumulador, o primeiro valor será usado:
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
Ordenar nossas coleções é fácil não só com uma, mas com duas funções de ordenação.
sort/1
usa a funcionalidade de ordenação do Erlang para determinar a ordem:
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
nos permite utilizar nossa própria função de ordenação:
# with our function
iex> Enum.sort([%{:val => 4}, %{:val => 1}], fn(x, y) -> x[:val] > y[:val] end)
[%{val: 4}, %{val: 1}]
# without
iex> Enum.sort([%{:count => 4}, %{:count => 1}])
[%{count: 1}, %{count: 4}]
Por conveniência, sort/2
permite passar :asc
ou :desc
como função de ordenação:
Enum.sort([2, 3, 1], :desc)
[3, 2, 1]
uniq
Podemos usar uniq/1
para eliminar itens duplicados em nossas coleções:
iex> Enum.uniq([1, 2, 3, 2, 1, 1, 1, 1, 1])
[1, 2, 3]
uniq_by
uniq_by/2
também pode ser utilizado para eliminar itens duplicados em nossas coleções, mas nos permite fornecer uma função para fazer a comparação de exclusividade.
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 usando o operador Capture (&)
Muitas funções dentro do módulo Enum no Elixir usam funções anônimas como um argumento para trabalhar com cada iterável do enumerável que é passado.
Essas funções anônimas são frequentemente escritas de forma abreviada usando o operador Capture (&).
Aqui estão alguns exemplos que mostram como o operador capture pode ser implementado com o módulo Enum. Cada versão é funcionalmente equivalente.
Usando o operador capture com uma função anônima
Abaixo está um exemplo típico da sintaxe padrão ao passar uma função anônima para Enum.map/2
.
iex> Enum.map([1,2,3], fn number -> number + 3 end)
[4, 5, 6]
Agora implementamos o operador capture (&); capturando cada iterável da lista de números ([1,2,3]) e atribuindo cada iterável à variável &1 à medida que é passado pela função de mapeamento.
iex> Enum.map([1,2,3], &(&1 + 3))
[4, 5, 6]
Isso pode ser refatorado para atribuir a função anônima anterior com o operador Capture a uma variável e chamada da função Enum.map/2
.
iex> plus_three = &(&1 + 3)
iex> Enum.map([1,2,3], plus_three)
[4, 5, 6]
Usando o operador capture com uma função nomeada
Primeiro, criamos uma função nomeada e a chamamos dentro da função anônima definida em 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]
Em seguida, podemos refatorar para usar o operador Capture.
iex> Enum.map([1,2,3], &Adding.plus_three(&1))
[4, 5, 6]
Para obter a sintaxe mais sucinta, podemos chamar diretamente a função nomeada sem capturar explicitamente a variável.
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!