Lists and Tuples

Read time: 52 minutes (13162 words)

See also

Reading Assignment

Text: Chapter 8

Now, we come to an important concept - the Data Structure. Up to now, we have been using small containers to hold fairly simple pieces of data. Things like integers, floats, characters are all pretty small chunks of stuff, and only take a few of our little eight-bit boxes to store in memory. We cannot make very interesting programs if all we can do is play around with simple data like these.

To paraphrase the sheriff in Jaws “We need a bigger boat” - er container for our data! Here is how we do it:

Creating a list

grades = [];

Hmmm. All we have done here is create a container and assign something involving square brackets to it. What is going on? We have told Python we want to set up a list of objects (there is that silly object thing again!) and initially it is empty. We will add items to this list and possibly remove items or reorder them as the program runs.

Lists can be any size, well, up to the limit of the amount of memory your machine makes available to the program. Lists can hold any Python data item. What may be confusing is that they can hold other lists as well, something we will look at later. For now, let’s focus on simple lists of numbers.

This group of memory locations is called a data structure to emphasize the fact that we are dealing with a group of containers, each big enough to hold a single data item. Obviously, we have to work harder to initialize this new kind of container, and we have several ways to do that. If the number of integers is small enough, we can use this approach:

>>> grades = [100,92,48,87,95]
>>> print(grades)
[100, 92, 48, 87, 95]
>>> print(len(grades))
5

Here, we have provided a list of values to be used to create the named container grades. This kind of initialization would be a real pain for a big list with lots of numbers, but for smaller lists, this is a common approach.

Lists are ordered, which means that the order of the items in the list is significant. It also means that that order will not change unless your code changes it!

Notice that we can ask Python how long this list is by using the len function, and it returns the number of data items in the list.

Python can figure how many items are in the list all by itself. Here is a version of the for-loop that can cycle through all the elements of the list and print them out:

>>> for item in grades:
...     print(item)
...
100
92
48
92
95

Here, item is a variable that will be assigned the values from the list, in order, as the loop runs. When the last item in the list has been processed, the loop stops.

Working with lists

Once we have a list to work with, we can do many things with it.

Accessing elements in the list

We can access individual elements in the list by adding square brackets to the end of the list name:

>>> print(grades[0])
100

The number inside the square brackets is called the index into the list. This index must be a number, and the list must contain an item at that location, or we will get an error.

Warning

In most programming languages, unlike real life, the first container in a group of containers has an index of zero, not one! So, for our last example, we can refer to each of the five locations in the container named grades by using the index numbers from zero to four!

Far to many mistakes in programming are due to thinking the first index should be one, and the last index should be five!

>>> print(grades[4])
95
>>> print(grades[5])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range

Here I tried to access an item that is not in the list. That last index (5) is just past the end of the list.

Warning

The list has five items, but the last index in the list is four! That fact is something you need to get used to, it is common in all programming languages!

Altering the contents of the list

We can modify an item in the list by indicating what index we are interested in modifying:

>>> grades[3] = 92
>>> print(grades)
[100, 92, 48, 92, 95]

Deleting an item from a list

Occasionally, you might like to remove an item from a list. There are three ways to do this:

Remove a value from the list
>>> print( grades)
[100, 92, 48, 92, 95]
>>> grades.remove(92)
>>> print(grades)
[100, 48, 92, 95]

Notice that it removed the first such value from the list

Remove an indexed item from the list
>>> del grades[2]
>>> print(grades)
[100, 48, 95]
Remove the last item in the list
>>> del grades[-1]
>>> print(grades)
[100,48]

Huh! That last index was weird. Python can act on lists of any size, and we often do not know how many elements we have. The -1 index indicates the last item in the list. (Guess what -2 would mean!)

Building up a large list

Suppose we had a big class and wanted to initialize all the grades to zero (OK, lets make it 100, just to be nicer). We can set up a simple loop to do the job:

grades = []
...
for index in range(0:25):
    grades.append(100)

Here, we see the loop counter variable is not used directly, but it must be there to control the loop. On each pass through the loop, a object with a value of 100 is crated and appended to the end of the current list. When the smoke clears, we have 25 objects in the list, all with the same value.

Examples of array problems

Let’s consider the problem of setting a letter grade for a numerical grade. Lets set up a problem and solve it using lists:

# Test grader

grades = [100,77,65,88,99,90,80,50,69,70]

def main():
    thisLetter = 'A'
    for grade in grades:
        print("A grade of",grade,"gets a",thisLetter)
main()

Which gives us this:

A grade of 100 gets a A
A grade of 77 gets a A
A grade of 65 gets a A
A grade of 88 gets a A
A grade of 99 gets a A
A grade of 90 gets a A
A grade of 80 gets a A
A grade of 50 gets a A
A grade of 69 gets a A
A grade of 70 gets a A

Well, that would be nice, but unfortunately, we cannot let this happen. Since we know about simple functions, let’s set up a function to determining the letter grade from a number grade.

# Test grader

grades = [100,77,65,88,99,90,80,50,69,70]

def get_letter(val):
    return 'A'

def main():
    thisLetter = 'A'
    for grade in grades:
        print("A grade of",grade,"gets a",get_letter(grade))
main()

Our new function returns a character and takes a single integer parameter.

This gives the same result, but it decomposes the problem into two parts, and now we know exactly where to put the code that figures out the real grade.

A simple way to complete this logic is to use a series of nested if-then-else statements:

# Test grader

grades = [100,77,65,88,99,90,80,50,69,70]

def get_letter(grade):
    if grade >= 90: return 'A'
    elif grade >= 80: return 'B'
    elif grade >= 70: return 'C'
    elif grade >= 60: return 'D'
    else: return 'F'

def main():
    thisLetter = 'A'
    for grade in grades:
        print("A grade of",grade,"gets a",get_letter(grade))
main()

This now produces this output:

A grade of 100 gets a A
A grade of 77 gets a C
A grade of 65 gets a D
A grade of 88 gets a B
A grade of 99 gets a A
A grade of 90 gets a A
A grade of 80 gets a B
A grade of 50 gets a F
A grade of 69 gets a D
A grade of 70 gets a C
Press any key to continue . . .

While this does work, we are trying to learn something about lists here, so let’s rework this function and use more lists. Try this on for size:

# Test grader

grades = [100,77,65,88,99,90,80,50,69,70]

def get_letter(grade):
    letters = ['A','B','C','D','F']
    mingrade = 90
    for i in range(0,5):
        letter = letters[i]
        if grade >= mingrade: break
        mingrade = mingrade -10
    return letter

def main():
    thisLetter = 'A'
    for grade in grades:
        print("A grade of",grade,"gets a",get_letter(grade))
main()

Phew, how does this code work. Well, to figure it out, we need to walk it through by hand and see if it does what we want.

What is the overall structure of this code?

In this version, we store a list of possible letter grades in another list (letters). The grades are arranged in the correct order, and we all know the rules for assigning letter grades to number grades. Grades from 90 to 100 get an A, from 80-89 get a B and so on. The break point starts off at 90 and decreases by 10 for each letter grade.

Hmmm, maybe we can use this fact to figure out the letter grade.

Inside the body of this function, we use another loop to figure out exactly what letter grade to assign.

Figuring out the letter grade

Since we have five possible letter grades available, we could loop over those grades and search for the one that one applies to the grade we are looking at at the moment. As soon as we can see that we have the right grade assigned, we can bail out of this loop and print out the answer. But, how can we know we have found the right letter grade.

Lets set up an integer variable called mingrade and set it to the lowest grade that will earn the first letter in our letter grade array.

Anything equal to or above a 90 will get an A.

