Lists and tuples in Elixir
Tags: webdev, beginners, tutorial, elixir
Published: 2024-06-23 12:54:49

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.

```elixir
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
```

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

```elixir
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]
```

### 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):

```elixir
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]
```

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

```elixir
iex> [head | tail] = [1, 2, 3]
iex> head
1
iex> tail
[2, 3]
```
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.

```elixir
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
```


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

```elixir
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
```

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

```elixir
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
```

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

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



## What is the difference between lists and tuples?
![Lists and Tuples?](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qtuf1cen0tfo1pvp64cr.png)

```elixir
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]
```

```elixir
#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}
```



## 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](https://dev.to/rhaenyraliang/basic-types-in-elixir-2f3l), 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!