Lists and tuples in Elixir

Rhaenyra - Jun 23 - - Dev Community

We will keep learning two of the most used collection data types in Elixir: lists and tuples. They are immutable!

Like lists, tuples can hold any value. In Elixir data structure they are immutable, so every operation will return a new List or a new Touple.

(Link)List

Elixir uses square brackets to specify a list of values. List operators never modify the existing list. You can freely pass the data around with the guarantee that no one will mutate it in memory—only transform it.

Linked lists hold zero, one, or more elements in the chosen order.

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

iex> [1, "two", 3, :four]
[1, "two", 3, :four]

iex> [1, 3, "two", :four]
[1, 3, "two", :four]

iex> length([1, 2, 3])
3
Enter fullscreen mode Exit fullscreen mode

List concatenated

Two lists can be concatenated or subtracted using the ++/2 and --/2 operators.

  • left ++ right

    Concatenates a proper list and a term, returning a list. If the right operand is not a proper list, it returns an improper list. If the left operand is not a proper list, it raises an ArgumentError.

  • left -- right

    List subtraction operator. Removes the first occurrence of each element in the left list for each element in the right list.

iex>[1] ++ [2,3]
[1, 2, 3]
iex> [1, 2, 3] ++ [4, 6, 6]
[1, 2, 3, 4, 5, 6]
iex>[1, 2] ++ 3 
[1, 2 | 3] 
iex> [1, 2] ++ {3, 4} 
[1, 2 | {3, 4}] 

iex> [1, 2, 3] -- [1, 2]
[3]
iex> [1, 2, 3, 2, 1] -- [1, 2, 2]
[3, 1]
iex> [1, 2, 3] -- [2] -- [3]
[1, 3]
iex> [1, 2, 3] -- ([2] -- [3])
[1, 3]
iex> [1, true, 2, false, 3, true] -- [true, false]
[1, 2, 3, true]
Enter fullscreen mode Exit fullscreen mode

List prepended

An element can be prepended to a list using |. Due to their cons cell-based representation, prepending an element to a list is always fast (constant time), while appending becomes slower as the list grows in size (linear time):

iex(1)> new = 0
iex(2)> list = [1, 2, 3]
iex(3)> [new | list]
[0, 1, 2, 3]

iex(1)>list = [1, 2, 3]
iex(2)[0 | list] # fast
[0, 1, 2, 3]
iex(3)list ++ [4] # slow
[1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode

Head and tail of list

Lists in Elixir are effectively linked lists, which means they are internally represented in pairs containing the head and the tail of a list.

iex> [head | tail] = [1, 2, 3]
iex> head
1
iex> tail
[2, 3]
Enter fullscreen mode Exit fullscreen mode

The head is the first element of a list and the tail is the remainder of the list. They can be retrieved with the functions hd/1 and tl/1

  • hd(list)

    Returns the head of a list. Raises ArgumentError if the list is empty.

  • tl(list)

    Returns the tail of a list, which is the list without its first element. Raises ArgumentError if the list is empty.

iex> hd([1, 2, 3, 4])
1
iex> hd([1 | 2])
1
iex> hd([])
** (ArgumentError) argument error

iex> tl([1, 2, 3, :go])
[2, 3, :go]
iex>tl([:a, :b | :improper_end])
[:b | :improper_end]
iex>tl([:a | %{b: 1}])
%{b: 1}
iex>tl([:one])
[]
iex>tl([])
** (ArgumentError) argument error
Enter fullscreen mode Exit fullscreen mode

Tuples

Elixir uses curly brackets to define tuples. Tuples store elements contiguously in memory, which means accessing a tuple element by index or getting the tuple size is a fast operation. Indexes start from zero.

iex> {}
{}
iex> {:ok, "hello"}
{:ok, "hello"}
iex> {1, :two, "three"}
{1, :two, "three"}
iex> tuple_size({:ok, "hello"})
2
iex(1)> tuple = {:ok, 1, "hello"}
{:ok, 1, "hello"}
iex(2)> tuple_size(tuple)
3
Enter fullscreen mode Exit fullscreen mode

Functions for working

Accessing a tuple element with elem/2, put_elem/3, and tuple_size/1.

  • elem/2(tuple, index)

    Gets the element at the zero-based index in tuple. Raises ArgumentError when the index is negative or out of range of the tuple elements.

  • put_elem/3(tuple, index, value)

    Puts value at the given zero-based index in tuple.

  • tuple_size/1(tuple)

    Returns the size of a tuple, which is the number of elements in the tuple.

iex(1)> tuple = {:ok, 1, "hello"}
{:ok, 1, "hello"}
iex(2)> elem(tuple, 1)
1

iex> elem({:ok, "hello", 1}, 1)
"helo"
iex> elem({}, 0)
** (ArgumentError) argument error
iex> elem({:foo, :bar}, 2) 
** (ArgumentError) argument error

iex> put_elem({:foo, :ok, 3}, 2, 4)
{:foo, :ok, 4}
iex(1)> tuple = {:foo, :bar, 3}
iex(2)> put_elem(tuple, 0, :baz)
{:baz, :bar, 3}

iex(1)> tuple = {:ok, 1, "hello"}
{:ok, 1, "hello"}
iex(2)> tuple_size(tuple)
3
Enter fullscreen mode Exit fullscreen mode

File.read/1

The File module is an interface module that provides an interface to the file system. The read(path) function returns a tuple with the atom :ok as the first element and the file contents as the second. Elixir allows us to pattern match on tagged tuples and effortlessly handle both success and failure cases.

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

What is the difference between lists and tuples?

Lists and Tuples?

iex > list = [1, 2, 3] 
[1, 2, 3]

# This is fast as we only need to traverse `[0]` to prepend to `list` 
iex > [0] ++ list [0, 1, 2, 3]

# This is slow as we need to traverse `list` to append 4
iex > list ++ [4] [1, 2, 3, 4]
Enter fullscreen mode Exit fullscreen mode
#When you update a tuple, all entries are shared between the old and the new tuple, except for the entry that has been replaced
iex >tuple = {:a, :b, :c, :d}
{:a, :b, :c, :d}

iex >put_elem(tuple, 2, :e)
{:a, :b, :e, :d}
Enter fullscreen mode Exit fullscreen mode

Final Thoughts

This wraps up today's introduction to collection data types in Elixir: Lists and Tuples. If you haven't read the previous article, Basic types in Elixir, be sure to check it out. Our next article will delve into Pattern matching. Make sure not to miss it. I hope this article helps you. See you next time, bye!

. . . .