(Linked) Lists
A linked list is a heterogeneous list of elements that are stored at different locations in memory and are kept track of by using references. Linked lists are data structures especially used in functional programming.
Elixir uses square brackets to specify a list of values. Values can be of any type −
[1, 2, true, 3]
When Elixir sees a list of printable ASCII numbers, Elixir will print that as a char list (literally a list of characters). Whenever you see a value in IEx and you are not sure what it is, you can use the i function to retrieve information about it.
IO.puts([104, 101, 108, 108, 111])
The above characters in the list are all printable. When the above program is run, it produces the following result −
hello
You can also define lists the other way round, using single quotes −
IO.puts(is_list(''Hello''))
When the above program is run, it produces the following result −
true
Keep in mind single-quoted and double-quoted representations are not equivalent in Elixir as they are represented by different types.
Length of a List
To find the length of a list, we use the length function as in the following program −
IO.puts(length([1, 2, :true, "str"]))
The above program generates the following result −
4
Concatenation and Subtraction
Two lists can be concatenated and subtracted using the ++ and — operators. Consider the following example to understand the functions.
IO.puts([1, 2, 3] ++ [4, 5, 6]) IO.puts([1, true, 2, false, 3, true] -- [true, false])
This will give you a concatenated string in the first case and a subtracted string in the second. The above program generates the following result −
[1, 2, 3, 4, 5, 6] [1, 2, 3, true]
Head and Tail of a List
The head is the first element of a list and the tail is the remainder of a list. They can be retrieved with the functions hd and tl. Let us assign a list to a variable and retrieve its head and tail.
list = [1, 2, 3] IO.puts(hd(list)) IO.puts(tl(list))
This will give us the head and tail of the list as output. The above program generates the following result −
1 [2, 3]
Note − Getting the head or the tail of an empty list is an error.
Other List functions
Elixir standard library provides a whole lot of functions to deal with lists. We will have a look at some of those here.
S.no. | Function Name and Description |
---|---|
1 |
delete(list, item) Deletes the given item from the list. Returns a list without the item. If the item occurs more than once in the list, just the first occurrence is removed. |
2 |
delete_at(list, index) Produces a new list by removing the value at the specified index. Negative indices indicate an offset from the end of the list. If index is out of bounds, the original list is returned. |
3 |
first(list) Returns the first element in list or nil if list is empty. |
4 |
flatten(list) Flattens the given list of nested lists. |
5 |
insert_at(list, index, value) Returns a list with value inserted at the specified index. Note that index is capped at the list length. Negative indices indicate an offset from the end of the list. |
6 |
last(list) Returns the last element in list or nil if list is empty. |
Tuples
Tuples are also data structures which store a number of other structures within them. Unlike lists, they store elements in a contiguous block of memory. This means accessing a tuple element per index or getting the tuple size is a fast operation. Indexes start from zero.
Elixir uses curly brackets to define tuples. Like lists, tuples can hold any value −
{:ok, "hello"}
Length of a Tuple
To get the length of a tuple, use the tuple_size function as in the following program −
IO.puts(tuple_size({:ok, "hello"}))
The above program generates the following result −
2
Appending a Value
To append a value to the tuple, use the Tuple.append function −
tuple = {:ok, "Hello"} Tuple.append(tuple, :world)
This will create and return a new tuple: {:ok, “Hello”, :world}
Inserting a Value
To insert a value at a given position, we can either use the Tuple.insert_at function or the put_elem function. Consider the following example to understand the same −
tuple = {:bar, :baz} new_tuple_1 = Tuple.insert_at(tuple, 0, :foo) new_tuple_2 = put_elem(tuple, 1, :foobar)
Notice that put_elem and insert_at returned new tuples. The original tuple stored in the tuple variable was not modified because Elixir data types are immutable. By being immutable, Elixir code is easier to reason about as you never need to worry if a particular code is mutating your data structure in place.
Tuples vs. Lists
What is the difference between lists and tuples?
Lists are stored in memory as linked lists, meaning that each element in a list holds its value and points to the following element until the end of the list is reached. We call each pair of value and pointer a cons cell. This means accessing the length of a list is a linear operation: we need to traverse the whole list in order to figure out its size. Updating a list is fast as long as we are prepending elements.
Tuples, on the other hand, are stored contiguously in memory. This means getting the tuple size or accessing an element by index is fast. However, updating or adding elements to tuples is expensive because it requires copying the whole tuple in memory.