3.17

Array

We covered plenty of functions from the List module, but we didn’t get to one of the most common operations performed on lists: accessing elements by index. That’s because there is no easy way to do that with lists. Because of the way lists are implemented, they are easy to traverse linearly, but accessing an element in random order is expensive. For example, to get to the last element in the list shown below, we have to first traverse through the six elements before it which is inefficient. We can imagine how inefficient it will get when a list has thousands of elements in it.

Not to worry - Elm offers another data structure called Array that makes accessing a random element a breeze. Arrays are very similar to lists in behavior. Almost any operation that can be performed on a list can be performed on an array too.

Creating an Array

We can’t create an array with a literal syntax like list. The most common way to create an array is to first create a list and transform it to an array.

> import Array

> myArray = Array.fromList [ 1, 2, 3, 4 ]
Array.fromList [1,2,3,4]

> myArray
Array.fromList [1,2,3,4]

Array is the first module we’ve encountered so far that isn’t automatically loaded by the repl. Therefore, we need to import it explicitly. fromList is a function that takes a list and creates an array from it. Notice when we printed the value of myArray constant, the repl gave us Array.fromList [1,2,3,4] as the output? That whole output is what represents the array. [1,2,3,4] by itself is still just a list.

We can also transform an array to a list.

> Array.toList myArray
[0,1,2,3,4]

If we want to include the index of each element when we transform an array to a list, we can do that too using the toIndexedList function.

> myArray = Array.fromList [ "Doctor", "River", "Clara", "Tardis" ]
Array.fromList ["Doctor","River","Clara","Tardis"]

> Array.toIndexedList myArray
[(0,"Doctor"),(1,"River"),(2,"Clara"),(3,"Tardis")]

Sometimes we need to initialize each element of an array with some value. We can use the initialize function for that. The example below creates a list with all elements initialized to zero.

> Array.initialize 5 (always 0)
Array.fromList [0,0,0,0,0]

initialize takes two parameters:

  1. Length of an array
  2. Function for generating each element. initialize passes the index of an element as an argument to this function.

We used the always function (defined in the Basics module) to return 0 for each element. always is defined like this:

always a b = a

It’s a constant function that ignores the second argument and always returns the first argument.

Constant Function
A constant function in mathematics is a function whose output is the same for every input value.

Another way to initialize an array with the same value is to use the repeat function.

> Array.repeat 5 0
Array.fromList [0,0,0,0,0]

> Array.repeat 3 "hodor"
Array.fromList ["hodor","hodor","hodor"]

The Basics module also provides a function called identity that unlike always returns the given value. Let’s use this function to create an array whose elements and the indexes are exactly the same.

> Array.initialize 5 identity
Array.fromList [0,1,2,3,4]

Unlike always, identity is not a constant function - its output depends on what the input is.

Getting and Setting a Value

To retrieve a specific element from an array, all we have to do is specify its index and the get function will return that element for us.

> myArray = Array.fromList [ 0, 1, 2, 3, 4 ]
Array.fromList [0,1,2,3,4]

> Array.get 3 myArray
Just 3

get returns an element wrapped inside Just. We saw a similar example when we asked a list to give us its head. We will learn how to unwrap a value from Just in chapter 4. What happens if we try to access an index that doesn’t exist?

> Array.get 5 myArray
Nothing

> Array.get -1 myArray
Nothing

Instead of an error, we get Nothing which makes perfect sense. Similarly, we can set an element at a particular index by using the set function.

> Array.set 3 8 myArray
Array.fromList [0,1,2,8,4]

set takes three arguments:

  1. The index of the element we want to update
  2. The new value that’ll replace the existing element
  3. An array

It’s important to know that set returns a new array instead of modifying an existing array. If we print the contents of myArray, we will see that it hasn’t been changed at all.

> myArray
Array.fromList [0,1,2,3,4]

This behavior is consistent with all other operations. It doesn’t matter which data structure we are dealing with, Elm never mutates them. We will discuss immutability in much more detail in the next chapter.

Checking Length

isEmpty function determines whether or not an array is empty whereas length returns the number of elements in an array.

> Array.isEmpty (Array.fromList [])
True

> Array.isEmpty (Array.fromList [ "Dolores", "Teddy", "Elsie" ])
False

> Array.length (Array.fromList [])
0

> Array.length (Array.fromList [ 1, 2, 3 ])
3

> Array.length (Array.fromList [ "Daenerys", "Tyrion", "Arya", "Khal Drogo" ])
4

isEmpty and length functions in array work exactly the same way as in list.

Combining Arrays

The append function allows us to combine two arrays.

