View Source The Ultimate Enum Cheatsheet

The Enum module provides functions that are incredibly useful, flexible and expressive. It is fair to claim that it is more than making up for the lack of loops in Elixir. However, the number of functions might feel overwhelming at first.

This cheatsheet is meant as an attempt to categorize functions in order to learn or find them more easily. It doesn't mean to be exhaustive, and it also contains a few non-Enum yet useful recipes for working with enumerables.

Disclaimer: this cheatsheet is not directly related to Iter, but since Iter API is based on Enum it can also help its usage.

return-a-list-of-the-same-size

Return a list of the same size

transform-each-element

Transform each element

Enum.map/2

iex> Enum.map(1..3, & &1 ** 2)
[1, 4, 9]

Using the index: Enum.with_index/2

iex> Enum.with_index(["a", "b", "c"])
[{"a", 0}, {"b", 1}, {"c", 2}]
iex> Enum.with_index(["a", "b", "c"], &String.duplicate(&1, &2))
["", "b", "cc"]

Using another list/enumerable: Enum.zip/2 or Enum.zip_with/3

iex> Enum.zip(["i", "x", "c"], [1, 10, 100])
[{"i", 1}, {"x", 10}, {"c", 100}]
iex> Enum.zip_with(["i", "x", "c"], [1, 10, 100], &"#{&1}: #{&2}")
["i: 1", "x: 10", "c: 100"]

See also: Enum.zip/1, Enum.zip_with/2, Enum.unzip/1

Using an accumulator: Enum.map_reduce/3

# 1/1, 2/3, 3/6, 4/10
iex> Enum.map_reduce(1..4, 0, fn x, acc ->
...>   new_acc = x + acc
...>   {x / new_acc, new_acc}
...> end)
{[1.0, 0.6666666666666666, 0.5, 0.4], 10}

just-cast-as-a-list

Just cast as a list

Enum.to_list/1

iex> Enum.to_list(1..3)
[1, 2, 3]
iex> Enum.to_list(%{"foo" => 1, "bar" => 2})
[{"bar", 2}, {"foo", 1}]

reordering

Reordering

Reverse order: Enum.reverse/2

iex> Enum.reverse([:a, :b, :c])
[:c, :b, :a]

Enum.sort/1 / Enum.sort/2

iex> Enum.sort([:b, :d, :a, :c])
[:a, :b, :c, :d]
iex> Enum.sort([:b, :d, :a, :c], :desc)
[:d, :c, :b, :a]

Enum.sort_by/2 / Enum.sort_by/3

iex> Enum.sort_by(["abc", "d", "ef"], &String.length/1)
["d", "ef", "abc"]
iex> Enum.sort_by(["abc", "d", "ef"], &String.length/1, :desc)
["abc", "ef", "d"]

⚠️ WARNING - Pitfall when comparing structs, see doc

iex> dates = [~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]]
iex> Enum.sort(dates)
[~D[2019-01-01], ~D[2020-03-02], ~D[2019-06-06]]
iex> Enum.sort(dates, Date)
[~D[2019-01-01], ~D[2019-06-06], ~D[2020-03-02]]

Random order: Enum.shuffle/1

iex> Enum.shuffle([:a, :b, :c, :d])
[:c, :a, :d, :b]

return-a-shorter-list

Return a shorter list

filtering-on-a-condition

Filtering on a condition

Enum.filter/2 / Enum.reject/2

iex> Enum.filter(["ant", "bat", "cat"], & &1 =~ "at")
["bat", "cat"]
iex> Enum.reject(["ant", "bat", "cat"], & &1 =~ "at")
["ant"]

Both at once: Enum.split_with/2:

iex> Enum.split_with(["ant", "bat", "cat"], & &1 =~ "at")
{["bat", "cat"], ["ant"]}

Filtering + transforming in one pass: for/1 comprehension

iex> for s <- ["ant", "bat", "cat"], s =~ "at" do
...>   String.capitalize(s)
...> end
["Bat", "Cat"]