Now we loop over the five letter grades, using the counter i to track which letter we are examining. If the grade we are checking is bigger than (or equal to) the minimum grade, we have found our letter grade and we can stop looking. The if statement checks this, and uses the break statement to bail out of the inner loop.

If the grade we are examining is less than the current minimum grade value, we have not found the right letter grade yet, so we need to move on to the next letter in the letter array. For this next letter, the minimum grade is 10 points lower than it was for the last letter, so we decrease our ‘’‘mingrade’‘’ variable by 10 and let the loop try again.

You need to think about what happens if the number grade really gets an F. Is the logic of this code working right? On the last pass through the inner loop, what value does minGrade have? Is 50 really the lowest number grade that gets an F? Actually, no - any number grade we have left when we reach this last pass gets an F. The variable grade will probably still be lower than the mingrade value, but all that means is that we will not break out of this loop on the if-statement. But, since we are on the last pass, we will exit the loop anyway with letter set to an F just like we want.

This is too complicated!

Well, I admit that thinking this logic up might not come naturally to you until you have fought through a few situations like this. What is important here is that you see how the loops work and can walk your way through the code and figure out what is happening.

Note

You learn a lot about programming by studying “other people’s code”. There is a huge amount of code available on the Internet (and GitHub!), and when you want to figure out how to do something, use your best friend; Google to find example code!

If you like, you can always rewrite this to use the nested if-statements we used earlier to figure out the letter grades - eliminating the inner loop and array of letter grades in the process. Which way is better? You get to decide as long as both work!

Other operations with lists

Python has a few more tricks up it’s sleeves (Huh? A snake has no arms!)

Searching an array

We can ask if a value is in the list.

>>> grades = [100, 97, 35, 88, 99]
>>> if 99 in grades:
...     print("Found it!")
...
Found it!

We can get the index of a value, but only if it is in the list:

>>> index = grades.index(88)
>>> print grades[index]
>>> print(grades[index])
88
  File "<stdin>", line 1

How would we find the biggest item in a list? Python has a simple way to do this, but let’s think it through the hard way!

If the list only had one item in it what would the biggest item be? (That was a trick question!)

Suppose the list has two items. We might assume that the first item was the biggest, but we are not sure until we check the second item as well. We can extend this idea into a list of any size. The process involves assuming the first one is biggest, then checking all the other items to see if any of them are bigger. If we find a bigger value, we throw away our previous guess and replace it with the current biggest. Along the way we can track where we found the biggest as well.

Try this:

# Test grader

grades = [45,77,65,88,99,90,80,50,69,70]

def max(grade_list):
    biggest = grade_list[0]
    bigindex = 0
    bindex = 0
    for grade in grade_list:
        if grade > biggest:
            biggest = grade
            bigindex = bindex
        bindex += 1
    return(biggest, bigindex)


def main():
    maxgrade, bindex = max(grades)
    print("Found the biggest grade (%d) at index %d" % (maxgrade, bindex))
    
main()

