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.size11.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