Today I'm going to go over the first half of the List module. Much of what you can do with a list involves functions defined in the Enum module, since a list is also an enumerable, but there are some functions that are specific to lists. Some of these functions are useful for any kind of list, while others are more useful for specific type of lists, like keyword lists or character lists.

Most of these functions will have O(n) performance unless otherwise noted.

List.ascii_printable?/2

The List.ascii_printable?/2 function determines whether all the characters in the list are code points that correspond to printable ASCII characters. Note that these are only ASCII characters, and not UTF-8 characters. Most of the UTF-8 code points do not correspond to ASCII characters, and even fewer of them correspond printable ASCII characters.

Through experimentation, I've found that printable ASCII characters correspond to the following code points: 7 - 13, 27, 32 - 126. This makes sense, since basic ASCII characters are in the range 0 - 127, and some of those characters are non-printable teletype control characters. Some of those "printable" characters like code point 7, which was meant to control the bell on the teletype, and 8, which represents a backspace character, are questionable, but since IEx will print those out as escape sequences, so I guess Elixir considers them to be a printable character.

iex> [6]
[6]
iex> [7]
'\a'
iex> [8]
'\b'

Here are some examples.

iex> List.ascii_printable?(45))
true
iex> List.ascii_printable?([45, 87, 91])
true
iex> List.ascii_printable?([45, 87, 91, 2])
false
iex> List.ascii_printable?(Enum.to_list(32..127))
false
iex> List.ascii_printable?(Enum.to_list(32..126))
true
iex> List.ascii_printable?(Enum.to_list(1..126))
false
iex> List.ascii_printable?([45, 87, "Bob"])
false
iex> List.ascii_printable?([45, 87, 37.2])
false
iex> List.ascii_printable?([:ok, true])
false

Note that only lists of numbers are considered to have printable ASCII characters, and those must be within the number ranges that I listed above.

List.delete/2

The List.delete/2 function deletes a particular item from a list. If the same item can be found in the list more than once, just the first item is deleted.

iex> List.delete([3, 4, -3, 8, 10], 4)
[3, -3, 8, 10]
iex> List.delete([3, 4, -3, 8, 10], -3)
[3, 4, 8, 10]
iex> List.delete([3, 4, 8, -3, 8, 10], 8)
[3, 4, -3, 8, 10]

List.delete_at/2

The List.delete_at/2 function deletes the item at a particular index. Negative indexes indicate an offset from the end rather than the beginning of the list.

iex> List.delete_at([3, 4, 8, -3, 8, 10], 0)
[4, 8, -3, 8, 10]
iex> List.delete_at([3, 4, 8, -3, 8, 10], 1)
[3, 8, -3, 8, 10]
iex> List.delete_at([3, 4, 8, -3, 8, 10], 2)
[3, 4, -3, 8, 10]
iex> List.delete_at([3, 4, 8, -3, 8, 10], -2)
[3, 4, 8, -3, 10]
iex> List.delete_at([3, 4, 8, -3, 8, 10], -1)
[3, 4, 8, -3, 8]
iex> List.delete_at([3, 4, 8, -3, 8, 10], 10)
[3, 4, 8, -3, 8, 10]
iex> List.delete_at([3, 4, 8, -3, 8, 10], -10)
[3, 4, 8, -3, 8, 10]

If the index is beyond the bounds of the list, the function returns the unmodified list.

List.duplicate/2

The List.duplicate/2 function takes a value and a number and create a list containing the value repeated N times, where N is the number parameter. This is a quick way of creating a list consisting of the same value repeated multiple times.

iex> List.duplicate(3, 4)
[3, 3, 3, 3]
iex> List.duplicate(3, 2)
[3, 3]
iex> List.duplicate("Doom", 8)
["Doom", "Doom", "Doom", "Doom", "Doom", "Doom", "Doom", "Doom"]
iex> List.duplicate(1..10, 8)
[1..10, 1..10, 1..10, 1..10, 1..10, 1..10, 1..10, 1..10]

List.first/1

The List.first/1 returns the first item in a list or nil of the list is empty. The performance of this function is O(1), since the first element in a list is always easily accessible without any iterating.

