Tuesday, January 31, 2017

Elixir - Lists and Tuples

(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 very useful data structures especially used a lot 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 quite 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 running above program, it produces following result:
'hello'
You can also define lists the other way round, using single quotes:
IO.puts(is_list('Hello'))
When running above program, it produces 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:
IO.puts(length([1, 2, :true, "str"]))
When running above program, it produces following result:
4

Concatenation and Subtraction

Two lists can be concatenated and subtracted using the ++ and -- operators. For example,
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. When running above program, it produces 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’s 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. When running above program, it produces 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. You can check out the rest at: List.
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:
IO.puts(tuple_size({:ok, "hello"}))
When running above program, it produces following result:
2

Appending a value

To append a value to the tuple, use Tuple.append function:
tuple = {:ok, "Hello"}
Tuple.append(tuple, :world)
This will create and return a new tuple: {:ok, "Hello", :world}

Insertng a value

To insert a value at a given position, we can either use the Tuple.insert_at function or put_elem function. For example:
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. Those performance characteristics dictate the usage of those data structures.

No comments:

Post a Comment