In this code, we are using a loop to search all the grades in the list. We start off looking at the first grade in the array. Is it the biggest? It is the biggest of all the grades we have looked at so far, so let’s keep that value as the current biggest. We also keep track of where that value was found in bigindex`. The ``bindex variable is a counter we are using to track the items we are examining. As the loop continues, if we find anything bigger, we will change our value to that new biggest, and so on. So, here is what we might end up with:

python Biggest.py
Found the biggest grade(99) at index 4

Looks like it worked. Neat!

Wait a minute! What was that funny function doing. It returned more than one item. In face, it returned a list of items, and we provided a list of variables to store the results in. We did not use square brackets here, but we got more than one value out.

Look closely at the print statement. It has another list in it, but this one is in parentheses. What is that thing?

More Python data types

There are more types available in Python, and part of the fun of learning new languages is exploring how to use these new types and why!

Tuples

A tuple is much like a list with one important difference. We can modify a list, but a tuple cannot be changed once it is created. One common use of tuples is in output statements like this:

print "This string has three numbers in it: %d, %d,  and %d" % (5,3, 1)

Note

The %X markers are called format specifiers that are placeholders for data items that will be formatted in those locations according to the code specified. This is very common in many languages. Here are a few of the specifiers we can use in place of the X:

There is a lot more to formatting output, which we will look at in a later lecture.

Tuples are a series of Python objects separated by commas and enclosed by parentheses. In Python everything is an object, and it may surprise you to see all the ways you can construct data types in the language. Just because we showed three numbers in the tuple above does not limit us:

print ("This is an odd list: %s, %f, %d" % ("hello", 1.25, 6))

One of the most powerful features of Python is the ease with which you can construct complicated data containers. This is made possible by the simple fact that something like a tuple or a list can contain a series of any kind of Python data - including another tuple!

>>> tuple1 = (1,2,3)
>>> tuple2 = (4,5,6)
>>> tuple3 = (7,8,9)
>>> tuple4 = (tuple1,tuple2,tuple3)
>>> print tuple4
((1, 2, 3), (4, 5, 6), (7, 8, 9))
>>> print tuple4[1][1]
5
>>> print tuple4[0][2]
3
>>>

That sure looks like a two-dimensional data container (matrix) that would be handy in many engineering applications. Want a three dimensional data container, no problem! Four dimensions? Yikes!

Note

In this class, you are not responsible for anything but a single list of values, not a list of lists. But thinking through this concept is a good idea anyway!

Actually, I have been able to visualize such things using a simple scheme:

1d a line of containers 1 subscript
2d a rectangle of containers 2 subscripts
3d a block of containers 3 subscripts
4d a line of blocks 4 subscripts
5d a rectangle of blocks 5 subscripts
6d a block of blocks 6 subscripts

See how this works? You can keep going as far as you need. Do folks ever need this kind of data container. Absolutely. I used nine-dimensional containers when I was doing computational aerodynamics!

Immutable vs mutable

Immutable means that once a thing have been created it cannot be modified. (It is acting like a constant of a sort). That might seem to be a problem, but, as you can see from the example above, we can always build a new tuple out of old ones whenever we like.

A last comment on lists

Lists are mutable, meaning we can add or delete elements from a list!

Creating an empty list

This is simple:

>>> alist = []
>>> print(len(alist))
0

Once the list has been created, we can change it:

Adding an element to a list
>>> alist.append("hello")
>>> print(alist)
['hello']
>>> alist.append("world")
>>> print(alist)
['hello', 'world']
>>> for a in alist: print(a)
...
hello
world
Adding another list to a list

What if we want to add more than one element to a list? Try this:

>>> alist.append(['goodbye','world'])
>>> print(alist)
['hello', 'world', ['goodbye', 'world']]

That is not quite what I want. I cannot access the elements of the list correctly, as you might expect if you thought about how Python works:

>>> for a in alist: print (a)
...
hello
world
['goodbye', 'world']

We can combine the two lists in what we might think is a more normal way by using a different scheme:

>>> alist = ['hello','world']
>>> alist.extend(['goodbye','world'])
>>> for a in alist: print(a)
...
hello
world
goodbye
world
>>> print(alist)
['hello', 'world', 'goodbye', 'world']
>>>

Obviously, this is a better solution for combining two lists!

Reordering lists

Normally, lists retain their order. Here are examples of changing the order:

Here is a basic list:

>>> blist = ['p','a','f','d']
>>> print(blist)
['p', 'a', 'f', 'd']

We can insert a new element in the middle of the list:

>>> blist.insert(3,'x')
>>> print(blist)
['p', 'a', 'f', 'x', 'd']

We can sort the list - if that makes sense!

>>> blist.sort()
>>> print(blist)
['a', 'd', 'f', 'p', 'x']

And, even flip it around:

>>> blist.reverse()
>>> print(blist)
['x', 'p', 'f', 'd', 'a']

Phew! That is enough about lists for today!