slicing

Slicing

From a range: Enum.slice/2

iex> Enum.slice([1, 2, 3, 4, 5], 1..3)
[2, 3, 4]
iex> Enum.slice(1..100, 5..25//5)
[6, 11, 16, 21, 26]

From an index and amount: Enum.slice/3

iex> Enum.slice(1..100, 5, 10)
[6, 7, 8, 9, 10, 11, 12, 13, 14, 15]

amount-based

Amount-based

Keep an amount: Enum.take/2

iex> Enum.take(["ant", "bat", "cat", "dog"], 2)
["ant", "bat"]
# negative index: take from the end
iex> Enum.take(["ant", "bat", "cat", "dog"], -2)
["cat", "dog"]

Remove an amount: Enum.drop/2

iex> Enum.drop(["ant", "bat", "cat", "dog"], 2)
["cat", "dog"]
# negative index: drop from the end
iex> Enum.drop(["ant", "bat", "cat", "dog"], -2)
["ant", "bat"]

Both at once: Enum.split/2

iex> Enum.split(["ant", "bat", "cat", "dog"], 2)
{["ant", "bat"], ["cat", "dog"]}

keep-drop-every-nth-element

Keep/drop every nth element

Enum.take_every/2

iex> Enum.take_every(1..10, 2)
[1, 3, 5, 7, 9]

Enum.drop_every/2

iex> Enum.drop_every(1..10, 2)
[2, 4, 6, 8, 10]

until-a-condition-is-met

Until a condition is met

Enum.take_while/2 / Enum.drop_while/2

iex> Enum.take_while(["bat", "cat", "dog"], & &1 =~ "at")
["bat", "cat"]
iex> Enum.drop_while(["bat", "cat", "dog"], & &1 =~ "at")
["dog"]

Both at once: Enum.split_while/2:

iex> Enum.split_while(["bat", "cat", "dog"], & &1 =~ "at")
{["bat", "cat"], ["dog"]}

removing-duplicates

Removing duplicates

All duplicates: Enum.uniq/1 / Enum.uniq_by/2

iex> Enum.uniq([3, 2, 1, 1, 2, 3])
[3, 2, 1]
iex> Enum.uniq_by(["foo", "bar", "baz", "foo"], &String.first/1)
["foo", "bar"]

Successive duplicates only: Enum.dedup/1 / Enum.dedup_by/2

iex> Enum.dedup([3, 2, 1, 1, 2, 3])
[3, 2, 1, 2, 3]
iex> Enum.dedup_by(["foo", "bar", "baz", "foo"], &String.first/1)
["foo", "bar", "foo"]

Side note - Use a set: MapSet.new/1 / MapSet.new/2

When working with unique elements, maybe a list isn't the best data structure. If you don't care about the ordering but want to efficiently test for membership, you most likely want a MapSet.

iex> MapSet.new([3, 2, 1, 1, 2, 3])
MapSet.new([1, 2, 3])
iex> MapSet.new(["foo", "bar", "baz", "foo"], &String.first/1)
MapSet.new(["b", "f"])

random-sample

Random sample

Enum.take_random/2

iex> Enum.take_random([:a, :b, :c, :d], 2)
[:c, :a]

return-search-a-single-element

Return/search a single element

element-matching-a-condition

Element matching a condition

These will return early at the first match.

Enum.find/2

iex> Enum.find(["ant", "bat", "cat"], & &1 =~ "at")
"bat"

With transformation: Enum.find_value/2

iex> Enum.find_value(["ant", "bat", "cat"], & &1 =~ "at" && String.upcase(&1))
"BAT"

Its index: Enum.find_index/2

iex> Enum.find_index(["ant", "bat", "cat"], & &1 =~ "at")
1

an-extreme-value

An extreme value

Enum.max/1 / Enum.max_by/2

iex> Enum.max([2, 5, 3, 1, 4])
5
iex> Enum.max_by(["a", "bcd", "ef"], &String.length/1)
"bcd"

Enum.min/1 / Enum.min_by/2

iex> Enum.min([2, 5, 3, 1, 4])
1
iex> Enum.min_by(["a", "bcd", "ef"], &String.length/1)
"a"

⚠️ WARNING - Pitfall when comparing structs, see doc

iex> Enum.max([~D[2017-03-31], ~D[2017-04-01]])
~D[2017-03-31]
iex> Enum.max([~D[2017-03-31], ~D[2017-04-01]], Date)
~D[2017-04-01]

Both at once: Enum.min_max/1 / Enum.min_max_by/2

iex> Enum.min_max([2, 5, 3, 1, 4])
{1, 5}
iex> Enum.min_max_by(["a", "bcd", "ef"], &String.length/1)
{"a", "bcd"}

at-a-known-index

At a known index

⚠️ WARNING

Accessing linked lists by index is linear. If you are using the following in a nested call, you are probably doing something wrong.

Raising on failure: Enum.fetch!/2

iex> Enum.fetch!(["ant", "bat", "cat"], 0)
"ant"
iex> Enum.fetch!(["ant", "bat", "cat"], -1)
"cat"
iex> Enum.fetch!(["ant", "bat", "cat"], 3)
** (Enum.OutOfBoundsError) out of bounds error

:ok/:error result: Enum.fetch/2

iex> Enum.fetch(["ant", "bat", "cat"], 2)
{:ok, "cat"}
iex> Enum.fetch(["ant", "bat", "cat"], 3)
:error

With default value: Enum.at/2

iex> Enum.at(["ant", "bat", "cat"], 3)
nil
iex> Enum.at(["ant", "bat", "cat"], 3, "none")
"none"

random-sample-1

Random sample

Enum.random/1

iex> Enum.random([:a, :b, :c, :d])
:c

return-a-number

Return a number

count-without-stopping

Count without stopping

All elements: Enum.count/1

iex> Enum.count(["ant", "bat", "cat"])
3

Matching elements: Enum.count/2

iex> Enum.count(["ant", "bat", "cat"], & &1 =~ "at")
2

count-up-to-a-limit

Count up to a limit

Enum.count_until/2

iex> Enum.count_until(1..20, 5)
5
iex> Enum.count_until(1..20, 50)
20

Matching elements: Enum.count_until/3

iex> Enum.count_until(1..20, &rem(&1, 2) == 0, 5)
5
iex> Enum.count_until(1..20, &rem(&1, 2) == 0, 50)
10

other-aggregations

Other aggregations

Enum.sum/1

iex> Enum.sum([1, 20, 300])
321

Enum.product/1

iex> Enum.product([1, 20, 300])
6000

find-an-index

Find an index

Enum.find_index/2

iex> Enum.find_index(["ant", "bat", "cat"], & &1 =~ "at")
1

return-a-map

Return a map

sample-data

Sample data

iex> users = [%{id: 10, name: "Joe"}, %{id: 20, name: "Robert"}, %{id: 30, name: "Jose"}]

from-key-value-pairs

From key/value pairs

Map.new/2 / Map.new/1

iex> Map.new(users, &{&1.id, &1.name})
%{10 => "Joe", 20 => "Robert", 30 => "Jose"}
iex> Map.new([{:foo, 12}, {:bar, 5}])
%{bar: 5, foo: 12}

Enum.into/3 / Enum.into/2

iex> Enum.into(users, %{}, &{&1.id, &1.name})
%{10 => "Joe", 20 => "Robert", 30 => "Jose"}
iex> Enum.into([{:foo, 12}, {:bar, 5}], %{})
%{bar: 5, foo: 12}

aggregations

Aggregations

Counting: Enum.frequencies/1 / Enum.frequencies_by/2

iex> Enum.frequencies(["a", "b", "a", "c"])
%{"a" => 2, "b" => 1, "c" => 1}
iex> Enum.frequencies_by(users, &String.first(&1.name))
%{"J" => 2, "R" => 1}

Grouping: Enum.group_by/2 / Enum.group_by/3

iex> Enum.group_by(users, &String.first(&1.name))
%{
  "J" => [%{id: 10, name: "Joe"}, %{id: 30, name: "Jose"}],
  "R" => [%{id: 20, name: "Robert"}]
}
iex> Enum.group_by(users, &String.first(&1.name), & &1.id)
%{"J" => [10, 30], "R" => [20]}

return-build-a-string

Return/build a string

sample-data-1

Sample data

words = ["hello", "world"]

actually-return-a-string

Actually return a string

Enum.map_join/3

iex> Enum.map_join(words, ", ", &String.capitalize/1)
"Hello, World"

When no need to transform: Enum.join/2

iex> Enum.join(1..5, "-")
"1-2-3-4-5"

⚠️ WARNING - Efficient string building

Building your string manually using concatenation might lead to degraded performance, make sure to always rely on Enum.map_join/3 / Enum.join/2 or use IO data to build large strings dynamically.

building-and-using-io-data

Building and using IO data

Joining as IO data: Enum.map_intersperse/3

iex> Enum.map_intersperse(words, ", ", &String.capitalize/1)
["Hello", ", ", "World"]

When no need to transform: Enum.intersperse/2

# only when already list/enumerable of strings
iex> Enum.intersperse(words, ", ")
["hello", ", ", "world"]
# else need to map_intersperse + to_string
iex> Enum.map_intersperse(1..5, ", ", &to_string/1)
["1", ", ", "2", ", ", "3", ", ", "4", ", ", "5"]

IO data to string: IO.iodata_to_binary/1

iex> IO.iodata_to_binary(["Hello", ", " | ["World", ?!]])
"Hello, World!"

NOTE - Building a string might be un-necessary

# IO data can be used directly for IO
iex> IO.puts(["Hello", ", " | ["World", ?!]])
Hello, World!
:ok
iex> File.write!("hello.txt", ["Hello", ", " | ["World", ?!]])
:ok

return-a-boolean

Return a boolean

matching-a-condition

Matching a condition

These will return early at the first match.

Enum.any?/2

iex> Enum.any?(["ant", "bat", "cat"], & &1 =~ "at")
true
iex> Enum.any?(["ant", "bat", "cat"], & &1 =~ "z")
false

Enum.all?/2

iex> Enum.all?(["ant", "bat", "cat"], & &1 =~ "t")
true
iex> Enum.all?(["ant", "bat", "cat"], & &1 =~ "at")
false

emptiness

Emptiness

Enum.empty?/1

iex> Enum.empty?([])
true
iex> Enum.empty?([:exists])
false

membership

Membership

Enum.member?/2

iex> Enum.member?(["ant", "bat", "cat"], "bat")
true
iex> Enum.member?(["ant", "bat", "cat"], "dog")
false

Same but shorter: in/2

iex> "bat" in ["ant", "bat", "cat"]
true

Side note - Use a set: MapSet.new/1 / MapSet.new/2

Checking membership within a list has a linear cost - potentially quadratic within a loop. You might want to convert a list to a MapSet for efficient lookups.

# DON'T
Enum.find(fn x -> x.id in list end)
# DO
set = MapSet.new(list)
Enum.find(fn x -> x.id in set end)

return-a-flattened-list

Return a flattened list

Without transformation: Enum.concat/1

iex> Enum.concat([["ant", "bat"], ["cat", "dog"]])
["ant", "bat", "cat", "dog"]

With transformation: Enum.flat_map/2

iex> list = [%{data: [1, 2]}, %{data: [3, 4]}]
iex> Enum.flat_map(list, & &1.data)
[1, 2, 3, 4]

return-several-smaller-lists

Return several smaller lists

By size: Enum.chunk_every/2 / Enum.chunk_every/4

iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 2)
[[1, 2], [3, 4], [5, 6]]
iex> Enum.chunk_every([1, 2, 3, 4, 5, 6], 3, 2, :discard)
[[1, 2, 3], [3, 4, 5]]