iex> List.first([3, 4, 5, 6, 7])
3
iex> List.first(["Dib", "Gaz", "Gir"])
"Dib"
iex> List.first(["Zim"])
"Zim"
iex> List.first([])
nil
iex> List.first(1..10)
** (FunctionClauseError) no function clause matching in List.first/1

    The following arguments were given to List.first/1:

        # 1
        1..10

    (elixir) lib/list.ex:221: List.first/1

The range did not work because a range is not a list. Range and lists were interchangeable when giving examples of Enum functions, but List functions must be passed a list.

List.flatten/1

The List.flatten/1 function flattens a list and returns it. The flattening causes all elements in sub-lists to be added to the same list. This function is like Enum.flat_map, but without the mapping part.

iex> List.flatten([[1, 2], [3, 4]])
[1, 2, 3, 4]
iex> List.flatten([[1, [2]]])
[1, 2]
iex> List.flatten([[1, 2, [3, 4]], ["a", "b", "c", ["d", ["e", ["f", "g"]]]]])
[1, 2, 3, 4, "a", "b", "c", "d", "e", "f", "g"]
iex> List.flatten([[1, 2], 1..5])
[1, 2, 1..5]
iex> Enum.flat_map([[1, 2], 1..5], fn x -> x end)
[1, 2, 1, 2, 3, 4, 5]

As we can see from the second-to-last example, this function will only flatten sub-lists and not any other kind of enumerable. The last example shows how we can use Enum.flat_map/2 to flatten all the enumerables instead of just the lists.

List.flatten/2

The List.flatten/2 function is like List.flatten/1 except that it receives a second list to add onto the end.

iex> List.flatten([[1, 2], [3, 4]], ["a", "b", "c"])
[1, 2, 3, 4, "a", "b", "c"]
iex> List.flatten([[1, 2], [3, 4]], ["a", ["b", "c"]])
[1, 2, 3, 4, "a", ["b", "c"]]

As we can see from the example above, the second list is just added as-is, and is not flattened.

List.foldl/3

The List.foldl/3 function is a reduce function. A reduce operation is sometimes called "fold". This is equivalent to Enum.reduce/3. I actually don't see any difference between this function and Enum.reduce/3, except that this function only operates on lists. I guess that it's somehow more efficient with lists than the Enum version? The documentation doesn't say, but I don't know why there would be another function that would do the same thing.

iex> List.foldl([1, 2, 3, 4, 5], 0, fn (num, sum) -> num + sum end)
15
iex> List.foldl(["a", "b", "c", "d", "e"], "", fn (letter, string) -> string <> letter end)
"abcde"

List.foldr/3

This List.foldr/3 function works just like List.foldl/3, except that it starts with the last element in the list and works its way backward toward the beginning. For an operation like a summation this makes no difference, but an order-dependent operation will be carried out in reverse order.

iex> List.foldr([1, 2, 3, 4, 5], 0, fn (num, sum) -> num + sum end)
15
iex> List.foldr(["a", "b", "c", "d", "e"], "", fn (letter, string) -> string <> letter end)
"edcba"

List.insert_at/3

The List.insert_at/3 function inserts a value in the specified index. A negative index indicates an offset from the end of the list.

iex> List.insert_at([1, 2, 3, 4, 5], 3, "Cucumber")
[1, 2, 3, "Cucumber", 4, 5]
iex> List.insert_at([1, 2, 3, 4, 5], 0, "Cucumber")
["Cucumber", 1, 2, 3, 4, 5]
iex> List.insert_at([1, 2, 3, 4, 5], 4, "Cucumber")
[1, 2, 3, 4, "Cucumber", 5]
iex> List.insert_at([1, 2, 3, 4, 5], 5, "Cucumber")
[1, 2, 3, 4, 5, "Cucumber"]
iex> List.insert_at([1, 2, 3, 4, 5], 10, "Cucumber")
[1, 2, 3, 4, 5, "Cucumber"]
iex> List.insert_at([1, 2, 3, 4, 5], -2, "Cucumber")
[1, 2, 3, 4, "Cucumber", 5]
iex> List.insert_at([1, 2, 3, 4, 5], -1, "Cucumber")
[1, 2, 3, 4, 5, "Cucumber"]
iex> List.insert_at([1, 2, 3, 4, 5], -10, "Cucumber")
["Cucumber", 1, 2, 3, 4, 5]

