Blog

Dealing with collections - Enum (and Stream)

Posted on August 31, 2015 by Clive

The Enum module is one of the main workhorses in your Elixir armoury. This module collects together all the various functions that operate on list (or recursive) structures, or rather Enumerables. If you need a refresher on lists then read the post found here. There is also pretty good information available on the Elixir website - Enumerables and Streams.

The Enum module provides a large amount of functionality which allows us to map, filter, fold, transform and sort any given enumerable - more than we will have space to cover, so we will the most important.

The Enum and Stream modules are similar, but different in a very important aspect - the Enum module is eager whereas the Stream module is lazy.

 

Map (map/2)

Map takes a collection and a function and applies the function to each of the elements in the collection, returning a new collection of the returned values.

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

If you're running map over a dictionary, then the function will require both key and value:

iex(3)> Enum.map([a: 1, b: 2, c: 3], fn({k, v}) -> {k, v * v} end)
[a: 1, b: 4, c: 9]

 

Take (take/2)

Take accepts a collection and an integer 'count' and returns a list with count number of elements from the beginning of the given collection.

iex(4)> Enum.take([1,2,3,4,5], 2)
[1, 2]

Take will accept negative integers as well. When presented with a negative integer, take will remove from the end of the collection:

iex(5)> Enum.take([1,2,3,4,5], -2)
[4, 5]

Entering a count of more than there are elements in the collection returns all the elements in the collection:

iex(6)> Enum.take([1,2,3,4,5], 10)
[1, 2, 3, 4, 5]

 

Sort (sort/1 & sort/2)

Sort with and arity of 1 will sort the collection according to Elixirs natural sorting algorithm:

iex(7)> Enum.sort([9,4,6,2,1,3,5,7,8])
[1, 2, 3, 4, 5, 6, 7, 8, 9]

Sort/2 takes a custom sorting function and sorts according to the output of the function:

iex(8)> Enum.sort([9,4,6,2,1,3,5,7,8], fn(x, y) -> x > y end)
[9, 8, 7, 6, 5, 4, 3, 2, 1]

 

Filter (filter/2)

Filter takes a collection and a function to provide the filtering mechanism. The filter function needs to return true for the element to be included in the output list.

iex(9)> Enum.filter(["this", "is", "just", "a", "test"], fn(x) -> String.length(x) >= 4 end)
["this", "just", "test"]

 

Each (each/2)

Each just applies the given function to the collection, there is no return like map/2:

iex(12)> Enum.each([1,2,3,4,5], &(IO.puts(&1 * 2)))
2
4
6
8
10
:ok

 

Any? (any?/2)

Any? returns true if any of the elements, when enacted on by the given function returns true, otherwise it returns false

iex(14)> Enum.any?(["this", "is", "just", "a", "test"], fn(x) -> String.length(x) >= 4 end)
true
iex(15)> Enum.any?(["this", "is", "just", "a", "test"], fn(x) -> String.length(x) >= 6 end)
false

 

Join (join/2)

Join returns a binary (string) where the collection elements are joined by the 'joiner'. If the elements in the collection can't be converted to a binary the function will throw an error

iex(16)> Enum.join([1,2,3,4,5], ".")
"1.2.3.4.5"
iex(17)> Enum.join(["this", "is", "just", "a", "test"], " ")
"this is just a test"

 

Reverse (reverse/1)

Does what it says on the tin.

iex(18)> Enum.reverse([5,4,3,2,1])
[1, 2, 3, 4, 5]

 

Slice (slice/2 & slice/3)

Returns a slice from the list. slice/2 takes a list and a range, slice/3 takes a list, a start position and a length

iex(20)> Enum.slice([1,2,3,4,5,6,7,9,10], 3..6)
[4, 5, 6, 7]
iex(21)> Enum.slice(1..10, 3..6)
[4, 5, 6, 7]
iex(23)> Enum.slice(1..10, 2, 2)
[3, 4]
iex(24)> Enum.slice(1..10, 2, 5)
[3, 4, 5, 6, 7]

 

Zip (zip/2)

Takes 2 collections and returns a list of tuples.

iex(25)> Enum.zip([:a, :b, :c], [1,2,3])
[a: 1, b: 2, c: 3]

 

Reject (reject/2)

Reject works as an opposite to filter - it returns a collection where the elements returned as false from the function

iex(27)> Enum.reject(["this", "is", "just", "a", "test", "thing"], fn(x) -> String.length(x) == 4 end)
["is", "a", "thing"]

 

All? (all?/2)

Returns true if all the elements in the collection return true from the function, otherwise it returns false

iex(28)> Enum.all?(["this", "is", "just", "a", "test"], fn(x) -> String.length(x) >= 4 end)
false
iex(29)> Enum.all?(["this", "is", "just", "a", "test"], fn(x) -> String.length(x) >= 1 end)
true

This concludes our whistle-stop tour of the Enum module. There is much more to explore, but I'll leave that up to you.

DistortedThinking.Agency
Phone: +44 (0)7815 166434Email: clive@distortedthinking.agency
www.distortedthinking.agency