Grouped by condition: Enum.chunk_by/2

iex> animals = ["cat", "bat", "beaver", "camel"]
iex> Enum.chunk_by(animals, &String.first/1)
[["cat"], ["bat", "beaver"], ["camel"]]

See also: Enum.chunk_while/4

when-nothing-else-works

When nothing else works

the-closest-thing-to-a-for-loop

The closest thing to a for loop

The most important one: Enum.reduce/3

iex> Enum.reduce([2, 4, 8], 0, fn x, acc ->
...>   acc + 1 / x
...> end)
0.875 # 1 / 2 + 1 / 4 + 1 / 8

⚠️ WARNING - Building a list

Lists should never be built by appending, always by prepending. Quoting the Erlang efficiency guide, "To avoid copying the result in each iteration, build the list in reverse order and reverse the list when you are done":

iex> acc = Enum.reduce(1..5, [], fn x, acc -> [x ** 2 | acc] end)
[25, 16, 9, 4, 1]
iex> Enum.reverse(acc)
[1, 4, 9, 16, 25]

⚠️ WARNING - Reinventing the wheel

Enum.reduce/3 and its variants are very powerful and can be seen as the swiss army knife of the Enum module. But if there is a specialized function that does what you need, rolling your own manual implementation might be more verbose and less efficient (see the build a string section for example).

