Collections

Lists, tuples, keyword lists, and maps.

Table of Contents

Lists

Lists are simple collections of values which may include multiple types; lists may also include non-unique values:

iex> [3.14, :pie, "Apple"]
[3.14, :pie, "Apple"]

Elixir implements list collections as linked lists. This means that accessing the list length is an operation that will run in linear time (O(n)). For this reason, it is typically faster to prepend than to append:

iex> list = [3.14, :pie, "Apple"]
[3.14, :pie, "Apple"]
# Prepending (fast)
iex> ["π"] ++ list  
["π", 3.14, :pie, "Apple"]
# Appending (slow)
iex> list ++ ["Cherry"]
[3.14, :pie, "Apple", "Cherry"]

List Concatenation

List concatenation uses the ++/2 operator:

iex> [1, 2] ++ [3, 4, 1]
[1, 2, 3, 4, 1]

A side note about the name (++/2) format used above: In Elixir (and Erlang, upon which Elixir is built), a function or operator name has two components: the name you give it (here ++) and its arity. Arity is a core part of speaking about Elixir (and Erlang) code. It is the number of arguments a given function takes (two, in this case). Arity and the given name are combined with a slash. We’ll talk more about this later; this knowledge will help you understand the notation for now.

List Subtraction

Support for subtraction is provided via the --/2 operator; it’s safe to subtract a missing value:

iex> ["foo", :bar, 42] -- [42, "bar"]
["foo", :bar]

Be mindful of duplicate values. For every element on the right, the first occurrence of it gets removed from the left:

iex> [1,2,2,3,2,3] -- [1,2,3,2]
[2, 3]

Note: List subtraction uses strict comparison to match the values.

Head / Tail

When using lists, it is common to work with a list’s head and tail. The head is the list’s first element, while the tail is a list containing the remaining elements. Elixir provides two helpful functions, hd and tl, for working with these parts:

iex> hd [3.14, :pie, "Apple"]
3.14
iex> tl [3.14, :pie, "Apple"]
[:pie, "Apple"]

In addition to the aforementioned functions, you can use pattern matching and the cons operator | to split a list into head and tail. We’ll learn more about this pattern in later lessons:

iex> [head | tail] = [3.14, :pie, "Apple"]
[3.14, :pie, "Apple"]
iex> head
3.14
iex> tail
[:pie, "Apple"]

Tuples

Tuples are similar to lists, but are stored contiguously in memory. This makes accessing their length fast but modification expensive; the new tuple must be copied entirely to memory. Tuples are defined with curly braces:

iex> {3.14, :pie, "Apple"}
{3.14, :pie, "Apple"}

It is common for tuples to be used as a mechanism to return additional information from functions; the usefulness of this will be more apparent when we get into pattern matching:

iex> File.read("path/to/existing/file")
{:ok, "... contents ..."}
iex> File.read("path/to/unknown/file")
{:error, :enoent}

Keyword lists

Keyword lists and maps are the associative collections of Elixir. In Elixir, a keyword list is a special list of two-element tuples whose first element is an atom; they share performance with lists:

iex> [foo: "bar", hello: "world"]
[foo: "bar", hello: "world"]
iex> [{:foo, "bar"}, {:hello, "world"}]
[foo: "bar", hello: "world"]

The three characteristics of keyword lists highlight their importance:

  • Keys are atoms.
  • Keys are ordered.
  • Keys may not be unique.

For these reasons, keyword lists are most commonly used to pass options to functions.

Maps

In Elixir, maps are the “go-to” key-value store. Unlike keyword lists, they allow keys of any type and are un-ordered. You can define a map with the %{} syntax:

iex> map = %{:foo => "bar", "hello" => :world}
%{:foo => "bar", "hello" => :world}
iex> map[:foo]
"bar"
iex> map["hello"]
:world

As of Elixir 1.2, variables are allowed as map keys:

iex> key = "hello"
"hello"
iex> %{key => "world"}
%{"hello" => "world"}

If a duplicate is added to a map, it will replace the former value:

iex> %{:foo => "bar", :foo => "hello world"}
%{foo: "hello world"}

As we can see from the output above, there is a special syntax for maps containing only atom keys:

iex> %{foo: "bar", hello: "world"}
%{foo: "bar", hello: "world"}
iex> %{foo: "bar", hello: "world"} == %{:foo => "bar", :hello => "world"}
true

In addition, there is a special syntax for accessing atom keys:

iex> map = %{foo: "bar", hello: "world"}
%{foo: "bar", hello: "world"}
iex> map.hello
"world"

Another interesting property of maps is that they provide their own syntax for updates:

iex> map = %{foo: "bar", hello: "world"}
%{foo: "bar", hello: "world"}
iex> %{map | foo: "baz"}
%{foo: "baz", hello: "world"}