IACS Computes! 2019

IACS Computes! High School summer camp

Binder

Day 1

Day 2

Day 3

Day 4

Day 5

Day 6

Day 7

Day 8

Day 9

View the Project on GitHub harpolea/IACS_computes_2019

Lists

We’ve learned how to assign names to variables and store things in them like numbers or strings. But sometimes, it’s advantageous to use the same name to refer to a bunch of numbers or strings. For example, let’s say you want to do some calculations with a some student test scores. Here’s one way you could store those as variables:

score1 = 89
score2 = 77
score3 = 100
score4 = 95
score5 = 88

This is only 5 test scores, and it took a lot of typing to store them into variables. Can you imagine what it would be like with a class of 30 students? And then what if you wanted to calculate the average of these scores? It would take longer to do that than simply not using variables at all. Fortunately there is a better, faster way.

Instead of assigning each number to a single variable, we can combine each of the scores into a single list of numbers and assign this list to a single variable called scores.

scores = [89, 77, 100, 95, 88]

We can also print all of the test scores at once by printing this variable:

print(scores)
[89, 77, 100, 95, 88]

You can put any kind of variable in a list, such as strings or booleans:

names = ["Billy", "Matthew", "Shannon", "Kristen", "Taylor"]
print(names)
['Billy', 'Matthew', 'Shannon', 'Kristen', 'Taylor']
key = [True, False, False, True, False]
print(key)
[True, False, False, True, False]

An important aspect of lists is that they are ordered. This means that the list [1,2,3] is different from the list [2,1,3]. This can work to our advantage, because each element of a list is uniquely identified by its position in the list.

Lists wouldn’t be very useful if we couldn’t access the individual components. To do this, we use square brackets [ ]. Inside the square brackets we put the position of the number (or string or boolean, etc.) that we want to use.

print(scores[0])
print(scores[1])
print(scores[2])
print(scores[3])
print(scores[4])
89
77
100
95
88

Note: Python begins numbering the elements of the list from 0. The number used to access an element of a list (i.e. the number inside the square brackets) is called an index. So the first element of the list is indexed as the 0th element. What happens if we try to access scores with the index 5?

print(scores[5])
---------------------------------------------------------------------------

IndexError                                Traceback (most recent call last)

<ipython-input-7-ad4a294d22ac> in <module>
----> 1 print(scores[5])


IndexError: list index out of range

We get an error. This is because we’re trying to access an element of a list that doesn’t exist.

While we can’t access scores[5], we can access scores[-1]. When Python encounters a negative index, it starts counting from the end of the list and returns the value it finds. So the last element in the list can be accessed with the index -1.

scores[-1]
88

Note that this only works up until you reach the beginning of the list, so the most negative index that won’t return an error is -(length of list). In our example, scores[-5] would return 89, the first entry of the list, but scores[-6] would give an error.

When we access an element of a list, it behaves just like the type that is stored in the list. For example, we can add two elements of a list (when that addition makes sense).

print(scores[0] + scores[1])
166
print(names[0] + names[1])
BillyMatthew

We can also make changes to a single element of a list (i.e. we can reassign what is stored at a certain position in the list). For example, let’s say we made an error and need to change the third student’s score to 79. We can do that like this:

scores[2] = 79 # An index of 2 will give us the 3rd element of the list
print(scores)
[89, 77, 79, 95, 88]

In Python, the elements of a list don’t have to be the same type. It’s therefore possible for us to do something like this:

randomlist = ["h", 67, True, 9, "masonry", [7, "moon", [3]], False, True]
print(randomlist)
['h', 67, True, 9, 'masonry', [7, 'moon', [3]], False, True]

From this example, we can see that we can even put lists inside of lists (and lists inside those). These are known as nested lists. We can access the elements of these nested lists by adding another set of brackets to the right containing the index of the element within the inner list. For example, if we want to print “moon” from the above nested list, we’d first access the nested list (which is the 6th element in the list so has index 5), then we’d access the second element (index 1) of that inner list:

print(randomlist[5][1])
moon

Some functions we can call on lists

There are some useful built-in functions we can call on lists to help us manipulate them. A function is a named block of code that we can run by typing its name. We’ve already seen a few functions: type and print are functions that are built in to the main Python library. When we call a function, we often (but not always!) give it one or more inputs. These inputs are known as the function’s arguments. Again, we’ve already seen this, as when we called the print function, we write its name followed by a set of parentheses containing what we want to the function to print for us (i.e. the print function’s argument).

The len function (short for length) tells us how many items are in a list.

len(scores)
5

This is useful if you want to use a loop to perform the same action for all the items in a list.

Say we decided to add 5 points to the score of every student. We can do so like this:

print(scores) # For reference, let's see what scores is right now
[89, 77, 79, 95, 88]
i = 0
while i < len(scores):
    scores[i] = scores[i] + 5  # add 5 to the ith element of scores
    i = i + 1                  # don't forget to increment i
print(scores)
[94, 82, 84, 100, 93]