It looks like that if the index goes beyond the bounds of the array, it will insert the value at the end if the index is positive, or at the beginning if the index is negative.

List.keydelete/3

The List.keydelete/3 function is the first in a series of functions in the List module that appears to be meant for use with keyword lists. All of these keyword list functions have names that start with the word "key".

The List.keydelete/3 function takes a list of tuples, a value, and a tuple index, and looks for for a tuple that has the given value at the specified tuple index. Once a matching tuple is found, that tuple is deleted from the list. Only the first matching tuple will be deleted.

Here are some examples:

#Delete the tuple where the first index has the value :name.
#One tuple will match.
iex> List.keydelete([name: "Bob", age: 3, location: :home], :name, 0)
[age: 3, location: :home]
#Delete the tuple where the second index has the value :name
#No tuples will match.
iex> List.keydelete([name: "Bob", age: 3, location: :home], :name, 1)
[name: "Bob", age: 3, location: :home]
#Delete the tuple where the second index has the value :home.
#One tuple will match.
iex> List.keydelete([name: "Bob", age: 3, location: :home], :home, 1)
[name: "Bob", age: 3]
#Delete the tuple where the second index has the value 2.
#One tuple will match.
iex> List.keydelete([{1, 2, 3}, {4, 5, 6}, {7, 8, 9}], 2, 1)
[{4, 5, 6}, {7, 8, 9}]
#Delete the tuple where the first index has the value 2.
#No tuples will match.
iex> List.keydelete([{1, 2, 3}, {4, 5, 6}, {7, 8, 9}], 2, 0)
[{1, 2, 3}, {4, 5, 6}, {7, 8, 9}]
#Delete the tuple where the third index has the value 6.
#One tuple will match.
iex> List.keydelete([{1, 2, 3}, {4, 5, 6}, {7, 8, 9}], 6, 2)
[{1, 2, 3}, {7, 8, 9}]
#Delete the tuple where the second index has the value 2.
#All tuples will match, but only the first will be deleted.
iex> List.keydelete([{1, 2, 3}, {1, 2, 3}, {1, 2, 3}], 2, 1)
[{1, 2, 3}, {1, 2, 3}]

So although this function is intended for use with keyword lists, it will function on any list of tuples with tuples of any size.

I tried this function out on a list containing no tuples, expecting an error. To my surprise, there were none. Instead, it appears that this function ignores any non-tuple elements and just looks for elements that are tuples. So we can also give it a list that has both tuple elements and non-tuple elements.

iex> List.keydelete([1, 2, 3, 4, 5, 6], 2, 0)
[1, 2, 3, 4, 5, 6]
iex> List.keydelete([1, 2, 3, {2, 1, 2}, 5, 6], 2, 0)
[1, 2, 3, 5, 6]

The documentation indicates that the list has to be a list of just tuples, so this was a surprise to me. Perhaps this only works by coincidence and not by design.

List.keyfind/4

The List.keyfind/4 functions similarly to List.keydelete/3, except that instead of deleting the matching tuple, the function will return the matching tuple. If no matching tuple is found, nil is returned. The fourth parameter is an optional parameter that allows you to override the value that is returned when no matching tuple is found.

Here are some examples.

#Find the tuple where the first index has the value :name.
#One tuple will match.
iex> List.keyfind([name: "Bob", age: 3, location: :home], :name, 0)
{:name, "Bob"}
#Find the tuple where the second index has the value :name.
#No tuples will match.
iex> List.keyfind([name: "Bob", age: 3, location: :home], :name, 1)
nil
#Do the same thing, but have it return :not_found when there is no match
iex> List.keyfind([name: "Bob", age: 3, location: :home], :name, 1, :not_found)
:not_found
#Find the tuple where the second index has the value 2.
#Multiple tuples will match, but only the first matching tuple will be returned.
iex> List.keyfind([{1, 2, 1}, {2, 2, 3}], 2, 1)
{1, 2, 1}

List.keymember?/3

The List.keymember?/3 function works just like List.keyfind/4, except that instead of returning the match (or a value indicating no match), it returns true if a match was found, otherwise false.

