.. _lists-and-tuples: ################ Lists and Tuples ################ .. include:: /references.inc .. wordcount:: .. seealso:: 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 *************** .. code-block:: c 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: .. code-block:: python >>> 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: .. code-block:: python >>> 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: .. code-block:: python >>> 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! .. code-block:: python >>> print(grades[4]) 95 >>> print(grades[5]) Traceback (most recent call last): File "", line 1, in 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: .. code-block:: python >>> 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 ---------------------------- .. code-block:: python >>> 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 ------------------------------------ .. code-block:: python >>> del grades[2] >>> print(grades) [100, 48, 95] Remove the last item in the list -------------------------------- .. code-block:: python >>> 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: .. code-block:: python 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: .. literalinclude:: code/ArrayTest1.py :language: python Which gives us this: .. code-block:: bash 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. .. literalinclude:: code/ArrayTest2.py 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: .. literalinclude:: code/ArrayTest3.py This now produces this output: .. code-block:: text 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: .. literalinclude:: code/ArrayTest4.py 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. .. code-block:: python >>> 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: .. code-block:: python >>> index = grades.index(88) >>> print grades[index] File "", line 1 >>> print(grades[index]) 88 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: .. literalinclude:: code/Biggest.py 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: .. code-block:: c 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: .. code-block:: python 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``: .. cvs-table b,Binary number c,Character (unicode) d,Decimal integer f,Fixed point (float) o,Octal format s,String x,Hexadecimal 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: .. code-block:: python 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! .. code-block:: python >>> 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: .. csv-table:: :widths: 5,25,15 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: .. code-block:: python >>> alist = [] >>> print(len(alist)) 0 Once the list has been created, we can change it: Adding an element to a list --------------------------- .. code-block:: python >>> 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: .. code-block:: python >>> 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: .. code-block:: python >>> 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: .. code-block:: python >>> 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: .. code-block:: python >>> blist = ['p','a','f','d'] >>> print(blist) ['p', 'a', 'f', 'd'] We can insert a new element in the middle of the list: .. code-block:: python >>> blist.insert(3,'x') >>> print(blist) ['p', 'a', 'f', 'x', 'd'] We can sort the list - if that makes sense! .. code-block:: python >>> blist.sort() >>> print(blist) ['a', 'd', 'f', 'p', 'x'] And, even flip it around: .. code-block:: python >>> blist.reverse() >>> print(blist) ['x', 'p', 'f', 'd', 'a'] Phew! That is enough about lists for today!