We can use the append function to add another student’s score to the list.

print(scores)      # again, print beforehand for reference
scores.append(85)  # This is a special function that uses a period to act on something. Note the syntax.
print(scores)
[94, 82, 84, 100, 93]
[94, 82, 84, 100, 93, 85]

The append function is very useful for building up a list from scratch (e.g. inside a loop). One very useful technique is starting an empty list and appending to it later. Accumulating elements of a list in a loop like this is very common.

Note: if you are working with very large loops and very long lists, then appending items to the end of the list can be very slow and should be avoided. Instead, you should create a list with the required number of elements before you enter the loop, then in the body of the loop update the elements to their correct values. Of course, this means that you need to have some idea of what the final length of the list should be, which is not always possible. In these cases, append can still be necessary.

mylist = [] # Make an empty list 

The code below will now put the first 8 even numbers into mylist.

i = 0
while i < 8:
    mylist.append(2*i)  #i runs from 0 to 7, so 2*i for each i will give us the first 8 evens
    i = i + 1
print(mylist)
[0, 2, 4, 6, 8, 10, 12, 14]

The del function removes the element of the list at the index specified. For example, this code deletes the last element of scores.

print(scores)
del scores[-1]
print(scores)
[94, 82, 84, 100, 93, 85]
[94, 82, 84, 100, 93]

A note of caution: be careful using the del function in loops, as it’s easy to lose track of how long your list is. This can understandably cause many errors.

So far we have seen how to add single elements to a list. But how can we append another list to a given list?

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

i = 0
while(i < len(b)):
    a.append(b[i])
    i = i + 1
print(a)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

There is much simpler way to append a list into another list by using the in built Python function extend.

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

a.extend(b)
print(a)
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

We can test to see if a list contains some element using the keyword in. This will return a boolean (True or False).

print(1 in a)
print(13 in a)
True
False

We can use the function sorted to sort a list.

a = [98, 43, 56, 38, 99, 17, 22]
sorted_a = sorted(a)
print("a :", a)
print("sorted a:", sorted_a)
a : [98, 43, 56, 38, 99, 17, 22]
sorted a: [17, 22, 38, 43, 56, 98, 99]

Slicing

What if we want to select more than one element in our list? For this, we can use slicing. Let’s first define a list a.

a = [98, 43, 56, 38, 99, 17, 22]

To define a slice from elements between indexes 1 and 5, we would write [1:5]:

print(a[1:5])
[43, 56, 38, 99]

But that only printed 4 numbers??!! In Python, a slice will return you the elements starting with the first index in the slice, up to but not including the last element. So a[1:5] returned the elements with indices 1, 2, 3, 4 but not 5.

If we leave out the index before the colon, then Python will start the slice with the 0th element:

print(a[:2])
[98, 43]

Similarly, if we leave out the index after the colon, then the slice will go from the first index up to the end of the list:

print(a[3:])
[38, 99, 17, 22]

As you may have guessed, if we leave out both indices, then we will get the full list. In this case, a[:] is just the same as writing a.

print(a[:])
[98, 43, 56, 38, 99, 17, 22]

We can use the negative indices we saw before to access elements at the end of the list. For example, we can get the last two elements of the list by writing

print(a[-2:])
[17, 22]

What if we want to miss out elements? We can define a step size, that will allow us to access every $n$th element. For example, if we want to access the 1st, 3rd and 5th elements in our list, we would define a slice with step size 2:

print(a[1:6:2])
[43, 38, 17]

We can also use negative step sizes in order to print the list backwards:

print(a[::-1])
[22, 17, 99, 38, 56, 43, 98]

The indexing can get a little tricky here. For example, what would we write if we wanted to print the list backwards without the last element? You may think that it would be a[:-1:-1], but that just gives us an empty list:

print(a[:-1:-1])
[]

In fact, to do this we’d write

print(a[-2::-1])
[17, 99, 38, 56, 43, 98]

What’s going on here??? We can understand this by imagining that Python starts of by reversing our list, but keeping the indices of the elements the same. The second-to-last element which we wish to begin our new reversed list therefore still has the index -2. We therefore use this index as the starting point for our slice. As we haven’t provided an index to the right of the first colon, Python will then print the rest of the elements after this one in the reversed list.

Tuples

Before we move on to some examples, let’s briefly look at tuples. Tuples are very similar to lists, however they are computer scientists call immutable (un-changeable). This means that once we have defined a tuple, we cannot go back and change its elements (as we did with lists). We define a tuple in Python by using parentheses instead of square brackets.

my_tuple = (5, 7, 1, 8, 2)

As you can see, elements of this tuple can be accessed in the same way as the element of a list:

print(my_tuple[0])
print(my_tuple[2:])
5
(1, 8, 2)

However, if we try to change one of the elements of the tuple, Python gives us an error:

my_tuple[1] = 3
---------------------------------------------------------------------------

TypeError                                 Traceback (most recent call last)