skipping-the-initial-accumulator

Skipping the initial accumulator

Enum.reduce/2

# first element will be used as accumulator instead
iex> Enum.reduce([2, 4, 8], fn x, acc ->
...>   acc + 1 / x
...> end)
2.375 # 2 + 1 / 4 + 1 / 8

⚠️ WARNING - Edge cases

# It might seem that 0 is optional here...
iex> Enum.reduce([10, 20, 30], &+/2)
60
# ... but will actually raise if empty
iex> Enum.reduce([], &+/2)
** (Enum.EmptyError) empty error

early-returns-like-break

Early returns (like break)

Enum.reduce_while/3

iex> Enum.reduce_while([2, 4, 0, 1], 0, fn x, acc ->
...>   if x == 0 do
...>     {:halt, acc}
...>   else
...>     {:cont, acc + 1 / x}
...>   end
...> end)
0.75 # 1 / 2 + 1 / 4

NOTE - Recursion

The benefit of Enum.reduce_while/3 is that it works with any enumerable. However, a recursion-based implementation might be more readable, maintainable and performant if you only need to support lists. The previous example could be re-implemented as:

  def sum_inverses([x | xs], acc) when x != 0 do
    sum_inverses(xs, acc + 1 / x)
  end

  def sum_inverses(_, acc), do: acc

working-with-several-lists-enumerables

Working with several lists / enumerables

Two inputs: Enum.zip_reduce/4

iex> Enum.zip_reduce([3, 4, 2], [100, 10, 1], 0, fn x, y, acc ->
...>   x * y + acc
...> end)
342 # 3 * 100 + 4 * 10 + 2 * 1

More inputs: Enum.zip_reduce/3

iex> Enum.zip_reduce([[4, 2], [10, 1], [1, -1]], 0, fn xs, acc ->
...>   Enum.product(xs) + acc
...> end)
38 # 4 * 10 * 1 + 2 * 1 * -1

keeping-all-the-steps

Keeping all the steps

With initial accumulator: Enum.scan/3

iex> Enum.scan([2, 4, 8], 0, fn x, acc ->
...>   acc + 1 / x
...> end)
[0.5, 0.75, 0.875]

Without initial accumulator: Enum.scan/2

iex> Enum.scan([2, 4, 8], fn x, acc ->
...>   acc + 1 / x
...> end)
[2, 2.25, 2.375]