# 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.

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.

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]
```

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 .

## 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.

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]
```
• 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 :(

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
```

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.

• 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¶

• 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?

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']
```

Activity

What happens when you do this?

```>>> a = [1,2,3,4]
>>> print a
```

```>>> a = [1,2,3,4]
>>> print a
```

```>>> a = [1,2,3,4]
>>> 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.

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?

Activity

Figure out if Python supports nested lists. If it does: build a few. If it doesn’t: how might you implement them yourself?

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?

## For next class¶

• Skim the NumPy Tutorial.
• Just Skim. Reading it in detail at this point might give you a headache.