<ipython-input-38-0a2170fd1534> in <module>
----> 1 my_tuple[1] = 3


TypeError: 'tuple' object does not support item assignment

Due to their limitations, tuples may appear to be just a less useful type of list. However, you will often see them used to pass in arguments to functions and more complex data structures where their unchangeable nature (their immutability) can be very useful.

Worked Examples

Given the list below, what is the average of the first, third, and fifth entries?

# The list holding the entries we want to look at
the_list = [98, 43, 56, 38, 99, 17, 22]

# Remember, Python starts indexing at 0. So to access
# the first entry, we need index 0, to access the
# the third entry, we need index 2, and the fifth entry 
# would be index 4. We want the average, so we need to
# add the entries together and divide by the number
# of entries. So lets add the entries together.
the_average = the_list[0] + the_list[2] + the_list[4]

# We have the sum, now we need to divide by the number
# of entries.
the_average = the_average / 3

# We've calculated the average. Now we need to display 
# it
print(the_average)
84.33333333333333

Here’s the above code without comments:

sum(the_list)
373
the_list = [98, 43, 56, 38, 99, 17, 22]

the_average = sum(the_list[:5:2])

the_average /= 3

print(the_average)
84.33333333333333

Practice Problems

Write some code that creates a list that contains 5 seperate entries with value 1. (That is, the list has length 5 and every entry has value 1)

ones = [1,1,1,1,1]
ones
[1, 1, 1, 1, 1]
counter = 0

ones = []

while counter < 5:
    ones.append(1)
    counter += 1
    
print(ones)
[1, 1, 1, 1, 1]
ones = [1] * 5
ones
[1, 1, 1, 1, 1]

Write some code that creates a list containing the first 17 odd numbers.

counter = 0

odds = []

while counter < 17:
    odds.append(2 * counter + 1)
    counter += 1
odds
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33]
list(range(1,18))

list(range(2*17))[1::2]
[1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31, 33]

Write some code that creates a list of all multiples of 5 less than 100.

counter = 5
multiples = []

while counter < 100:
    multiples.append(counter)
    counter += 5
    
print(multiples)
[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]
list(range(100))[5::5]
[5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55, 60, 65, 70, 75, 80, 85, 90, 95]

Write some code that sums all the entries of a list.

random_list = [234,587341,456.34,82,2985]

sum(random_list)
591098.34

Write some code that calculates the average of a list of numbers.

sum(random_list) / len(random_list)
118219.66799999999

Write some code that copies all numbers from the given list below that are not multiples of 2 and puts them in a new list. (Hint: This can be done using a loop with a conditional).

first_list = [ 1, 4, 9, 16, 25, 36, 49, 64, 81, 100]

new_list = []
counter = 0

while counter < len(first_list):
    element = first_list[counter]
    
    if element % 2 != 0:
        new_list.append(element)
    
    counter += 1
    
print(new_list)
[1, 9, 25, 49, 81]

Advanced Problems

Standard Deviation: Write some code that calculates the standard deviation of a list of numbers. The formula for the standard deviation is

where $N$ is the number of elements in the list, $x_i$ is a particular element of the list, and $\bar{x}$ is the average of the list of numbers. (Hint: you will need to calculate the average first).

Test your code on the given list. The answer is approximately 31.23.

test_list = [7, 39, 2, 56, 98, 74, 34, 17, 56, 88, 66, 0, 56, 34]

average = sum(test_list) / len(test_list)

counter = 0
my_sum = 0

while counter < len(test_list):
    my_sum += (test_list[counter] - average)**2
    counter += 1

from math import sqrt
standard_deviation = sqrt(1 / (len(test_list) - 1) * my_sum)
standard_deviation
31.234050875061563

Median: Write some code that calculates the median of a list of numbers. The answer for test_list is 47.5.

sorted_list = sorted(test_list)

central_index = len(test_list) // 2

median = sum(sorted_list[central_index - 1:central_index+1]) / 2

median
47.5

Mode: Write some code that calculates the mode of a list of numbers. The answer for test_list is 56.

# this is tricky without using more complex data structures we haven't seen yet,
# so while this solution works in practice it's pretty fiddly and not a great way 
# of doing this

# first sort the list 
sorted_list = sorted(test_list)

# make a variable to keep track of the most common one 
most_common = -1
# variable holding how many times it's repeated 
most_common_reps = 0
# also need a local value to hold number of reps of test values
local_reps = 0

# now loop over the list 
counter = 1

while counter < len(sorted_list):
    # check to see if the element of the list is the same 
    # as the previous element
    if sorted_list[counter] == sorted_list[counter - 1]:
        # if so we increment the number of local reps
        local_reps += 1
        
        # check to see if the local number of reps is greater that 
        # the largest one found so far
        if local_reps > most_common_reps:
            most_common = sorted_list[counter]
            most_common_reps = local_reps
            
    else:
        # reset the number of local reps
        local_reps = 0
        
    counter += 1
    
most_common
56

Back to day 2