# 10. Lists ## 10.1. Lists A **list** is a sequential collection of Python data values, where each value is identified by an index. The values that make up a list are called its **elements**. Lists are similar to strings, which are ordered collections of characters, except that the elements of a list can have any type and for any one list, the items can be of different types. ## 10.2. List Values There are several ways to create a new list. The simplest is to enclose the elements in square brackets ( `[` and `]`). ```python [10, 20, 30, 40] ["spam", "bungee", "swallow"] ``` The first example is a list of four integers. The second is a list of three strings. As we said above, the elements of a list don’t have to be the same type. The following list contains a string, a float, an integer, and another list. ```python ["hello", 2.0, 5, [10, 20]] ``` A list within another list is said to be **nested** and the inner list is often called a **sublist**. Finally, there is a special list that contains no elements. It is called the empty list and is denoted `[]`. As you would expect, we can also assign list values to variables and pass lists as parameters to functions. ```python vocabulary = ["iteration", "selection", "control"] numbers = [17, 123] empty = [] mixedlist = ["hello", 2.0, 5*2, [10, 20]] print(numbers) print(mixedlist) newlist = [ numbers, vocabulary ] print(newlist) ``` **Check your understanding** A list can contain only integer items. * False * Yes, unlike strings, lists can consist of any type of Python data. * True * Lists are heterogeneous, meaning they can have different types of data. ## 10.3. List Length As with strings, the function `len` returns the length of a list (the number of items in the list). However, since lists can have items which are themselves lists, it important to note that `len` only returns the top-most length. In other words, sublists are considered to be a single item when counting the length of the list. ```python alist = ["hello", 2.0, 5, [10, 20]] print(len(alist)) print(len(['spam!', 1, ['Brie', 'Roquefort', 'Pol le Veq'], [1, 2, 3]])) ``` **Check your understanding** What is printed by the following statements? ```python alist = [3, 67, "cat", 3.14, False] print(len(alist)) ``` * 4 * len returns the actual number of items in the list, not the maximum index value. * 5 * Yes, there are 5 items in this list. What is printed by the following statements? ```python alist = [3, 67, "cat", [56, 57, "dog"], [ ], 3.14, False] print(len(alist)) ``` * 7 * Yes, there are 7 items in this list even though two of them happen to also be lists. * 8 * len returns the number of top level items in the list. It does not count items in sublists. ## 10.4. Accessing Elements The syntax for accessing the elements of a list is the same as the syntax for accessing the characters of a string. We use the index operator ( `[]` – not to be confused with an empty list). The expression inside the brackets specifies the index. Remember that the indices start at 0. Any integer expression can be used as an index and as with strings, negative index values will locate items from the right instead of from the left. ```python numbers = [17, 123, 87, 34, 66, 8398, 44] print(numbers[2]) print(numbers[9 - 8]) print(numbers[-2]) print(numbers[len(numbers) - 1]) ``` **Check your understanding** What is printed by the following statements? ```python alist = [3, 67, "cat", [56, 57, "dog"], [ ], 3.14, False] print(alist[5]) ``` * [ ] * The empty list is at index 4. * 3.14 * Yes, 3.14 is at index 5 since we start counting at 0 and sublists count as one item. * False * False is at index 6. What is printed by the following statements? ```python alist = [3, 67, "cat", [56, 57, "dog"], [ ], 3.14, False] print(alist[2].upper()) ``` * Error, you cannot use the upper method on a list. * alist[2] is the string cat so the upper method is legal * 2 * 2 is the index. We want the item at that index. * CAT * Yes, the string cat is upper cased to become CAT. What is printed by the following statements? ```python alist = [3, 67, "cat", [56, 57, "dog"], [ ], 3.14, False] print(alist[2][0]) ``` * 56 * Indexes start with 0, not 1. * c * Yes, the first character of the string at index 2 is c * cat * cat is the item at index 2 but then we index into it further. * Error, you cannot have two index values unless you are using slicing. * Using more than one index is fine. You read it from left to right. ## 10.5. List Membership `in` and `not in` are boolean operators that test membership in a sequence. We used them previously with strings and they also work here. ```python fruit = ["apple", "orange", "banana", "cherry"] print("apple" in fruit) print("pear" in fruit) ``` **Check your understanding** What is printed by the following statements? ```python alist = [3, 67, "cat", [56, 57, "dog"], [ ], 3.14, False] print(3.14 in alist) ``` * True * Yes, 3.14 is an item in the list alist. * False * There are 7 items in the list, 3.14 is one of them. What is printed by the following statements? ```python alist = [3, 67, "cat", [56, 57, "dog"], [ ], 3.14, False] print(57 in alist) ``` * True * in returns True for top level items only. 57 is in a sublist. * False * Yes, 57 is not a top level item in alist. It is in a sublist. ## 10.6. Concatenation and Repetition Again, as with strings, the `+` operator concatenates lists. Similarly, the `*` operator repeats the items in a list a given number of times. ```python fruit = ["apple", "orange", "banana", "cherry"] print([1, 2] + [3, 4]) print(fruit + [6, 7, 8, 9]) print([0] * 4) print([1, 2, ["hello", "goodbye"]] * 2) ``` It is important to see that these operators create new lists from the elements of the operand lists. If you concatenate a list with 2 items and a list with 4 items, you will get a new list with 6 items (not a list with two sublists). Similarly, repetition of a list of 2 items 4 times will give a list with 8 items. One way for us to make this more clear is to run a part of this example in codelens. As you step through the code, you will see the variables being created and the lists that they refer to. Pay particular attention to the fact that when `newlist` is created by the statement `newlist = fruit + numlist`, it refers to a completely new list formed by making copies of the items from `fruit` and `numlist`. You can see this very clearly in the codelens object diagram. The objects are different. Activity: CodeLens 10.6.2 (chp09\_concatid) In Python, every object has a unique identification tag. Likewise, there is a built-in function that can be called on any object to return its unique id. The function is appropriately called `id` and takes a single parameter, the object that you are interested in knowing about. You can see in the example below that a real id is usually a very large integer value (corresponding to an address in memory). ```python >>> alist = [4, 5, 6] >>> id(alist) 4300840544 >>> ``` **Check your understanding** What is printed by the following statements? ```python alist = [1, 3, 5] blist = [2, 4, 6] print(alist + blist) ``` * 6 * Concatenation does not add the lengths of the lists. * [1, 2, 3, 4, 5, 6] * Concatenation does not reorder the items. * [1, 3, 5, 2, 4, 6] * Yes, a new list with all the items of the first list followed by all those from the second. * [3, 7, 11] * Concatenation does not add the individual items. What is printed by the following statements? ```python alist = [1, 3, 5] print(alist * 3) ``` * 9 * Repetition does not multiply the lengths of the lists. It repeats the items. * [1, 1, 1, 3, 3, 3, 5, 5, 5] * Repetition does not repeat each item individually. * [1, 3, 5, 1, 3, 5, 1, 3, 5] * Yes, the items of the list are repeated 3 times, one after another. * [3, 9, 15] * Repetition does not multiply the individual items. ## 10.7. List Slices The slice operation we saw with strings also work on lists. Remember that the first index is the starting point for the slice and the second number is one index past the end of the slice (up to but not including that element). Recall also that if you omit the first index (before the colon), the slice starts at the beginning of the sequence. If you omit the second index, the slice goes to the end of the sequence. ```python a_list = ['a', 'b', 'c', 'd', 'e', 'f'] print(a_list[1:3]) print(a_list[:4]) print(a_list[3:]) print(a_list[:]) ``` **Check your understanding** What is printed by the following statements? ```python alist = [3, 67, "cat", [56, 57, "dog"], [ ], 3.14, False] print(alist[4:]) ``` * [ [ ], 3.14, False] * Yes, the slice starts at index 4 and goes up to and including the last item. * [ [ ], 3.14] * By leaving out the upper bound on the slice, we go up to and including the last item. * [ [56, 57, "dog"], [ ], 3.14, False] * Index values start at 0. ## 10.8. Lists are Mutable Unlike strings, lists are **mutable**. This means we can change an item in a list by accessing it directly as part of the assignment statement. Using the indexing operator (square brackets) on the left side of an assignment, we can update one of the list items. ```python fruit = ["banana", "apple", "cherry"] print(fruit) fruit[0] = "pear" fruit[-1] = "orange" print(fruit) ``` An assignment to an element of a list is called **item assignment**. Item assignment does not work for strings. Recall that strings are immutable. Here is the same example in codelens so that you can step through the statements and see the changes to the list elements. Activity: CodeLens 10.8.2 (item\_assign) By combining assignment with the slice operator we can update several elements at once. ```python alist = ['a', 'b', 'c', 'd', 'e', 'f'] alist[1:3] = ['x', 'y'] print(alist) ``` We can also remove elements from a list by assigning the empty list to them. ```python alist = ['a', 'b', 'c', 'd', 'e', 'f'] alist[1:3] = [] print(alist) ``` We can even insert elements into a list by squeezing them into an empty slice at the desired location. ```python alist = ['a', 'd', 'f'] alist[1:1] = ['b', 'c'] print(alist) alist[4:4] = ['e'] print(alist) ``` **Check your understanding** What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] alist[2] = True print(alist) ``` * [4, 2, True, 8, 6, 5] * Item assignment does not insert the new item into the list. * [4, 2, True, 6, 5] * Yes, the value True is placed in the list at index 2. It replaces 8. * Error, it is illegal to assign * Item assignment is allowed with lists. Lists are mutable. ## 10.9. List Deletion Using slices to delete list elements can be awkward and therefore error-prone. Python provides an alternative that is more readable. The `del` statement removes an element from a list by using its position. ```python a = ['one', 'two', 'three'] del a[1] print(a) alist = ['a', 'b', 'c', 'd', 'e', 'f'] del alist[1:5] print(alist) ``` As you might expect, `del` handles negative indices and causes a runtime error if the index is out of range. In addition, you can use a slice as an index for `del`. As usual, slices select all the elements up to, but not including, the second index, but do not cause runtime errors if the index limits go too far. Note This workspace is provided for your convenience. You can use this activecode window to try out anything you like. ## 10.10. Objects and References If we execute these assignment statements, ```python a = "banana" b = "banana" ``` we know that `a` and `b` will refer to a string with the letters `"banana"`. But we don’t know yet whether they point to the *same* string. There are two possible ways the Python interpreter could arrange its internal states:  or  In one case, `a` and `b` refer to two different string objects that have the same value. In the second case, they refer to the same object. Remember that an object is something a variable can refer to. We already know that objects can be identified using their unique identifier. We can also test whether two names refer to the same object using the *is* operator. The *is* operator will return true if the two references are to the same object. In other words, the references are the same. Try our example from above. ```python a = "banana" b = "banana" print(a is b) ``` The answer is `True`. This tells us that both `a` and `b` refer to the same object, and that it is the second of the two reference diagrams that describes the relationship. Since strings are *immutable*, Python can optimize resources by making two names that refer to the same string literal value refer to the same object. This is not the case with lists. Consider the following example. Here, `a` and `b` refer to two different lists, each of which happens to have the same element values. ```python a = [81, 82, 83] b = [81, 82, 83] print(a is b) print(a == b) ``` The reference diagram for this example looks like this:  `a` and `b` have the same value but do not refer to the same object. There is one other important thing to notice about this reference diagram. The variable `a` is a reference to a **collection of references**. Those references actually refer to the integer values in the list. In other words, a list is a collection of references to objects. Interestingly, even though `a` and `b` are two different lists (two different collections of references), the integer object `81` is shared by both. Like strings, integers are also immutable so Python optimizes and lets everyone share the same object for some commonly used small integers. Here is the example in codelens. Pay particular attention to the id values. Activity: CodeLens 10.10.3 (chp09\_istrace) ## 10.11. Aliasing Since variables refer to objects, if we assign one variable to another, both variables refer to the same object: ```python a = [81, 82, 83] b = a print(a is b) ``` In this case, the reference diagram looks like this:  Because the same list has two different names, `a` and `b`, we say that it is **aliased**. Changes made with one alias affect the other. In the codelens example below, you can see that `a` and `b` refer to the same list after executing the assignment statement `b = a`. Activity: CodeLens 10.11.2 (chp09\_is3) Although this behavior can be useful, it is sometimes unexpected or undesirable. In general, it is safer to avoid aliasing when you are working with mutable objects. Of course, for immutable objects, there’s no problem. That’s why Python is free to alias strings and integers when it sees an opportunity to economize. **Check your understanding** What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] blist = alist blist[3] = 999 print(alist) ``` * [4, 2, 8, 6, 5] * blist is not a copy of alist, it is a reference to the list alist refers to. * [4, 2, 8, 999, 5] * Yes, since alist and blist both reference the same list, changes to one also change the other. > Consider the following lists: > > ```python > list1=[1,100,1000] > list2=[1,100,1000] > list3=list1 > ``` > > Which statements will output **True**? (Select **all** that apply). * print(list1 == list2) * list1 and list2 have the same value * print(list1 is list2) * list1 and list2 have the same value but do not refer to the same object * print(list1 is list3) * list3 is assigned to list1, so they refer to the same object * print(list2 is not list3) * list2 and list3 do not refer to the same object. * print(list2 != list3) * list2 and list3 have the same value ## 10.12. Cloning Lists If we want to modify a list and also keep a copy of the original, we need to be able to make a copy of the list itself, not just the reference. This process is sometimes called **cloning**, to avoid the ambiguity of the word copy. The easiest way to clone a list is to use the slice operator. Taking any slice of `a` creates a new list. In this case the slice happens to consist of the whole list. Activity: CodeLens 10.12.1 (chp09\_is4) Now we are free to make changes to `b` without worrying about `a`. Again, we can clearly see in codelens that `a` and `b` are entirely different list objects. ## 10.13. Repetition and References We have already seen the repetition operator working on strings as well as lists. For example, ```python origlist = [45, 76, 34, 55] print(origlist * 3) ``` With a list, the repetition operator creates copies of the references. Although this may seem simple enough, when we allow a list to refer to another list, a subtle problem can arise. Consider the following extension on the previous example. ```python origlist = [45, 76, 34, 55] print(origlist * 3) newlist = [origlist] * 3 print(newlist) ``` `newlist` is a list of three references to `origlist` that were created by the repetition operator. The reference diagram is shown below.  Now, what happens if we modify a value in `origlist`. ```python origlist = [45, 76, 34, 55] newlist = [origlist] * 3 print(newlist) origlist[1] = 99 print(newlist) ``` `newlist` shows the change in three places. This can easily be seen by noting that in the reference diagram, there is only one `origlist`, so any changes to it appear in all three references from `newlist`.  Here is the same example in codelens. Step through the code paying particular attention to the result of executing the assignment statement `origlist[1] = 99`. Activity: CodeLens 10.13.4 (reprefstep) **Check your understanding** What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] blist = alist * 2 blist[3] = 999 print(alist) ``` * [4, 2, 8, 999, 5, 4, 2, 8, 6, 5] * print(alist) not print(blist) * [4, 2, 8, 999, 5] * blist is changed, not alist. * [4, 2, 8, 6, 5] * Yes, alist was unchanged by the assignment statement. blist was a copy of the references in alist. What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] blist = [alist] * 2 alist[3] = 999 print(blist) ``` * [4, 2, 8, 999, 5, 4, 2, 8, 999, 5] * [alist] \* 2 creates a list containing alist repeated 2 times * [[4, 2, 8, 999, 5], [4, 2, 8, 999, 5]] * Yes, blist contains two references, both to alist. * [4, 2, 8, 6, 5] * print(blist) * [[4, 2, 8, 999, 5], [4, 2, 8, 6, 5]] * blist contains two references, both to alist so changes to alist appear both times. ## 10.14. List Methods The dot operator can also be used to access built-in methods of list objects. `append` is a list method which adds the argument passed to it to the end of the list. Continuing with this example, we show several other list methods. Many of them are easy to understand. ```python mylist = [] mylist.append(5) mylist.append(27) mylist.append(3) mylist.append(12) print(mylist) mylist.insert(1, 12) # Insert 12 at pos 1, shift other items up print(mylist) print(mylist.count(12)) # How many times is 12 in mylist? print(mylist.index(3)) # Find index of first 3 in mylist print(mylist.count(5)) mylist.reverse() print(mylist) mylist.sort() print(mylist) mylist.remove(5) # Removes the first 12 in the list print(mylist) lastitem = mylist.pop() # Removes and returns the last item of the list print(lastitem) print(mylist) ``` There are two ways to use the `pop` method. The first, with no parameter, will remove and return the last item of the list. If you provide a parameter for the position, `pop` will remove and return the item at that position. Either way the list is changed. The following table provides a summary of the list methods shown above. The column labeled result gives an explanation as to what the return value is as it relates to the new value of the list. The word **mutator** means that the list is changed by the method but nothing is returned (actually `None` is returned). A **hybrid** method is one that not only changes the list but also returns a value as its result. Finally, if the result is simply a return, then the list is unchanged by the method. Be sure to experiment with these methods to gain a better understanding of what they do. | Method | Parameters | Result | Description | | --- | --- | --- | --- | | append | item | mutator | Adds a new item to the end of a list | | insert | position, item | mutator | Inserts a new item at the position given | | pop | none | hybrid | Removes and returns the last item | | pop | position | hybrid | Removes and returns the item at position | | sort | none | mutator | Modifies a list to be sorted | | reverse | none | mutator | Modifies a list to be in reverse order | | index | item | return idx | Returns the position of first occurrence of item | | count | item | return ct | Returns the number of occurrences of item | | remove | item | mutator | Removes the first occurrence of item | Details for these and others can be found in the [Python Documentation](http://docs.python.org/py3k/library/stdtypes.html#sequence-types-str-bytes-bytearray-list-tuple-range). It is important to remember that methods like `append`, `sort`, and `reverse` all return `None`. This means that re-assigning `mylist` to the result of sorting `mylist` will result in losing the entire list. Calls like these will likely never appear as part of an assignment statement (see line 8 below). ```python mylist = [] mylist.append(5) mylist.append(27) mylist.append(3) mylist.append(12) print(mylist) mylist = mylist.sort() #probably an error print(mylist) ``` **Check your understanding** What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] alist.append(True) alist.append(False) print(alist) ``` * [4, 2, 8, 6, 5, False, True] * True was added first, then False was added last. * [4, 2, 8, 6, 5, True, False] * Yes, each item is added to the end of the list. * [True, False, 4, 2, 8, 6, 5] * append adds at the end, not the beginning. What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] alist.insert(2, True) alist.insert(0, False) print(alist) ``` * [False, 4, 2, True, 8, 6, 5] * Yes, first True was added at index 2, then False was added at index 0. * [4, False, True, 2, 8, 6, 5] * insert will place items at the index position specified and move everything down to the right. * [False, 2, True, 6, 5] * insert does not remove anything or replace anything. What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] temp = alist.pop(2) temp = alist.pop() print(alist) ``` * [4, 8, 6] * pop(2) removes the item at index 2, not the 2 itself. * [2, 6, 5] * pop() removes the last item, not the first. * [4, 2, 6] * Yes, first the 8 was removed, then the last item, which was 5. What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] alist = alist.pop(0) print(alist) ``` * [2, 8, 6, 5] * alist is now the value that was returned from pop(0). * [4, 2, 8, 6, 5] * pop(0) changes the list by removing the first item. * 4 * Yes, first the 4 was removed from the list, then returned and assigned to alist. The list is lost. * None * pop(0) returns the first item in the list so alist has now been changed. Note This workspace is provided for your convenience. You can use this activecode window to try out anything you like. ## 10.15. The Return of L-Systems Let’s return to the L-systems we introduced in the previous chapter and introduce a very interesting new feature that requires the use of lists. Suppose we have the following grammar: ```python X X --> F[-X]+X F --> FF ``` This L-system looks very similar to the old L-system except that we’ve added one change. We’ve added the characters ‘[’ and ‘]’. The meaning of these characters adds a very interesting new dimension to our L-Systems. The ‘[’ character indicates that we want to save the state of our turtle, namely its position and its heading so that we can come back to this position later. The ‘]’ tells the turtle to warp to the most recently saved position. The way that we will accomplish this is to use lists. We can save the heading and position of the turtle as a list of 3 elements. `[heading x y]` The first index position in the list holds the heading, the second index position in the list holds the x coordinate, and the third index position holds the y coordinate. Now, if we create an empty list and every time we see a ‘[’ we append the list that contains `[heading, x, y]` we create a history of saved places the turtle has been where the most recently saved location will always be at the end of the list. When we find a ‘]’ in the string we use the pop function to remove the the most recently appended information. Let’s modify our `drawLsystem` function to begin to implement this new behavior. ```python import turtle def drawLsystem(aTurtle, instructions, angle, distance): savedInfoList = [] for cmd in instructions: if cmd == 'F': aTurtle.forward(distance) elif cmd == 'B': aTurtle.backward(distance) elif cmd == '+': aTurtle.right(angle) elif cmd == '-': aTurtle.left(angle) elif cmd == '[': savedInfoList.append([aTurtle.heading(), aTurtle.xcor(), aTurtle.ycor()]) print(savedInfoList) elif cmd == ']': newInfo = savedInfoList.pop() print(newInfo) print(savedInfoList) t = turtle.Turtle() inst = "FF[-F[-X]+X]+F[-X]+X" drawLsystem(t, inst, 60, 20) ``` When we run this example we can see that the picture is not very interesting, but notice what gets printed out, and how the saved information about the turtle gets added and removed from the end of the list. In the next example we’ll make use of the information from the list to save and restore the turtle’s position and heading when needed. We’ll use a longer example here so you get an idea of what the kind of drawing the L-System can really make. ```python import turtle def drawLsystem(aTurtle, instructions, angle, distance): savedInfoList = [] for cmd in instructions: if cmd == 'F': aTurtle.forward(distance) elif cmd == 'B': aTurtle.backward(distance) elif cmd == '+': aTurtle.right(angle) elif cmd == '-': aTurtle.left(angle) elif cmd == '[': savedInfoList.append([aTurtle.heading(), aTurtle.xcor(), aTurtle.ycor()]) print(savedInfoList) elif cmd == ']': newInfo = savedInfoList.pop() aTurtle.setheading(newInfo[0]) aTurtle.setposition(newInfo[1], newInfo[2]) t = turtle.Turtle() inst = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF[-FFFFFFFFFFFFFFFF[-FFFFFFFF[-FFFF[-FF[-F[-X]+X]+F[-X]+X]+FF[-F[-X]+X]+F[-X]+X]+FFFF[-FF[-F[-X]+X]+F[-X]+X]+FF[-F[-X]+X]+F[-X]+X]+FFFFFFFF[-FFFF[-FF[-F[-X]+X]+F[-X]+X]+FF[-F[-X]+X]+F[-X]+X]+FFFF[-FF[-F[-X]+X]+F[-X]+X]+FF[-F[-X]+X]+F[-X]+X]+FFFFFFFFFFFFFFFF[-FFFFFFFF[-FFFF[-FF[-F[-X]+X]+F[-X]+X]+FF[-F[-X]+X]+F[-X]+X]+FFFF[-FF[-F[-X]+X]+F[-X]+X]+FF[-F[-X]+X]+F[-X]+X]+FFFFFFFF[-FFFF[-FF[-F[-X]+X]+F[-X]+X]+FF[-F[-X]+X]+F[-X]+X]+FFFF[-FF[-F[-X]+X]+F[-X]+X]+FF[-F[-X]+X]+F[-X]+X" t.setposition(0, -200) t.left(90) drawLsystem(t, inst, 30, 2) ``` Rather than use the `inst` string supplied here, use the code from the string chapter, and write your own applyRules function to implement this L-system. This example only uses 6 expansions. Try it out with a larger number of expansions. You may also want to try this example with different values for the angle and distance parameters. ## 10.16. Append versus Concatenate The `append` method adds a new item to the end of a list. It is also possible to add a new item to the end of a list by using the concatenation operator. However, you need to be careful. Consider the following example. The original list has 3 integers. We want to add the word “cat” to the end of the list. Activity: CodeLens 10.16.1 (appcon1) Here we have used `append` which simply modifies the list. In order to use concatenation, we need to write an assignment statement that uses the accumulator pattern: ```python origlist = origlist + ["cat"] ``` Note that the word “cat” needs to be placed in a list since the concatenation operator needs two lists to do its work. Activity: CodeLens 10.16.2 (appcon2) It is also important to realize that with append, the original list is simply modified. On the other hand, with concatenation, an entirely new list is created. This can be seen in the following codelens example where `newlist` refers to a list which is a copy of the original list, `origlist`, with the new item “cat” added to the end. `origlist` still contains the three values it did before the concatenation. This is why the assignment operation is necessary as part of the accumulator pattern. Activity: CodeLens 10.16.3 (appcon3) **Check you understanding** What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] alist = alist + 999 print(alist) ``` * [4, 2, 8, 6, 5, 999] * You cannot concatenate a list with an integer. * Error, you cannot concatenate a list with an integer. * Yes, in order to perform concatenation you would need to write alist+[999]. You must have two lists. ## 10.17. Lists and `for` loops It is also possible to perform **list traversal** using iteration by item as well as iteration by index. ```python fruits = ["apple", "orange", "banana", "cherry"] for afruit in fruits: # by item print(afruit) ``` It almost reads like natural language: For (every) fruit in (the list of) fruits, print (the name of the) fruit. We can also use the indices to access the items in an iterative fashion. ```python fruits = ["apple", "orange", "banana", "cherry"] for position in range(len(fruits)): # by index print(fruits[position]) ``` In this example, each time through the loop, the variable `position` is used as an index into the list, printing the `position`-eth element. Note that we used `len` as the upper bound on the range so that we can iterate correctly no matter how many items are in the list. Any sequence expression can be used in a `for` loop. For example, the `range` function returns a sequence of integers. ```python for number in range(20): if number % 3 == 0: print(number) ``` This example prints all the multiples of 3 between 0 and 19. Since lists are mutable, it is often desirable to traverse a list, modifying each of its elements as you go. The following code squares all the numbers from `1` to `5` using iteration by position. ```python numbers = [1, 2, 3, 4, 5] print(numbers) for i in range(len(numbers)): numbers[i] = numbers[i] ** 2 print(numbers) ``` Take a moment to think about `range(len(numbers))` until you understand how it works. We are interested here in both the *value* and its *index* within the list, so that we can assign a new value to it. **Check your understanding** What is printed by the following statements? ```python alist = [4, 2, 8, 6, 5] blist = [ ] for item in alist: blist.append(item+5) print(blist) ``` * [4, 2, 8, 6, 5] * 5 is added to each item before the append is peformed. * [4, 2, 8, 6, 5, 5] * There are too many items in this list. Only 5 append operations are performed. * [9, 7, 13, 11, 10] * Yes, the for loop processes each item of the list. 5 is added before it is appended to blist. * Error, you cannot concatenate inside an append. * 5 is added to each item before the append is performed. ## 10.18. The Accumulator Pattern with Lists Remember the [accumulator pattern](../Functions/TheAccumulatorPattern.html#accumulator)? Many algorithms involving lists make use of this pattern to process the items in a list and compute a result. In this section, we’ll explore the use of the accumulator pattern with lists. Let’s take the problem of adding up all of the items in a list. The following program computes the sum of a list of numbers. ```python sum = 0 for num in [1, 3, 5, 7, 9]: sum = sum + num print(sum) ``` The program begins by defining an accumulator variable, `sum`, and initializing it to 0 (line 1). Next, the program iterates over the list (lines 2-3), and updates the sum on each iteration by adding an item from the list (line 3). When the loop is finished, `sum` has accumulated the sum of all of the items in the list. Take a moment to step through this program using CodeLens to see how it works. It’s important to grasp the basic techniques. Sometimes when we’re accumulating, we don’t want to add to our accumulator every time we iterate. Consider, for example, the following program which counts the number of names with more than 3 letters. ```python long_names = 0 for name in ["Joe", "Sally", "Amy", "Brad"]: if len(name) > 3: long_names += 1 print(long_names) ``` Here, we **initialize** the accumulator variable to be zero on line 1. We **iterate** through the sequence (line 2). The **update** step happens in two parts. First, we check to see if the name is longer than 3 letters. If so, then we increment the accumulator variable `long_names` (on line 4) by adding one to it. At the end, we have accumulated the total number of long names. We can use conditionals to also count if particular items are in a string or list. The following code finds all occurrences of vowels in a string. ```python s = "what if we went to the zoo" num_vowels = 0 for i in s: if i in ['a', 'e', 'i', 'o', 'u']: num_vowels += 1 print(num_vowels) ``` We can also use `==` to execute a similar operation. Here, we’ll check to see if the character we are iterating over is an “o”. If it is an “o” then we will update our counter.  ### 10.18.1. Accumulating the Max Value We can also use the accumulation pattern with conditionals to find the maximum or minimum value. Instead of continuing to build up the accumulator value like we have when counting or finding a sum, we can reassign the accumulator variable to a different value. The following example shows how we can get the maximum value from a list of integers. ```python nums = [9, 3, 8, 11, 5, 29, 2] best_num = 0 for n in nums: if n > best_num: best_num = n print(best_num) ``` Here, we initialize best\_num to zero, assuming that there are no negative numbers in the list. In the for loop, we check to see if the current value of n is greater than the current value of `best_num`. If it is, then we want to **update** `best_num` so that it now is assigned the higher number. Otherwise, we do nothing and continue the for loop. You may notice that the current structure could be a problem. If the numbers were all negative what would happen to our code? What if we were looking for the smallest number but we initialized `best_num` with zero? To get around this issue, we can initialize the accumulator variable using one of the numbers in the list. ```python nums = [9, 3, 8, 11, 5, 29, 2] best_num = nums[0] for n in nums: if n > best_num: best_num = n print(best_num) ``` The only thing we changed was the value of `best_num` on line 2 so that the value of `best_num` is the first element in `nums`, but the result is still the same! ### 10.18.2. Accumulating a String Result The accumulator pattern can be used to convert a list of items to a string. Consider the following program: ```python scores = [85, 95, 70] result = '' for score in scores: result = result + str(score) + ',' print("The scores are " + result) ``` Here, the accumulator variable is `result`. Each time through the loop, the program concatenates the current contents of `result` with the comma separator and a score from the list, and updates the `result` with the new value. Use CodeLens to step through this example to see it in action. The output of the program has some undesirable formatting problems: there is a trailing comma instead of a period, and there are no spaces between the items. The next activity lets you work to correct those problems. Let’s work to improve the formatting of the sentence produced by the program above. Revise the following code so that it outputs the sentence: ```python The scores are 85, 95, and 70. ``` ```python scores = [85, 95, 70] result = '' for score in scores: result = result + str(score) + ',' print("The scores are " + result) ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(self.getOutput().rstrip('\n'), 'The scores are 85, 95, and 70.', "Output should be 'The scores are 85, 95, and 70.'") myTests().main() ``` Try changing the loop so that it does not process the final score. Handle that last score separately, after the loop finishes. This solution works by iterating over all of the scores in the list except the last, and dealing with that one separately. ```python scores = [85, 95, 70] result = '' for score in scores[:-1]: result = result + str(score) + ', ' # Now, append the last score result = result + 'and ' + str(scores[-1]) + '.' print("The scores are " + result) ``` **Check your understanding** What is printed by the following statements? ```python s = "We are learning!" x = 0 for i in s: if i in ['a', 'b', 'c', 'd', 'e']: x += 1 print(x) ``` * 2 * Though only two of the letters in the list are found, we count them each time they appear. * 5 * Yes, we add to x each time we come across a letter in the list. * 0 * Check again what the conditional is evaluating. The value of i will be a character in the string s, so what will happen in the if statement? * There is an error in the code so it cannot run. * There are no errors in this code. What is printed by the following statements? ```python list= [5, 2, 1, 4, 9, 10] min_value = 0 for item in list: if item < min_value: min_value = item print(min_value) ``` * 10 * Not quite. What is the conditional checking? * 1 * min\_value was set to a number that was smaller than any of the numbers in the list, so it was never updated in the for loop. * 0 * Yes, min\_value was set to a number that was smaller than any of the numbers in the list, so it was never updated in the for loop. * There is an error in the code so it cannot run. * The code does not have an error that would prevent it from running. **Challenge** For each word in `words`, add ‘d’ to the end of the word if the word ends in “e” to make it past tense. Otherwise, add ‘ed’ to make it past tense. Save these past tense words to a list called `past_tense`. ```python words = ["adopt", "bake", "beam", "confide", "grill", "plant", "time", "wave", "wish"] ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testNine(self): self.assertEqual(past_tense, ['adopted', 'baked', 'beamed', 'confided', 'grilled', 'planted', 'timed', 'waved', 'wished'], "Testing that the past_tense list is correct.") self.assertIn("else", self.getEditorText(), "Testing output (Don't worry about actual and expected values).") self.assertIn("for", self.getEditorText(), "Testing output (Don't worry about actual and expected values).") myTests().main() ``` ## 10.19. Using Lists as Parameters Functions which take lists as arguments and change them during execution are called **modifiers** and the changes they make are called **side effects**. Passing a list as an argument actually passes a reference to the list, not a copy of the list. Since lists are mutable, changes made to the elements referenced by the parameter change the same list that the argument is referencing. For example, the function below takes a list as an argument and multiplies each element in the list by 2: ```python def doubleStuff(aList): """ Overwrite each element in aList with double its value. """ for position in range(len(aList)): aList[position] = 2 * aList[position] things = [2, 5, 9] print(things) doubleStuff(things) print(things) ``` The parameter `aList` and the variable `things` are aliases for the same object.  Since the list object is shared by two references, there is only one copy. If a function modifies the elements of a list parameter, the caller sees the change since the change is occurring to the original. This can be easily seen in codelens. Note that after the call to `doubleStuff`, the formal parameter `aList` refers to the same object as the actual parameter `things`. There is only one copy of the list object itself. Activity: CodeLens 10.19.2 (chp09\_parm1\_trace) ## 10.20. Pure Functions A **pure function** does not produce side effects. It communicates with the calling program only through parameters (which it does not modify) and a return value. Here is the `doubleStuff` function from the previous section written as a pure function. To use the pure function version of `double_stuff` to modify `things`, you would assign the return value back to `things`. ```python def doubleStuff(a_list): """ Return a new list in which contains doubles of the elements in a_list. """ new_list = [] for value in a_list: new_elem = 2 * value new_list.append(new_elem) return new_list things = [2, 5, 9] print(things) things = doubleStuff(things) print(things) ``` Once again, codelens helps us to see the actual references and objects as they are passed and returned. Activity: CodeLens 10.20.2 (ch09\_mod3) ## 10.21. Which is Better? Anything that can be done with modifiers can also be done with pure functions. In fact, some programming languages only allow pure functions. There is some evidence that programs that use pure functions are faster to develop and less error-prone than programs that use modifiers. Nevertheless, modifiers are convenient at times, and in some cases, functional programs are less efficient. In general, we recommend that you write pure functions whenever it is reasonable to do so and resort to modifiers only if there is a compelling advantage. This approach might be called a *functional programming style*. ## 10.22. Functions that Produce Lists The pure version of `doubleStuff` above made use of an important **pattern** for your toolbox. Whenever you need to write a function that creates and returns a list, the pattern is usually: ```python initialize a result variable to be an empty list loop create a new element append it to result return the result ``` Let us show another use of this pattern. Assume you already have a function `is_prime(x)` that can test if x is prime. Now, write a function to return a list of all prime numbers less than n: ```python def primes_upto(n): """ Return a list of all prime numbers less than n. """ result = [] for i in range(2, n): if is_prime(i): result.append(i) return result ``` ## 10.23. List Comprehensions The previous example creates a list from a sequence of values based on some selection criteria. An easy way to do this type of processing in Python is to use a **list comprehension**. List comprehensions are concise ways to create lists. The general syntax is: ```python [
for
in
if
] ``` where the if clause is optional. For example, ```python mylist = [1,2,3,4,5] yourlist = [item ** 2 for item in mylist] print(yourlist) ``` The expression describes each element of the list that is being built. The `for` clause iterates through each item in a sequence. The items are filtered by the `if` clause if there is one. In the example above, the `for` statement lets `item` take on all the values in the list `mylist`. Each item is then squared before it is added to the list that is being built. The result is a list of squares of the values in `mylist`. To write the `primes_upto` function we will use the `is_prime` function to filter the sequence of integers coming from the `range` function. In other words, for every integer from 2 up to but not including `n`, if the integer is prime, keep it in the list. ```python def primes_upto(n): """ Return a list of all prime numbers less than n using a list comprehension. """ result = [num for num in range(2,n) if is_prime(num)] return result ``` Note This workspace is provided for your convenience. You can use this activecode window to try out anything you like. **Check your understanding** What is printed by the following statements? ```python alist = [4,2,8,6,5] blist = [num*2 for num in alist if num%2==1] print(blist) ``` * [4,2,8,6,5] * Items from alist are doubled before being placed in blist. * [8,4,16,12,10] * Not all the items in alist are to be included in blist. Look at the if clause. * 10 * The result needs to be a list. * [10]. * Yes, 5 is the only odd number in alist. It is doubled before being placed in blist. ## 10.24. Nested Lists A nested list is a list that appears as an element in another list. In this list, the element with index 3 is a nested list. If we print(`nested[3]`), we get `[10, 20]`. To extract an element from the nested list, we can proceed in two steps. First, extract the nested list, then extract the item of interest. It is also possible to combine those steps using bracket operators that evaluate from left to right. ```python nested = ["hello", 2.0, 5, [10, 20]] innerlist = nested[3] print(innerlist) item = innerlist[1] print(item) print(nested[3][1]) ``` **Check your understanding** What is printed by the following statements? ```python alist = [ [4, [True, False], 6, 8], [888, 999] ] if alist[0][1][0]: print(alist[1][0]) else: print(alist[1][1]) ``` * 6 * 6 is in the wrong list. alist[1] refers to the second item in alist, namely [888,999]. * 8 * 8 is in the wrong list. alist[1] refers to the second item in alist, namely [888,999]. * 888 * Yes, alist[0][1][0] is True and alist[1] is the second list, the first item is 888. * 999 * alist[0][1][0] is True. Take another look at the if statement. ## 10.25. Strings and Lists Two of the most useful methods on strings involve lists of strings. The `split` method breaks a string into a list of words. By default, any number of whitespace characters is considered a word boundary. ```python song = "The rain in Spain..." wds = song.split() print(wds) ``` An optional argument called a **delimiter** can be used to specify which characters to use as word boundaries. The following example uses the string `ai` as the delimiter: ```python song = "The rain in Spain..." wds = song.split('ai') print(wds) ``` Notice that the delimiter doesn’t appear in the result. The inverse of the `split` method is `join`. You choose a desired **separator** string, (often called the *glue*) and join the list with the glue between each of the elements. ```python wds = ["red", "blue", "green"] glue = ';' s = glue.join(wds) print(s) print(wds) print("***".join(wds)) print("".join(wds)) ``` The list that you glue together (`wds` in this example) is not modified. Also, you can use empty glue or multi-character strings as glue. **Check your understanding** What is printed by the following statements? ```python myname = "Edgar Allan Poe" namelist = myname.split() init = "" for aname in namelist: init = init + aname[0] print(init) ``` * Poe * Three characters but not the right ones. namelist is the list of names. * EdgarAllanPoe * Too many characters in this case. There should be a single letter from each name. * EAP * Yes, split creates a list of the three names. The for loop iterates through the names and creates a string from the first characters. * William Shakespeare * That does not make any sense. ## 10.26. `list` Type Conversion Function Python has a built-in type conversion function called `list` that tries to turn whatever you give it into a list. For example, try the following: ```python xs = list("Crunchy Frog") print(xs) ``` The string “Crunchy Frog” is turned into a list by taking each character in the string and placing it in a list. In general, any sequence can be turned into a list using this function. The result will be a list containing the elements in the original sequence. It is not legal to use the `list` conversion function on any argument that is not a sequence. It is also important to point out that the `list` conversion function will place each element of the original sequence in the new list. When working with strings, this is very different than the result of the `split` method. Whereas `split` will break a string into a list of “words”, `list` will always break it into a list of characters. ## 10.27. Tuples and Mutability So far you have seen two types of sequential collections: strings, which are made up of characters; and lists, which are made up of elements of any type. One of the differences we noted is that the elements of a list can be modified, but the characters in a string cannot. In other words, strings are **immutable** and lists are **mutable**. A **tuple**, like a list, is a sequence of items of any type. Unlike lists, however, tuples are immutable. Syntactically, a tuple is a comma-separated sequence of values. Although it is not necessary, it is conventional to enclose tuples in parentheses: ```python julia = ("Julia", "Roberts", 1967, "Duplicity", 2009, "Actress", "Atlanta, Georgia") ``` Tuples are useful for representing what other languages often call *records* — some related information that belongs together, like your student record. There is no description of what each of these *fields* means, but we can guess. A tuple lets us “chunk” together related information and use it as a single thing. Tuples support the same sequence operations as strings and lists. For example, the index operator selects an element from a tuple. A tuple can be the sequence in a for-loop. As with strings, if we try to use item assignment to modify one of the elements of the tuple, we get an error. ```python julia[0] = 'X' TypeError: 'tuple' object does not support item assignment ``` Of course, even if we can’t modify the elements of a tuple, we can make a variable reference a new tuple holding different information. To construct the new tuple, it is convenient that we can slice parts of the old tuple and join up the bits to make the new tuple. So `julia` has a new recent film, and we might want to change her tuple. We can easily slice off the parts we want and concatenate them with the new tuple. ```python julia = ("Julia", "Roberts", 1967, "Duplicity", 2009, "Actress", "Atlanta, Georgia") print(julia[2]) print(julia[2:6]) print(len(julia)) for field in julia: print(field) julia = julia[:3] + ("Eat Pray Love", 2010) + julia[5:] print(julia) ``` To create a tuple with a single element (but you’re probably not likely to do that too often), we have to include the final comma, because without the final comma, Python treats the `(5)` below as an integer in parentheses: ```python tup = (5,) print(type(tup)) x = (5) print(type(x)) ``` ## 10.28. Tuple Assignment Python has a very powerful **tuple assignment** feature that allows a tuple of variables on the left of an assignment to be assigned values from a tuple on the right of the assignment. ```python (name, surname, birth_year, movie, movie_year, profession, birth_place) = julia ``` This does the equivalent of seven assignment statements, all on one easy line. One requirement is that the number of variables on the left must match the number of elements in the tuple. Once in a while, it is useful to swap the values of two variables. With conventional assignment statements, we have to use a temporary variable. For example, to swap `a` and `b`: ```python temp = a a = b b = temp ``` Tuple assignment solves this problem neatly: ```python (a, b) = (b, a) ``` The left side is a tuple of variables; the right side is a tuple of values. Each value is assigned to its respective variable. All the expressions on the right side are evaluated before any of the assignments. This feature makes tuple assignment quite versatile. Naturally, the number of variables on the left and the number of values on the right have to be the same. ```python >>> (a, b, c, d) = (1, 2, 3) ValueError: need more than 3 values to unpack ``` ## 10.29. Tuples as Return Values Functions can return tuples as return values. This is very useful — we often want to know some batsman’s highest and lowest score, or we want to find the mean and the standard deviation, or we want to know the year, the month, and the day, or if we’re doing some ecological modeling we may want to know the number of rabbits and the number of wolves on an island at a given time. In each case, a function (which can only return a single value), can create a single tuple holding multiple elements. For example, we could write a function that returns both the area and the circumference of a circle of radius r. ```python def circleInfo(r): """ Return (circumference, area) of a circle of radius r """ c = 2 * 3.14159 * r a = 3.14159 * r * r return (c, a) print(circleInfo(10)) ``` Note This workspace is provided for your convenience. You can use this activecode window to try out anything you like. ## 10.30. Glossary aliases : Multiple variables that contain references to the same object. clone : To create a new object that has the same value as an existing object. Copying a reference to an object creates an alias but doesn’t clone the object. delimiter : A character or string used to indicate where a string should be split. element : One of the values in a list (or other sequence). The bracket operator selects elements of a list. index : An integer variable or value that indicates an element of a list. list : A collection of objects, where each object is identified by an index. Like other types `str`, `int`, `float`, etc. there is also a `list` type-converter function that tries to turn its argument into a list. list traversal : The sequential accessing of each element in a list. modifier : A function which changes its arguments inside the function body. Only mutable types can be changed by modifiers. mutable data type : A data type in which the elements can be modified. All mutable types are compound types. Lists are mutable data types; strings are not. nested list : A list that is an element of another list. object : A thing to which a variable can refer. pattern : A sequence of statements, or a style of coding something that has general applicability in a number of different situations. Part of becoming a mature Computer Scientist is to learn and establish the patterns and algorithms that form your toolkit. Patterns often correspond to your “mental chunking”. pure function : A function which has no side effects. Pure functions only make changes to the calling program through their return values. sequence : Any of the data types that consist of an ordered collection of elements, with each element identified by an index. side effect : A change in the state of a program made by calling a function that is not a result of reading the return value from the function. Side effects can only be produced by modifiers. tuple : A sequential collection of items, similar to a list. Any python object can be an element of a tuple. However, unlike a list, tuples are immutable. ## 10.31. Exercises 1. Draw a reference diagram for `a` and `b` before and after the third line of the following python code is executed: ```python a = [1, 2, 3] b = a[:] b[0] = 5 ``` Your diagram should show two variables referring to two different lists. `a` refers to the original list with 1,2, and 3. `b` refers to a list with 5,2, and 3 since the zero-eth element was replaced with 5. [Show Comments](#disqus_thread) 2. Create a list called `myList` with the following six items: 76, 92.3, “hello”, True, 4, 76. Begin with the empty list shown below, and add 6 statements to add each item, one per item. The first three statements should use the append method to append the item to the list, and the last three statements should use concatenation. ```python myList = [] # Your code here ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(myList,[76, 92.3, 'hello', True, 4, 76],"myList should contain the specified items") self.assertIn(".append(", self.getEditorText(), 'append method must be used') myTests().main() ``` ```python myList = [] myList.append(76) myList.append(92.3) myList.append("hello") myList = myList + [True] myList = myList + [4] myList = myList + [76] print(myList) ``` 3. Starting with the list of the previous exercise, write Python statements to do the following: 1. Append “apple” and 76 to the list. 2. Insert the value “cat” at position 3. 3. Insert the value 99 at the start of the list. 4. Find the index of “hello”. 5. Count the number of 76s in the list. 6. Remove the first occurrence of 76 from the list. 7. Remove True from the list using `pop` and `index`. ```python myList = [76, 92.3, 'hello', True, 4, 76] # Your code here ``` ```python myList = [76, 92.3, 'hello', True, 4, 76] myList.append("apple") # a myList.append(76) # a myList.insert(3, "cat") # b myList.insert(0, 99) # c print(myList.index("hello")) # d print(myList.count(76)) # e myList.remove(76) # f myList.pop(myList.index(True)) # g print (myList) ``` [Show Comments](#disqus_thread) 4. Write a function called `average` that takes a list of numbers as a parameter and returns the average of the numbers. ```python def average(numlist): # Complete the function definition ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(int(average([1, 3, 5, 7])),4,"average([1, 3, 5, 7]) should be 4") self.assertEqual(int(average([9, 5, 4])),6,"average([9, 5, 4]) should be 6") myTests().main() ``` ```python def average(numlist): total = 0 for num in numlist: total = total + num return total / len(numlist) ``` 5. Write a Python function named `max` that takes a parameter containing a nonempty list of integers and returns the maximum value. (Note: there is a builtin function named `max` but pretend you cannot use it.) ```python def max(lst): # Complete the function ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(max([3, 31, 5, 7]),31,"max([3, 31, 5, 7]) should be 31") self.assertEqual(max([3, 13, 51, 7]),51,"max([3, 13, 51, 7]) should be 51") myTests().main() ``` ```python def max(lst): max = lst[0] for e in lst: if e > max: max = e return max ``` [Show Comments](#disqus_thread) 6. Write a function `sum_of_squares(xs)` that computes the sum of the squares of the numbers in the list `xs`. For example, `sum_of_squares([2, 3, 4])` should return 4+9+16 which is 29: ```python def sum_of_squares(xs): # your code here ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(sum_of_squares([2,3,4]),29,"Tested sum_of_squares on input [2,3,4]") self.assertEqual(sum_of_squares([0,1,-1]),2,"Tested sum_of_squares on input [0,1,-1]") self.assertEqual(sum_of_squares([5,12,14]),365,"Tested sum_of_squares on input [5,12,14]") myTests().main() ``` 7. Write a function to count how many odd numbers are in a list. ```python def countOdd(lst): # your code here ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(countOdd([1,3,5,7,9]),5,"Tested countOdd on input [1,3,5,7,9]") self.assertEqual(countOdd([1,2,3,4,5]),3,"Tested countOdd on input [-1,-2,-3,-4,-5]") self.assertEqual(countOdd([2,4,6,8,10]),0,"Tested countOdd on input [2,4,6,8,10]") self.assertEqual(countOdd([0,-1,12,-33]),2,"Tested countOdd on input [0,-1,12,-33]") myTests().main() ``` ```python import random def countOdd(lst): odd = 0 for e in lst: if e % 2 != 0: odd = odd + 1 return odd # make a random list to test the function lst = [] for i in range(100): lst.append(random.randint(0, 1000)) print(countOdd(lst)) ``` [Show Comments](#disqus_thread) 8. Sum up all the even numbers in a list. ```python def sumEven(lst): # your code here ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(sumEven([1,3,5,7,9]),0,"Tested sumEven on input [1,3,5,7,9]") self.assertEqual(sumEven([-1,-2,-3,-4,-5]),-6,"Tested sumEven on input [-1,-2,-3,-4,-5]") self.assertEqual(sumEven([2,4,6,7,9]),12,"Tested sumEven on input [2,4,6,7,9]") self.assertEqual(sumEven([0,1,12,33]),12,"Tested sumEven on input [0,1,12,33]") myTests().main() ``` 9. Sum up all the negative numbers in a list. ```python def sumNegatives(lst): # your code here ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(sumNegatives([-1,-2,-3,-4,-5]),-15,"Tested sumNegatives on input [-1,-2,-3,-4,-5]") self.assertEqual(sumNegatives([1,-3,5,-7,9]),-10,"Tested sumNegatives on input [1,-3,5,-7,9]") self.assertEqual(sumNegatives([-2,-4,6,-7,9]),-13,"Tested sumNegatives on input [-2,-4,6,-7,9]") self.assertEqual(sumNegatives([0,1,2,3,4]),0,"Tested sumNegatives on input [0,1,2,3,4]") myTests().main() ``` ```python import random def sumNegative(lst): sum = 0 for e in lst: if e < 0: sum = sum + e return sum lst = [] for i in range(100): lst.append(random.randrange(-1000, 1000)) print(sumNegative(lst)) ``` [Show Comments](#disqus_thread) 10. Count how many words in a list have length 5. ```python def countWords(lst): # your code here ``` 11. Sum all the elements in a list up to but not including the first even number. ```python def sumUntilEven(lst): # your code here ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(sumUntilEven([1,2,3,4,5]),1,"Tested sumUntilEven on input [1,2,3,4.5]") self.assertEqual(sumUntilEven([1,3,5,7,9]),25,"Tested sumUntilEven on input [1,3,5,7,9]") self.assertEqual(sumUntilEven([2,4,6,7,9]),0,"Tested sumUntilEven on input [2,4,6,7,9]") myTests().main() ``` ```python import random def sum(lst): sum = 0 index = 0 while index < len(lst) and lst[index] % 2 != 0: sum = sum + lst[index] index = index + 1 return sum lst = [] for i in range(100): lst.append(random.randint(0,1000)) print(sum(lst)) ``` [Show Comments](#disqus_thread) 12. Count how many words occur in a list up to and including the first occurrence of the word “sam”. ```python def count(lst): # your code here ``` 13. Although Python provides us with many list methods, it is good practice and very instructive to think about how they are implemented. Implement a Python function that works like the following: 1. count 2. in 3. reverse 4. index 5. insert ```python def count(obj, lst): count = 0 for e in lst: if e == obj: count = count + 1 return count def is_in(obj, lst): # cannot be called in() because in is a reserved keyword for e in lst: if e == obj: return True return False def reverse(lst): reversed = [] for i in range(len(lst)-1, -1, -1): # step through the original list backwards reversed.append(lst[i]) return reversed def index(obj, lst): for i in range(len(lst)): if lst[i] == obj: return i return -1 def insert(obj, index, lst): newlst = [] for i in range(len(lst)): if i == index: newlst.append(obj) newlst.append(lst[i]) return newlst lst = [0, 1, 1, 2, 2, 3, 4, 5, 6, 7, 8, 9] print(count(1, lst)) print(is_in(4, lst)) print(reverse(lst)) print(index(2, lst)) print(insert('cat', 4, lst)) ``` [Show Comments](#disqus_thread) 14. Write a function `replace(s, old, new)` that replaces all occurences of `old` with `new` in a string `s`: ```python test(replace('Mississippi', 'i', 'I'), 'MIssIssIppI') s = 'I love spom! Spom is my favorite food. Spom, spom, spom, yum!' test(replace(s, 'om', 'am'), 'I love spam! Spam is my favorite food. Spam, spam, spam, yum!') test(replace(s, 'o', 'a'), 'I lave spam! Spam is my favarite faad. Spam, spam, spam, yum!') ``` *Hint*: use the `split` and `join` methods. ```python def replace(s, old, new): # your code here ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(replace('Mississippi','i','I'),'MIssIssIppI',"Tested replace on input 'Mississippi','i','I'") self.assertEqual(replace('Bookkeeper','e','A'),'BookkAApAr',"Tested failed on input 'Bookkeeper','e','A'") self.assertEqual(replace('Deeded','e','q'),'Dqqdqd',"Tested failed on input 'Deeded','e','q'") myTests().main() ``` 15. Here are the rules for an L-system that creates something that resembles a common garden herb. Implement the following rules and try it. Use an angle of 25.7 degrees. ```python H H --> HFX[+H][-H] X --> X[-FFF][+FFF]FX ``` ```python import turtle def createLSystem(numIters, axiom): startString = axiom endString = "" for i in range(numIters): endString = processString(startString) startString = endString return endString def processString(oldStr): newstr = "" for ch in oldStr: newstr = newstr + applyRules(ch) return newstr def applyRules(ch): newstr = "" if ch == 'H': newstr = 'HFX[+H][-H]' # Rule 1 elif ch == 'X': newstr = 'X[-FFF][+FFF]FX' else: newstr = ch # no rules apply so keep the character return newstr def drawLsystem(aTurtle, instructions, angle, distance): savedInfoList = [] for cmd in instructions: if cmd == 'F': aTurtle.forward(distance) elif cmd == 'B': aTurtle.backward(distance) elif cmd == '+': aTurtle.right(angle) elif cmd == '-': aTurtle.left(angle) elif cmd == '[': savedInfoList.append([aTurtle.heading(), aTurtle.xcor(), aTurtle.ycor()]) #print(savedInfoList) elif cmd == ']': newInfo = savedInfoList.pop() aTurtle.setheading(newInfo[0]) aTurtle.setposition(newInfo[1], newInfo[2]) def main(): inst = createLSystem(4, "H") # create the string print(inst) t = turtle.Turtle() # create the turtle wn = turtle.Screen() t.up() t.back(200) t.down() t.speed(9) drawLsystem(t, inst, 27.5, 5) # draw the picture wn.exitonclick() main() ``` [Show Comments](#disqus_thread) 16. Here is another L-System. Use an Angle of 25. ```python F F --> F[-F]F[+F]F ``` 17. Create a list named `randlist` containing 100 random integers between 0 and 1000 (use iteration, append, and the random module). ```python ===== from unittest.gui import TestCaseGui class myTests(TestCaseGui): def testOne(self): self.assertEqual(len(randlist),100,"randlist should contain 100 numbers") myTests().main() ``` ```python import random randlist = [] for i in range(100): randlist.append(random.randint(0, 1000)) ```