# Tuples and lists

Often, we run into situations where we have a collection of values rather than just one value. For instance, if we were trying to plot data (as we will do later on) on the Cartesian plane, we might have two lists of values: the list corresponding to the horizontal axis and the list corresponding to the vertical axis. Notice, also, that the order in the \(x\) and \(y\) components are listed in is important.

Two ways that Python implements an ordered collection are tuples and lists. Letâ€™s start with tuples first.

## Tuples

Suppose we want to plot some data. The \(x\) coordinates are `0`

, `2`

, `5`

, and `6`

. If we use tuples, we might code this information as

```
x = (0, 2, 5, 6)
```

This defines a **tuple** `x`

. Similarly, if the \(y\) coordinates are `1`

, `2`

, `3`

, and `4`

, then we might code that information as

```
y = (1, 2, 3, 4)
```

Suppose that we want to access a particular data point. Well, then, we need the correct \(x\) coordinate and \(y\) coordinate. For that, we index the `x`

and `y`

variables at, say, the third element:

```
print('x[2] = ' + str(x[2]) + '; y[2] = ' + str(y[2]))
```

Python is 0-indexed, so 0 corresponds to the first item, 1, corresponds to the second item, and so on.

## Immutability of tuples

Tuples are similar to strings in that they are immutable. For instance, the following code will occur in a runtime error:

```
x = (0, 2, 5, 6)
x[0] = 1 # error!
print(x)
```

## Tuple assignment and tuple return values

In Python, there is a very powerful feature that allows one to assign multiple variables at once via tuples. As an example:

```
(lastName, firstName, occupation) = ('White', 'Walter', 'Chemist')
print(firstName + ' ' + lastName + ' the ' + occupation)
```

This outputs

```
Walter White the Chemist
```

The power of tuple assignment is particular prominent when functions *return* tuples. For instance, suppose we roll a fair six-sided dice 10 times (for the sake of a small example) and want to know the sample mean and standard deviation. We can implement this in code via the following:

```
def stats_summary(data):
mean = sum(data) / len(data)
stdev = 0
for observation in data:
stdev += (observation - mean)**2
stdev /= len(data) - 1
stdev **= 1/2
return mean, stdev # Notice that we can drop the parentheses for tuples here
observations = [4, 3, 5, 4, 1, 4, 6, 2, 4, 5]
mean, stdev = stats_summary(observations) # Notice that we can also drop the parentheses for tuples here
print('mean = ' + str(mean) + '; stdev = ' + str(stdev))
```

This outputs

```
mean = 3.8; stdev = 1.4757295747452437
```

Notice that tuple assignment here allows us to collect multiple values corresponding to information about a particular input.

## Lists

Another way we can represent an ordered collection of values is as a **list**. There are many ways to create a list with simplest being to enclose the elements in square brackets:

```
myList = ['chris', 3324]
```

As you can see, we can be quite liberal with the values allowed to be in a list. It is also possible to start with an empty list:

```
myList = []
```

It is also perfectly legal to put lists inside of lists. Such lists are called **nested lists**:

```
myList = [2.4, ['chris', 'bob']]
```

Lists, tuples, and strings share many properties of accessing their elements. Everything we know from indexing strings and tuples also applies to lists. Slices, the `len`

function, and the `in`

operator are the same. We can also concatenate two lists together in the same way we would strings:

```
a = [1, 2, 3]
b = [4, 5, 6]
print(a + b)
```

This outputs

```
[1, 2, 3, 4, 5, 6]
```

Similarly, we can repeat a list using the `*`

operator:

```
print([1, 2, 3] * 3)
```

This outputs

```
[1, 2, 3, 1, 2, 3, 1, 2, 3]
```

## Mutability of lists

Lists, unlike strings, are **mutable** and thus can have its elements modified. For instance:

```
a = [1, 2, 3]
a[0] = 9
print(a)
```

This will run perfectly fine and outputs

```
[9, 2, 3]
```

Using slice notation, it is also possible to update an entire sublist at once:

```
a = [1, 2, 3, 4, 5]
a[1:3] = [10, 11]
print(a)
```

This will output

```
[1, 10, 11, 4, 5]
```

## Appending elements to lists

To add an element to a list, we use the `.append()`

method. For instance:

```
myList = []
myList.append('a')
myList.append(5)
print(myList)
```

This will output

```
['a', 5]
```

## Objects and references

Consider the following code:

```
a = 'chris'
b = 'chris'
```

Since strings are immutable, it does not make sense to think of `a`

and `b`

as different string objects. Accordingly, Python will internally optimize so that `a`

and `b`

reference the same object. We can observe this by using the `is`

operator:

```
a = 'chris'
b = 'chris'
print(a is b)
```

This code prints `True`

.

Lists, on the other hand, are mutable. Because of this difference, if we have the following code:

```
a = [1, 2, 3]
b = [1, 2, 3]
print(a is b)
```

then `a`

and `b`

should not be considered the same object because we might want to modify `a`

but not `b`

or vice versa. Or maybe we might modify both later so that they become different. Accordingly, the code above prints `False`

. Despite this difference, however, we might still want to know if `a`

and `b`

have the same elements. To check this, we just run the following:

```
a = [1, 2, 3]
b = [1, 2, 3]
print(a == b)
```

This will output `True`

.

## List comprehension

Sometimes when we want a list of items, that list is easily obtained by a for loop. For instance, if we want a list that contains \[1^2, 2^2, \ldots, n^2\], we could make that list with a `for`

loop:

```
n = 10
myList = []
for k in range(1, n + 1):
myList.append(k**2)
print(myList)
```

We do get the desired output of `[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]`

, but it is possible to make the above much more concise while keeping the same amount of clarity via **list comprehension**:

```
n = 10
myList = [k**2 for k in range(1, n + 1)]
print(myList)
```

# Exercises

Given variables

`a`

and`b`

, use tuple assignment to swap the values of`a`

and`b`

in one line of code.Write a function that computes the

`mode`

of a sample (i.e. list of observed data).Use list comprehension to generate a list consisting of \[1^3, 2^3, \ldots, n^3.\]

Read the documentation for lists. In particular, check this page and look over the methods for lists. Experiment with some of them.

- What is the output of the following code?
`print(list(range(10)))`

Read the documentation for

`range`

and experiment and experiment with different arguments for the`range`

function in the code above. - Complete the following code below by implementing the
`cross_product`

function:

```
def cross_product(u, v):
pass # return the cross product of u and v
u = (1, 2, 3)
v = (-1, 2, 3)
print(cross_product(u, v))
```