# Programming in JuliaLists and Tuples

Let's revisit the spreadsheet example we discussed earlier: suppose you're writing a spreadsheet application and you want to introduce some functionality for highlighting every row whose third-column value is greater than 10:

 20 16 2 1 19 9 12 15 1 19 7 2 1 15 4 19 6 16 4 7 3 14 3 1 1 16 5 15 6 6 14 9 7 18 15 15 9 3 9 16 13 6 13 10 20 10 14 5 8 8 4 13 16 15 9 16 9 4 14 1 17 9 4 3 8 2 6 4 6 14 15 8 14 3 14 14 19 8 17 10 18 8 9 5 9 4 4 5 5 8 11 8 1 14 2 12 11 13 19 7

We definitely don't want to think of 100 variable names for the 100 values in the table, and we don't want to write a line of code for each row. What we need is a way to store all of the rows (or columns) in an object designed to contain many objects. Julia provides several such compound data structures, and in this section we will learn about two: arrays and tuples.

## Arrays

A Array in Julia is a compound data type for storing a finite ordered sequence of Julia objects. Arrays are mutable, meaning that they can be changed.

The simplest way to produce an array in a Julia program is with a array literal, which requires listing the objects separated by commas and delimited by square brackets:

myArray = [1, "flower", true, 7]
x = 5
myOtherArray = [1, x, x, 2]

Exercise
What happens to myOtherArray in the example above if a different value is assigned to x after myOtherArray is created?

Solution. The list doesn't change. The object associated with the variable x is retrieved when the list is created, and after that point the list is no longer connected to the name x.

Like strings, arrays can be indexed to obtain their elements. The keyword end in an array index refers to the last index.

myArray = [1, "flower", true, 7]
myArray[1] # returns 1
myArray[4] # returns 7
myArray[end-1] # returns true

Subarrays can be extracted by slicing. Indexing a list with the range object i:j returns the portion of the list from the ith element to the jth element.

myList = [1, "flower", true, 7]
myList[1:3]

Exercise
If i = and j = , then myList[i:j] is equal to ["flower", true].

Range objects can include a step value between the starting and ending values. For example, A[1::9] returns the elements of A at positions 1, 3, 5, 7, and 9.

Exercise
What step value can be used to reverse a list? (Hint: you can reason it out!)

[2,4,6,8][] # insert a range object to return [8,6,4,2]

Solution. Going in reverse order through a list corresponds to stepping by each time. Using the range object end : -1 : 1 to index an array reverses the array.

Arrays can be concatenated with the vcat function:

vcat([1,2,3],[4,5,6,7])

Elements can be appended to an array with push!:

A = [1,2,5]
push!(A,-4)
A

To perform operations entry-by-entry on two arrays, prefix the operation with a dot:

[1,2,3] .+ [4,5,6]

Same idea for functions. To apply the function sin to each entry in an array of 100 equally spaced values from 0 to 2π, we would do:

sin.(range(0, stop=2π, length=100))

(Note: the range function is another way to produce a range object; it allows us to specify the number of entries rather than the step size.)

Exercise
Write a function which takes as arguments an array A and a positive integer n and rotates A by n positions. In other words, every element of the list should move forward n positions, wrapping around to the beginning if it goes off the end of the list.

"Cyclically shift the A by n positions"
function rotate(A, n)
end

using Test
@test rotate([1,2,3],1) == [3,1,2]
@test rotate([1,2,3],2) == [2,3,1]
@test rotate([1,2,3,4,5],8) == [3,4,5,1,2]

Solution. We figure out where the list needs to be split and concatenate the two resulting sublists in the opposite order:

function rotate(L, n)
k = length(L) - n % length(L) + 1
vcat(L[k:end], L[1:k-1])
end

Arrays may be modified by combining indexing with assignment:

A = [4,-3,2]
A[1] = 1
A[2:3] = [6,3]
A

Exercise
Write a line of code which sets every even-indexed entry of an array A to zero. Note that you can get a list of n zeros fill0,n

A = [1,2,3,4,5,6,7,8,9,10]
A

Solution. A[2::end] = fill(0,length(A)÷2)

## List comprehensions

Two of the most common ways of generating one list from another are (1) applying a given function to every element of the original list, and (2) retaining only those elements of the original list which satisfy a given criterion. These two operations are called map and filter, respectively.

square(x) = x * x

collect(map(square, 0:5)) # returns [0, 1, 4, 9, 16]

collect(filter(iseven, 0:5)) # returns [0,2,4]

The extra calls to collect in the examples above are required to see the result because map and filter are lazy: they return objects which promise to perform the specified calculation when it's needed. The function collect forces Julia to turn such objects into actual arrays.

Julia provides a convenient syntax for both mapping and filtering: the array comprehension. It's essentially a programming version of set builder notation. For example, to square the even numbers from 0 to 4, we can use the following expression:

