Collections

Improper list: [1 | 2] (tail is not a list) — avoid except in iodata.

11.1 Lists — linked lists

Improper list: [1 | 2] (tail is not a list) — avoid except in iodata.

list = [1, 2, 3]
[0 | list]              # prepend: O(1)
list ++ [4]             # append: O(n)
[1, 2, 3, 2] -- [2]     # [1, 3, 2]
hd(list), tl(list)      # 1, [2, 3]
length(list)            # O(n)
3 in list               # true
List.first(list), List.last(list)
List.delete([1, 2, 1], 1)     # [2, 1]
List.insert_at([1, 3], 1, 2)  # [1, 2, 3]
List.flatten([1, [2, [3]]])
List.zip([[1, 2], [:a, :b]])  # [{1, :a}, {2, :b}]
List.wrap(nil), List.wrap(1)  # [], [1]
List.keyfind([{:a, 1}], :a, 0)
List.to_tuple, List.to_string, List.duplicate(:x, 3)

11.2 Tuples — fixed-size, contiguous

Use tuples for small fixed groupings and tagged return values: {:ok, value}, {:error, reason}.

t = {:ok, "data", 42}
elem(t, 1)              # "data" — O(1)
put_elem(t, 2, 43)      # new tuple
tuple_size(t)           # 3
Tuple.to_list(t)
Tuple.append(t, :x)     # (deprecated in 1.18 in favour of Tuple.insert_at)

11.3 Keyword lists — lists of {atom, value} pairs

Keys are ordered and may repeat. Used for options.

opts = [timeout: 5000, retries: 3]     # same as [{:timeout, 5000}, {:retries, 3}]
opts[:timeout]                          # 5000
Keyword.get(opts, :retries, 1)
Keyword.put(opts, :verbose, true)
Keyword.merge(opts, timeout: 1000)
Keyword.fetch!(opts, :timeout)
Keyword.validate!(opts, [:timeout, retries: 1])   # validate allowed keys & defaults

String.split("a b c", " ", trim: true)          # keyword as last arg: brackets optional
if true, do: :yes, else: :no                     # do/else is a keyword list!

11.4 Maps — key/value store

Maps with ≤32 keys are ordered by key; larger ones use a HAMT (no guaranteed order).

m = %{"name" => "Ann", :age => 30}
atoms = %{name: "Ann", age: 30}      # atom-key shorthand

atoms.name                 # "Ann" — dot access (raises KeyError if missing)
atoms[:name]               # "Ann" — returns nil if missing
m["name"]
Map.get(m, "x", :default)
Map.fetch(m, "name")       # {:ok, "Ann"}
Map.put(atoms, :city, "Oslo")
%{atoms | age: 31}         # update syntax — key MUST exist
Map.update(atoms, :age, 0, &(&1 + 1))
Map.update!(atoms, :age, &(&1 + 1))
Map.delete(atoms, :age)
Map.merge(%{a: 1}, %{b: 2})
Map.merge(%{a: 1}, %{a: 2}, fn _k, v1, v2 -> v1 + v2 end)
Map.keys(atoms), Map.values(atoms)
Map.has_key?(atoms, :name)
Map.take(atoms, [:name]), Map.drop(atoms, [:age])
Map.new([{:a, 1}]), Map.new([1, 2], &{&1, &1 * 2})
Map.filter(atoms, fn {_k, v} -> is_integer(v) end)
Map.to_list(atoms)

11.5 Nested data access

data = %{user: %{profile: %{name: "Ann", langs: ["elixir"]}}}

get_in(data, [:user, :profile, :name])
put_in(data, [:user, :profile, :name], "Bob")
update_in(data, [:user, :profile, :langs], &["erlang" | &1])
put_in(data.user.profile.name, "Bob")          # macro path syntax
get_and_update_in(data, [:user, :profile, :name], &{&1, String.upcase(&1)})
pop_in(data, [:user, :profile, :name])

# Access helpers
get_in(%{list: [%{a: 1}, %{a: 2}]}, [:list, Access.all(), :a])   # [1, 2]
Access.at(0), Access.key(:k, default), Access.filter(&pred/1), Access.elem(1)

11.6 MapSet

s = MapSet.new([1, 2, 3, 3])
MapSet.member?(s, 2)
MapSet.put(s, 4)
MapSet.union(s, MapSet.new([5]))
MapSet.intersection, MapSet.difference, MapSet.subset?, MapSet.size

11.7 Ranges

1..10                     # inclusive
1..10//2                  # step: 1, 3, 5, 7, 9
10..1//-1                 # descending
Enum.to_list(1..5)
5 in 1..10                # true
Range.size(1..10//3)

11.8 Choosing a collection

Sequence, prepend, iterate Fixed small record / tagged return Domain entity with known fields Big mutable shared table