CS 2120: Class #8 ================= Lists ^^^^^^ * Probably the most important things in python. * If you're going to pay attention only once this term... now's the time. * Our values so far have been pretty simple. * One thing at a time. One ``int``, one ``float``, one ``string``... * ... except, wait, strings were a bit different, weren't they? * How? * Can we *generalize* this idea of a container that stores multiple values? * Yes!:: >>> a=[5,7,9,10] >>> print a [5, 7, 9, 10] * This is called a *list*. * I can grab individual *elements* of the list using *indices*, exactly like we did with strings:: >>> print a [5, 7, 9, 10] >>> print a[0] 5 >>> print a[1] 7 >>> print a[1:3] [7, 9] * Turns out: strings are really like lists in which the elements happen to be characters. .. admonition:: Activity Hack around with the Python interpreter to find answers to these questions: 1. What types (``int``, ``float``, etc.) are we allowed to put in lists? 2. Can we put *different* types together in the *same* list? 3. What ``type`` does a list have? 4. How do you select the last 3 items in a list? 5. Can you have a list with nothing in it? An *empty* list? Data Structures ^^^^^^^^^^^^^^^^ * The ``list`` is your first real **data structure**. * The name "data *structure*" pretty much tells you everything you need to know. * A data structure is a formal way to *structure* data. * Hurray, we're done the course! * Not really. There is a whole zoo of data structures, each with it's own strenghts and weaknesses. * Lists, although simple, are one of the most useful and powerful of all data structures. * Sometimes they are a bit slower than more specialized alternatives. * This isn't a big deal for us though. .. raw:: html .. admonition:: Activity Let's apply what we've learned about loops to our newfound ``list`` data structure. Combining algorithms and data structures is what programming is all about! 1. Figure out how to find the number of elements in a list. 2. Write a function ``even_print(l)`` that takes a list ``l`` as its argument and prints out only the even elements of the list. 3. Write a *single line* of Python code to test if a particular value appears in a list (e.g. test if ``5`` appears *in* ``[1,7,5,3]``.) List operations ^^^^^^^^^^^^^^^^ * We can contenate lists with the ``+`` operator: >>> a=[5,7,9,10] >>> b=['also','a','list'] >>> a+b [5, 7, 9, 10, 'also', 'a', 'list'] * We can concatenate a list with itself, multiple times, using the ``*`` operator: >>> a*3 [5, 7, 9, 10, 5, 7, 9, 10, 5, 7, 9, 10] * Can we do this with Strings? * As you've discovered for yourself, we can also *slice* lists (just like we did strings), find their size and check for membership. Home on the ^^^^^^^^^^^^^^^^^^^^^^ * In real world programming applications, we very frequently need a list of integers. * For example: ``[1,2,3,4,5,...]`` so that we can count things. * Python has a built in function ``range()`` that will generate lists of integers for us: >>> range(1,5) [1, 2, 3, 4] >>> range(5,10) [5, 6, 7, 8, 9] .. admonition:: Activity Generate the following lists, using ``range``: 1. All integers from 0 to 17 2. All integers from -10 to 0 3. All integers from 10 to 0 (that is: counting *down* instead of up) 4. All even integers from 0 to 20 If you're having trouble with the last two, look up the `docs for range `_ . .. raw:: html Mutability ^^^^^^^^^^^ * Strings do kinda look like "list of characters" and, in many ways, they are. * *But not exactly*. * Strings, remember, are *immutable*. What about lists? Let's try: >>> a=[5,7,9,10] >>> print a [5, 7, 9, 10] >>> a[2]='I changed!' >>> print a [5, 7, 'I changed!', 10] * Unlike strings, lists are *mutable*. .. admonition:: Activity Consider the list ``l=range(0,10)``. Find single-line commands to do the following: 1. Change the 5th element of the list to ``'X'``. 2. Replace the first two elements of the list with ``10`` and ``11``, respectively. Remember, single line only! (Hint: slicing) 3. Delete the two elements you just changed. (Hint: what does assigning the empty list to a slice do?) * A 'cleaner' way to delete an element from a list is with the ``del`` statement:: >>> a=[5,7,9,10] >>> a [5, 7, 9, 10] >>> del a[2] >>> a [5, 7, 10] Aliasing ^^^^^^^^^ * Pay attention here, because this is a *major* source of confusion for new programmers. * Suppose you have a list, ``biglist`` with 500 billion entries in it. * That's a big list. Probably uses a lot of RAM. * A lot of space inside the computer. * Now you type: >>> newlist = biglist * What seems like a better idea: * Copy all 500 billion entries into ``newlist``, using twice as much RAM to store the same data. * Memorize the fact that ``newlist`` is just another name for ``biglist``. Copy nothing. * Pretty obvious when you think about it that way, but less obvious when your lists only have 5 items in them. * like this: >>> a=[1,2,3,4] >>> print a [1, 2, 3, 4] >>> b=a >>> b[2]='Z' >>> print a [1, 2, 'Z', 4] * You should probably pay attention to this * Probably one of the more annoying things new computer scientists have to deal with * If you expect ``b`` to be a *full copy* of ``a``, what just happened makes no sense. * If you expect ``b`` just to be another name for ``a``, it makes perfect sense. .. warning:: In Python, when you "assign" a list, you **are not copying the list**. You are saying 'this is another name for the exact same list'. * The reason this is so upsetting is that this behaviour is *different* from what happens with simple values like ``int``, ``float``, etc. You have to make an effort to remember that "=" means something different for lists than it does for other types. C'est la vie. * Suppose you *really want* to **copy** your list instead of just giving it another name. You can do that easily enough using slicing: ``newlist = biglist[:]``. Slicing always creates a *new* list. >>> a=[1,2,3,4] >>> print a [1, 2, 3, 4] >>> b=a[:] >>> b[2]='Z' >>> print a [1, 2, 3, 4] .. raw:: html * Spend some time getting used to this concept. I promise you, 100%, it will cause bugs in your code. * Happens to me all the time :( .. image:: ../img/sad.jpeg .. admonition:: Activity Create a list named ``l``. Make an *alias* of the list named ``lalias``. Make a *copy* of the list named ``lcopy``. Prove to yourself that one is an alias and one is a copy. Lists and loops ^^^^^^^^^^^^^^^^ * ``for`` loops can be used to execute a block of code for every element in a list:: for element in list: do_something(element) * Just like the loop we did with Strings last class! * This is incredibly useful. In fact, you've already seen it in assignment 1. Let's try it:: def like_food(food_list): for food in food_list: if food not in ['McDonalds','Burger King']: print 'I like ' + food else: print 'I dont like ' + food + ' so much.' * And now we'll run our function: >>> like_food(['curry','sushi','McDonalds','bison']) I like curry I like sushi I dont like McDonalds so much. I like bison .. admonition:: Activity Write a function ``beer_on_wall`` that will print out "n bottles of beer on the wall" for all n from 99 down to 1. Remember: ``range`` returns a list... and a ``for`` loop can *iterate* over every element of a list. .. raw:: html * Suppose I want to print out a list of strings, in order, with each element preceded by number indicating it's position in the list:: >>> list=['a','b','c','d'] >>> for index in range(len(list)): print index, list[index] 0 a 1 b 2 c 3 d * What is going on in ``range(len(list))``? Break it down one step at a time. * This pattern is so common that Python has given us a built in function ``enumerate`` to enumerate lists in a loop:: for index, item in enumerate(list): print index, item * Most of our ``for`` loops have only a single *loop variable*... * ... but.. notice how instead of a single loop variable, we now have *two* (``index`` *and* ``item``). They iterate together in lockstep. * ``index`` gets the index of the item in the list * ``item`` gets the actual item itself * This is a special feature of the ``enumerate`` function. Mind the rotating knives ^^^^^^^^^^^^^^^^^^^^^^^^^ .. image:: ../img/spinning.gif * Remember how assigning lists wasn't really *copying* them, but just creating a new name? * I wonder what happens when you pass a list to a function as an argument? * Does the function get it's own copy? * ... or does the function just get an alias to the same list? .. admonition:: Activity Figure out the answer to this question emperically. Write a function that will prove to you which of the two options above is correct. Side effects ^^^^^^^^^^^^ * Consider the code:: def add_to_list(mylist): mylist.append('appended') * Now consider the code:: def add_to_list_2(mylist): return mylist + ['appended'] .. admonition:: Activity What happens when you do this? >>> a = [1,2,3,4] >>> add_to_list(a) >>> print a How about this: >>> a = [1,2,3,4] >>> add_to_list_2(a) >>> print a Finally, how about this: >>> a = [1,2,3,4] >>> b = add_to_list_2(a) >>> print a >>> print b * The function ``add_to_list`` *modified* the parmaeter you passed in. * The function ``add_to_list_2`` kept a respectful distance from your parameter and, instead, created a *new* list and *returned* that as the answer. * If a function modifies a parameter it is said to have *side effects*. * The term "side effect" comes from our mathematical expectation of a "function". A function maps some parameters on to a value. If I give you the function `f(x,y,z)=x+y-z` and ask you to evaluate `f(1,2,3)`, you don't expect the values of `x`, `y` and `z` to change! Pure functions ^^^^^^^^^^^^^^^ * If a function has no side effects, we call it a *pure function*. * Some programming languages allow *only* pure functions (e.g., `Haskell `_). * There are some nice theoretical, and practical benefits to this. * As you might guess from the ameliorative term "pure"... functions with side effects are considered... "not pure"... even downright dirty, by some folks. .. admonition:: Activity Think of three potential advantages to pure functions over functions with side effects. Who wants to be pure? ^^^^^^^^^^^^^^^^^^^^^^ * Anything you can possibly do with a computer *can* be done with pure functions... * ... but... some stuff is just plain easier to do with side effects. * This is a course for working scientists, so let's be pragmatic: * Write pure functions when practical to do so. The advantages make it worthwhile. * If it really is a lot easier to do the job with side effects... just do it and don't lose sleep over it. Nested lists ^^^^^^^^^^^^^ * If you can nest loops... can you nest lists? .. admonition:: Activity Figure out if Python supports nested lists. If it does: build a few. If it doesn't: how might you implement them yourself? .. admonition:: Activity Hack around with the Python interpreter to find answers to these questions: 1. Can you have a list in a list? 2. What about a list in a list in a list? 3. How about a list in a list in a list in a list in a list in a list? .. .. image:: ../img/idk.jpg .. I worried about offending people with this image For next class ^^^^^^^^^^^^^^^ * Skim the `NumPy Tutorial `_. * Just Skim. Reading it in detail at this point might give you a headache.