[x^2 for x in 0:4 if iseven(x)]

Let's break this example down step-by-step: the first value of 0:4 is assigned to the variable x, and then the if expression is evaluated. If it's true, the expression x^2 is evaluated and stored as the first value of the list that is to be returned. Then the second value of 0:4 is assigned to x, the condition is evaluated, and so on.

Exercise
Write an array comprehension which returns a list whose kth entry is the last digit of the kth three-digit prime number. Note: the string function converts an integer into a string.

using Primes: isprime

Solution. Here's an example solution:

using Primes: isprime
[string(k)[end] for k in 100:999 if isprime(k)]

Exercise
Write an array comprehension which takes a array of arrays and returns only those arrays whose second element has a least five elements.

records = [[3, "flower", -1], [2, "rise", 3], [0, "basket", 0]]

Solution. Here's one solution:

     [record for record in records if length(record[2]) ≥ 5]

## Tuples

Tuples are very similar to lists, except that tuples are immutable.

row = (22,2.0,"tomato")
row[3] # returns "tomato"
row[3] = "squash" # throws MethodError

Programmers tend to use tuples instead of lists in situations where position in the tuple carries more meaning than order. For example, perhaps the tuple assigned to row above describes a row of plants in a garden, with the three numbers indicating the number of plants, the number of weeks since they were planted, and the type of plant. We could have chosen some other order for those three values, as long as we're consistent about which position corresponds to which value. By contrast, the 22 heights of the plants on that row would typically be stored in an array, since the list order corresponds to something meaningful in that case (namely, the order of the plants in the row).

Functions often return multiple values by returning a tuple containing those values. You can access individual elements of a tuple without having to index the tuple using tuple unpacking:

mycolor = (1.0,1.0,0.44)
r, g, b = mycolor
b

The convention in Julia for values you don't want to store is to assign them to the variable whose name is just an underscore. That way you don't have to think of names for those variables, and you signal to anyone reading your code that you are not using those values.

Tuple unpacking can be combined with array comprehension syntax. If we want to extract the first element from each tuple in a list of triples, for example, we can do that as follows:

A = [(1,2,3),(4,5,6),(7,8,9)]
[a for (a,_,_) in A]

The value 1 is assigned to a, the value 2 is assigned to the underscore variable, and then the value 3 is also assigned to the underscore variable (this overwrite is no problem since we aren't using that value anyway). Then a is evaluated as the first element in the new list, and the process repeats for the remaining triples in the list.

Exercise
Write a list comprehension which adds the first two elements of each tuple in A. (So for the example above, the resulting list should be [3, 9, 15].)

Solution. Same idea:

A = [(1,2,3),(4,5,6),(7,8,9)]
[a+b for (a,b,_) in A]

Exercise
The fractional part of a positive real number is the part after the decimal point: it's defined to be the positive difference between and the greatest integer which is less than or equal to . You can find the fractional part of x in Julia with the expression mod(x,1)

Find the fractional parts of the first 100 positive integer multiples of . Use the function extrema on the resulting array to find its least and greatest values. Find the ratio of the greatest value to the least.

Solution. We use tuple unpacking to extract the min and max values from the tuple returned by the extrema function.

m,M = extrema([mod(pi*k,1) for k in 1:100])
M/m

A common pattern for generating new arrays combines list comprehension, tuple unpacking, and the function zip. The zip function takes two arrays and returns a single array of pairs of corresponding entries (or three arrays, in which case it returns an array of triples, etc.). For example,

zip(["a", "b", "c"], [1, 2, 3])

returns an object which is equivalent to [("a", 1), ("b", 2), ("c", 3)].

Exercise
Suppose that is a list which stores the heights of 100 cylinders and is a list which stores their radii (in the same order). Write an array comprehension which returns a list containing the volumes of these cylinders.

H = [1, 2, 3]
R = [0.8, 1.0, 1.2]

Solution. We zip H and R and use the volume formula :

H = [1, 2, 3]
R = [0.8, 1.0, 1.2]
[pi*r*r*h for (h,r) in zip(H,R)]

Exercise
Write a function which takes a matrix M and an index i and returns the $i$th column of M. Assume that M is represented as an array of arrays, where each array represents a row.

function select_col(M, i)
end

using Test
@test select_col([[1,2],[3,4]],1) == [1,3]
@test select_col([[7,8],[8,-2],[3,4]],2) == [8,-2,4]


Solution. We use an array comprehension to select the appropriate entry from each row.

function select_col(M, i)
[row[i] for row in M]
end

Exercise
Write a function which reverses the words in a sentence. For simplicity, you may assume that the sentence does not contain punctuation.

Hint: The functions join and split might be helpful.

function reverse_words(sentence)

Solution. We use the string method split, which splits a string on a given character. This gives us a list of the words in the sentence, which we can reverse by indexing with a negative step and rejoin with the join method.
function reverse_words(sentence)
end