iex> List.keymember?([name: "Bob", age: 3, location: :home], :name, 0)
true
iex> List.keymember?([name: "Bob", age: 3, location: :home], :name, 1)
false
iex> List.keymember?([{1, 2, 1}, {2, 2, 3}], 2, 1)
true
iex> List.keymember?([{1, 2, 1}, {2, 2, 3}], 5, 1)
false

List.keyreplace/4

The List.keyreplace/4 function works like List.keydelete/3, except that instead of deleting the matching tuple, it replaces the matching tuple with another tuple.

#Match
iex> List.keyreplace([name: "Bob", age: 32, location: :home], :location, 0, {:location, :away})
[name: "Bob", age: 32, location: :away]
#No match
iex> List.keyreplace([name: "Bob", age: 32, location: :home], :occupation, 0, {:occupation, "Von Neumann Probe"})
[name: "Bob", age: 32, location: :home]

List.keysort/2

The List.keysort/2 function sorts a list of tuples by the values at the specified index. So if we pass an index of 2, the tuples will all be sorted by the value at index 2 in each tuple.

Let's see some examples of this in action.

#Sort the tuples by the values at tuple index 0
iex> List.keysort([{5, 10, 3}, {-8, 0, 4}, {6, -2, 22}], 0)
[{-8, 0, 4}, {5, 10, 3}, {6, -2, 22}]
#Sort the tuples by the values at tuple index 1
iex> List.keysort([{5, 10, 3}, {-8, 0, 4}, {6, -2, 22}], 1)
[{6, -2, 22}, {-8, 0, 4}, {5, 10, 3}]
#Sort the tuples by the values at tuple index 2
iex> List.keysort([{5, 10, 3}, {-8, 0, 4}, {6, -2, 22}], 2)
[{5, 10, 3}, {-8, 0, 4}, {6, -2, 22}]
#Sort the tuples by the values at tuple index 3.
#This fails because there is no index 3
iex> List.keysort([{5, 10, 3}, {-8, 0, 4}, {6, -2, 22}], 3)
** (ArgumentError) argument error
    (stdlib) lists.erl:757: :lists.keysort/2
#Sort the tuples by the values at tuple index 1.
#This fails because one of the tuples does not have a value at index 1
iex> List.keysort([{5, 10, 3}, {-8}, {6, -2, 22}], 1)
** (ArgumentError) argument error
    (stdlib) lists.erl:757: :lists.keysort/2

List.keystore/4

The List.keystore/4 function works just like List.keyreplace/4, except that if there is no matching tuple, the new tuple is placed at the end of the list. It acts like a dictionary set operation for keyword lists.

#Match. In this case, it behaves just like List.keyreplace/4
iex> List.keystore([name: "Bob", age: 32, location: :home], :location, 0, {:location, :away})
[name: "Bob", age: 32, location: :away]
#No Match. The new tuple is added onto the end of the list
iex> List.keystore([name: "Bob", age: 32, location: :home], :occupation, 0, {:occupation, "Von Neumann Probe"})
[name: "Bob", age: 32, location: :home, occupation: "Von Neumann Probe"]

List.keytake/3

The List.keytake/3 function acts like a combination of List.keyfind/4 and List.keydelete/3 in that it returns a matching tuple (like List.keyfind/4) and the list without the tuple (like List.keydelete/3. As with List.keyfind/4, if List.keytake/3 does not find a match, it returns nil. Unlike List.keyfind/4, List.keytake/3 does not allow you to specify another value to return if a match was not found.

If a match occurs, the tuple that matched is returned in the first index of a tuple and the list without the tuple is returned in the second index of that same tuple.

Here are some examples.

#Find the tuple where the first index has the value :name.
#One tuple will match.
iex> List.keytake([name: "Bob", age: 3, location: :home], :name, 0)
{{:name, "Bob"}, [age: 3, location: :home]}
#Find the tuple where the second index has the value :name.
#No tuples will match.
iex> List.keytake([name: "Bob", age: 3, location: :home], :name, 1)
nil
#Find the tuple where the second index has the value 2.
#Multiple tuples will match, but only the first matching tuple will be returned as the tuple match.
iex> List.keytake([{1, 2, 1}, {2, 2, 3}], 2, 1)
{{1, 2, 1}, [{2, 2, 3}]}

That's it for today. We've covered half of the functions in the List module. I'll return to this module later.