> array1 = Array.fromList [ 1, 2, 3 ]
Array.fromList [1,2,3]

> array2 = Array.fromList [ 4, 5, 6 ]
Array.fromList [4,5,6]

> Array.append array1 array2
Array.fromList [1,2,3,4,5,6]

We cannot, however, use the ++ operator to append two arrays.

> array1 ++ array2

----------------- TYPE MISMATCH -----------------------
The left argument of (++) is causing a type mismatch.

6|   array1 ++ array2
     ^^^^^^
(++) is expecting the left argument to be a:

    appendable

But the left argument is:

    Array.Array number

Hint: Only strings, text, and lists are appendable.

If we want to add an element to the end of an array, we can do that too with push function.

> Array.push 4 array1
Array.fromList [1,2,3,4]

There is no function to add an element to the beginning of an array. That’s because it’s an expensive operation. Each element has to be moved one step right and if an array has lots of elements in it, it could get quite slow from performance standpoint. But adding an element to the end of an array is quite cheap because it doesn’t require other elements to move.

One way to get around the performance issues inherent with trying to add an element to the beginning of an array is to append an array with one element instead.

> Array.append (Array.fromList [0]) array1
Array.fromList [0,1,2,3]

Splitting Arrays

Remember how we extracted a substring from a string using the slice function?

> String.slice 0 5 "Bears. Beets. Battlestar Galactica."
"Bears"

> String.slice 7 12 "Bears. Beets. Battlestar Galactica."
"Beets"

The Array module also provides a function called slice that lets us get a sub-section of an array. Like String.slice, it takes three parameters:

  1. Start index of the sub-array
  2. End index of the sub-array
  3. An array
> myArray = Array.fromList [ 0, 1, 2, 3, 4, 5 ]
Array.fromList [0,1,2,3,4,5]

> Array.slice 0 3 myArray
Array.fromList [0,1,2]

Like String.slice, Array.slice also extracts elements up to but not including the end index. We can also use negative indices.

> myArray
Array.fromList [0,1,2,3,4,5]

> Array.slice 0 -1 myArray
Array.fromList [0,1,2,3,4]

Notice how we popped off the last element from myArray by using a negative index. Nice trick, huh?

Mapping, Filtering, and Folding an Array

In the List section we took a closer look at what map, filter, and fold operations really mean. Their behavior is exactly the same in arrays too. Let’s see some examples.

Mapping

> Array.map (\x -> x * 2) (Array.fromList [ 1, 2, 3, 4 ])
Array.fromList [2,4,6,8]

> guardians = Array.fromList [ "Star-lord", "Groot", "Gamora", "Drax", "Rocket" ]
Array.fromList ["Star-lord","Groot","Gamora","Drax","Rocket"]

> lengths = Array.map String.length guardians
Array.fromList [9,5,6,4,6]

Filtering

> Array.filter (\x -> x < 6) lengths
Array.fromList [5,4]

> isOdd number = if number % 2 == 0 then False else True
<function>

> Array.filter isOdd (Array.fromList [ 0, 1, 2, 3, 4, 5 ])
Array.fromList [1,3,5]

> isHost name = List.member name [ "Dolores", "Teddy", "Maeve" ]
<function>

> westWorldCharacters = Array.fromList [ "William", "Bernard", "Dolores", "Teddy" ]
Array.fromList ["William","Bernard","Dolores","Teddy"]

> Array.filter isHost westWorldCharacters
Array.fromList ["Dolores","Teddy"]

Folding from left

> myArray = Array.fromList [ 1, 2, 3, 4 ]
Array.fromList [1,2,3,4]

> Array.foldl (+) 0 myArray
10

> Array.foldl (*) 1 myArray
24

> guardians = Array.fromList [ "Star-lord", "Groot", "Gamora", "Drax", "Rocket" ]
Array.fromList ["Star-lord","Groot","Gamora","Drax","Rocket"]

> Array.foldl (\x a -> (String.length x) + a) 0 guardians
30

Folding from right

> myArray = Array.fromList [ 4, 2, 3 ]
Array.fromList [4,2,3]

> Array.foldr (^) 1 myArray
65536

List vs Array

Now that we know what lists and arrays are, we are left with an important question: which one should we use and when? As lists have no easy way to access an element by index, we have no choice but to use arrays when we need positional access. But in all other cases, I recommend using lists because they are the standard choice for representing a sequence of values in Elm. Furthermore, creating a list is easy with literal square brackets, whereas creating an array is cumbersome. We have to first create a list and then use fromList function to convert it to an array.

Back to top

New chapters are coming soon!

Sign up for the Elm Programming newsletter to get notified!

* indicates required
Close