# Lists in Python

## Sequences #

Sequences is a generic term used to refer to data types which can hold multiple items of data. In Python, we have several types of sequences, the following three are most important:

1. list
2. tuples
3. strings

In this chapter we will discuss list type.

## What is a list ? #

Suppose we want to calculate average marks of 100 students in a class. To accomplish this task, our first step would be to store marks of 100 students by creating 100 variables. So far so good. What if we want to calculate average marks of 1000 students or more ? Should we create 1000 variables ? No ! Off course not. This way of solving problem would be very impractical. What we need is a list.

A list is sequences of multiple items separated by comma, and enclosed inside square brackets i.e `[]`. The syntax of creating list is as follows:

``variable = [item1, item2, item3, ..., itemN]``

Each item that is stored in the list is called an element of list or simply an element. For example:

``````>>>
>>> numbers = [11, 99, 66, 22]
>>>``````

This statement creates a list of 4 elements and assign it to the variable `numbers`. Note that just like everything else, a list is an object too, of type `list`. The `numbers` variable do not store the contents of the list, it only stores a reference to the address where list object is actually stored in the memory.

``````>>>
>>> type(numbers)
<class 'list'>
>>>``````

To print the contents of a list in the Python shell just type the list name.

``````>>>
>>> numbers
[11, 99, 66, 22]
>>>``````

We can also use `print()` function to print the list.

``````>>>
>>> print(numbers)
[11, 99, 66, 22]
>>>``````

A list can contain elements of same or different types.

``````>>>
>>> mixed = ["a string", 3.14, 199]  # list where elements are of different types
>>>
>>> mixed
['a string', 3.14, 199]
>>>``````

To create an empty list simply type square brackets without any elements inside it.

``````>>>
>>> empty_list = []  # an empty list
>>>``````

We can also create list using the `list()` constructor function.

``````>>>
>>> list1 = list()  # an empty list
>>> list2 = list([3.2, 4, 0.12])  # again elements in a list can be of different types
>>> list3 = list(["@@", "###", ">>>"])   # you can use symbols too
>>> list4 = list("1234")  # creating list from string
>>>
>>>
>>> list1
[]
>>>
>>> list2
[3.2, 4, 0.12]
>>>
>>> list3
['@@', '###', '>>>']
>>>
>>> list4
['1', '2', '3', '4']
>>>
>>>``````

The elements of a list can be a list too.

``````>>>
>>> list5 = [
...    [33, 55, 77],  # first element
...    [99, 31, 64]   # second element
... ]
>>>
>>>
>>> list5
[[33, 55, 77], [99, 31, 64]]
>>>
>>>``````

`list5` contains two elements of type `list`. This type of lists is know as list of list or nested list or multi-dimensional list.

## Creating lists using range() function #

The `range()` function can also be used to create long lists. Recall that the `range()` function returns an object of type iterable (to learn more click here), all we need to create a list is to pass this iterable object to the `list()` constructor function as follows:

``````>>>
>>> list1 = list(range(5))
>>> list1
[0, 1, 2, 3, 4]
>>>
>>>``````

`range(5)` generates the following sequence:

``0, 1, 2, 3, 4``

The `list()` function then uses numbers from this sequence to create a list.

Here are some more examples:

Example 1:

``````>>>
>>> list2 = list(range(1, 101))  ## create a list of numbers from 1 to 100
>>> list2
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,
23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42,
43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62,
63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82,
83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100]
>>>
>>>``````

Example 2:

``````>>>
>>> list3 = list(range(0, 100, 10))  ## create a list of numbers from 0 to 100 with step value of 10
>>> list3
[0, 10, 20, 30, 40, 50, 60, 70, 80, 90]
>>>
>>>``````

## List functions #

The following table lists some functions, we commonly use while working with lists.

Function Description
`len()` returns the number of elements in the sequence.
`sum()` returns the sum of elements in the sequence.
`max()` returns the element with greatest value in the sequence.
`min()` returns the element with smallest value in the sequence.
``````>>>
>>>
>>> list1 = [1, 9, 4, 12, 82]
>>>
>>> len(list1)  # find the length of the list
5
>>>
>>> sum(list1)  # find the sum of the list
108
>>>
>>> max(list1)  # find the greatest element in the list
82
>>>
>>> min(list1)  # find the smallest element in the list
1
>>>
>>>``````

## Index Operator #

Just like strings, the elements in a list are `0` indexed, meaning that the first element is at index `0`, second is at `1`, third is at `2` and so on. The last valid index will be one less than the length of the list. We use the following syntax to access an element from the list.

``a_list[index]``

where `index` must be an integer.

For example, the following statement creates a list of 6 elements.

``list1 = [88, 99, 4.12, 199, 993, 9999]``

Here `list1[0]` refers to the element `88`, `list1[1]` refers to `99` and `list[5]` refers to `9999`.

``````>>>
>>> list1 = [88, 99, 4.12, 199, 993, 9999]
>>>
>>> list1[0]  # get the first element
88
>>> list1[1]  # get the second element
99
>>> list1[5]  # get the sixth element
9999
>>>``````

We can also calculate the last valid index of a list using the `len()` function, as follows:

``````>>>
>>> len(list1) - 1
5
>>>
>>> list1[len(list1) - 1]  # get the last element
9999
>>>``````

Trying to access a element beyond the last last valid index will result in `IndexError`.

``````>>>
>>> list1[100]   # get the element at index 100
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: list index out of range
>>>
>>>``````

Just like strings, negative indexes are still valid here. In fact we can use can negative index on almost all types of sequences in Python.

As you can see from the image the last element is at index `-1`, the second last element is at `-2` and so on. The first element is at index `-6`.

``````>>>
>>> list1
[88, 99, 4.12, 199, 993, 9999]
>>>
>>> list1[-1]   # get the last element
9999
>>> list1[-2]   # get the second last element
993
>>>``````

To calculate the index of the first element use `len()` function as follows:

``````>>>
>>> list1[-len(list1)]  # get the first element
88
>>>``````

## Lists are Mutable #

List are mutable, what this means is that we can modify a list without creating a new list in the process. Consider the following example:

``````>>>
>>> list1 = ["str", "list", "int", "float"]
>>>
>>> id(list1)   # address where list1 is stored
43223176
>>>
>>> list1[0] = "string"  # Update element at index 0
>>>
>>> list1   # list1 is changed now
['string', 'list', 'int', 'float']
>>>
>>> id(list1)  # notice that the id is still same
43223176
>>>
>>>``````

Notice that even after modifying the list the id of the variable `list1` remains the same. This indicates that no new list object is created when we assign new element at index `0`.

## Iterating through elements in a List #

To iterate though a list we can use for loop as follows:

``````>>>
>>> marks = [122, 45, 23, 78, 65, 12]
>>> for m in marks:
...   print(m)
...
122
45
23
78
65
12
>>>``````

In each iteration the variable `m` is assigned a value from the list. Changing the value of variable `m` in the loop body doesn't update the elements in the list. So, this method is commonly used to iterate over list when we don't need to modify the elements in a list.

To modify elements, we can use a for loop in conjunction with the `range()` function, as follows:

``````>>>
>>> marks = [122, 45, 23, 78, 65, 12]
>>>
>>> import random
>>>
>>> for i in range(len(marks)):
...   marks[i] = random.randint(1, 100)  # assign some random value between 1 to 100 to all elements
...
>>>
>>> marks
[59, 9, 59, 21, 75, 61]
>>>
>>>``````

Although, the for loop is a preferred way to iterate over list, if we want, we can use while loop too. For example:

``````>>>
>>> i = 0
>>>
>>> while i < len(marks):
...   print(marks[i])
...   i += 1
...
59
9
59
21
75
61
>>>
>>>``````

## List Slicing #

Slicing operator we discussed in lesson Strings in Python is also available in list. The only difference is that instead of returning a slice of string, here it returns a slice of list. It's is syntax is:

``list[start:end]``

This returns a slice of list starting from index `start` to `end - 1`. Here are some examples:

``````>>>
>>> list1 = [11, 33, 55, 22, 44, 89]
>>>
>>> list1[0:4]
[11, 33, 55, 22]
>>>
>>>
>>> list1[1:5]
[33, 55, 22, 44]
>>>
>>>
>>> list1[4:5]
[44]
>>>``````

The `start` and `end` indexes are optional, if not specified then start index is `0` and end index is the `length` of the list. For example:

``````>>>
>>> list1
[11, 33, 55, 22, 44, 89]
>>>
>>> list1[:2]       # same as list1[0:2]
[11, 33]
>>>
>>> list1[2:]       # same as list1[2:len(list1)]
[55, 22, 44, 89]
>>>
>>> list1[:]        # same as list1[0:len(list1)]
[11, 33, 55, 22, 44, 89]
>>>
>>>``````

# Membership operator in and not in #

Just like strings, we can check whether an element exists in the list or not using `in` and `not in` operator. Here are some examples:

``````>>>
>>> cards  = ["club", "diamond", "heart", "spades"]
>>>
>>> "club" in cards
True
>>>
>>> "joker" in cards
False
>>>
>>> "pikes" not in cards
True
>>>
>>> "heart" not in cards
False
>>>
>>>``````

## List Concatenation #

List can be joined too using `+` operator. When operands on both side are lists `+` operator creates a new list by combing elements from both the lists. For example:

``````>>>
>>> list1 = [1,2,3]  # create list1
>>> list2 = [11,22,33]  # create list2
>>>
>>> id(list1)   # address of list1
43223112
>>> id(list2)   # address of list2
43223048
>>>
>>>``````
``````>>>
>>> list3 = list1 + list2   # concatenate list1 and list2 and create list3
>>> list3
[1, 2, 3, 11, 22, 33]
>>>
>>>``````
``````>>>
>>> id(list3)  # address of the new list list3
43222920
>>>
>>>
>>> id(list1)   # address of list1 is still same
43223112
>>> id(list2)   # address of list2 is still same
43223048
>>>
>>>``````

Notice that concatenation doesn't affect the `list1` and `list2`, their addresses remains the same before and after the concatenation.

Another way to concatenate list is use `+=` operator. The `+=` operator modifies the list instead of creating a new list. Here is an example:

``````>>>
>>> list1
[1, 2, 3]
>>>
>>> id(list1)
43223112
>>>
>>> list1 += list2   # append list2 to list1
>>>
>>> list1
[1, 2, 3, 11, 22, 33]
>>>
>>>
>>> id(list1)  # address is still same
43223112
>>>``````

The statement `list1 += list2` appends `list2` to the end of `list1`. Notice that the address of `list1` is still not changed. Unlike languages like C, C++ Java, where arrays are of fixed size. In Python, lists are dynamically sized. It means that the size of list grows automatically as needed.

## Repetition Operator #

We can use `*` operator with lists too. It's syntax is:

``sequence * n``

The `*` operator replicates the list and then joins them. Here are some examples:

``````>>>
>>> list1 = [1, 5]
>>>
>>>
>>> list2 = list1 * 4  # replicate list1 4 times and assign the result to list2
>>>
>>> list2
[1, 5, 1, 5, 1, 5, 1, 5]
>>>
>>>``````

The `*` operator can also be used as a compound assignment operator `*=` . The only difference is that instead of creating a new list object, it would update the existing list object.

``````>>>
>>> action = ["eat", "sleep", "repeat"]
>>>
>>> id(action)  # address of action list
32182472
>>>
>>> action *= 5
>>>
>>> action
['eat', 'sleep', 'repeat', 'eat', 'sleep', 'repeat', 'eat', 'sleep', 'repeat', '
eat', 'sleep', 'repeat', 'eat', 'sleep', 'repeat']
>>>
>>> id(action)   # address is still the same
32182472
>>>``````

## Comparing Lists #

Just like strings, we can compare lists using relational operators (`>`, `>=`, `<`, `<=`, `!=`, `==`). List comparison only works when operands involved contains the same type of elements. The process starts off by comparing the elements at index `0` from both list. The comparison stops only when either the end of the list is reached or corresponding characters in the list are not same.

Consider the following example:

``````>>>
>>> n1 = [1,2,3]
>>> n2 = [1,2,10]
>>>
>>>
>>> n1 > n2
False
>>>``````

Here are the steps involved in the comparison of list `n1` and `n2`.

Step 1: `1` from `n1` is compared with `1` from `n2`. As they are same, the next two characters are compared.

Step 2: `2` from `n2` is compared with `2` from `n2`. Again they are same, the next two characters are compared.

Step 3: `3` from `n1` is compared with `10` from `10`. Clearly `3` is smaller than `10`. So the comparison `n1 > n2` returns `False`.

Here is another example where elements are strings.

``````>>>
>>> word_list1 = ["pow", "exp"]
>>> word_list2 = ["power", "exponent"]
>>>
>>> word_list1 < word_list2
True
>>>``````

Step 1: `"pow"` from `word_list1` is compared with `"power"` from `word_list2`. Clearly `"pow"` is smaller than `"power"`. At this point, comparison stops because we have found the elements in list which are not same , so comparison `word_list1 < word_list2` is true.

## List Comprehension #

Often times you will need to create lists where each element in the sequence is a result of some operations or each element in the list satisfies some condition. For example, create a sequence of cubes of numbers from `50` to `100`. We use list comprehension in situations like these. The syntax of list comprehension is:

``[ expression for item in iterable ]``

here is how it works:

In each iteration `item` is assigned a value from iterable object, then the `expression` before the `for` keyword is evaluated; the result of the `expression` is then used to produce values for the list. The process repeats until there are no more elements left to iterate.

Here is an example:

``````>>>
>>> cube_list = [ i**3 for i in range(50, 101) ]
>>>
>>> cube_list
[125000, 132651, 140608, 148877, 157464, 166375, 175616, 185193, 195112, 205379,
216000, 226981, 238328, 250047, 262144, 274625, 287496, 300763, 314432, 328509,
343000, 357911, 373248, 389017, 405224, 421875, 438976, 456533, 474552, 493039,
512000, 531441, 551368, 571787, 592704, 614125, 636056, 658503, 681472, 704969,
729000, 753571, 778688, 804357, 830584, 857375, 884736, 912673, 941192, 970299,
1000000]
>>>``````

We can also include an if condition in the list comprehension, as follows:

``[ expression for item in iterable if condition ]``

This works exactly like above the only difference is that the `expression` before the `for` keyword is only evaluated when the `condition` is `True`.

Here is an example:

``````>>>
>>> even_list = [ i for i in range(1, 10) if i % 2 == 0 ]
>>>
>>> even_list
[2, 4, 6, 8]
>>>``````

## List Methods #

The `list` class have many built-in methods which allows us to add element, remove element, update element and much more. The following table lists some common methods provided by the `list` class to manipulate lists.

Method Description
`appends(item)` adds an `item` to the end of the list.
`insert(index, item)` inserts an `item` at the specified `index`. If `index` specified is greater than the last valid `index`, `item` is added to the end of the list.
`index(item)` returns the index of the first occurrence of specified `item`. If the specified `item` doesn't exists in the list, an exception is raised.
`remove(item)` removes the first occurrence of the specified `item` from the list. If the specified `item` doesn't exists in the list, an exception is raised.
`count(item)` returns the number of times an `item` appears in the list.
`clear()` removes all the element from the list.
`sort()` sorts the list in ascending order.
`reverse()` reverse the order of elements in the list.
`extends(sequence)` appends the elements of the `sequence` to the end of the list.
`pop([index])` removes the element at the specified `index` and returns that element. If `index` is not specified, it removes and returns last element from the list. When `index` is not valid, an exception is raised.

Note that all these method except `count()` modify the list object on which it is called.

The following shell session demonstrates how to use these methods:

### append() method #

``````>>>
>>> list1 = [1,2,3,4,5,6]
>>>
>>> id(list1)
>>>
>>> list1.append(10)   # append 10 to list1
>>>
>>> list1
[1, 2, 3, 4, 5, 6, 10]
>>>
>>> id(list1)   # address remains unchanged
45741512
>>>``````

### insert() method #

``````>>>
>>> list1 = [1,2,3,4,5,6]
>>>
>>> id(list1)
45739272
>>>
>>> list1.insert(2, 1000)  # insert item 1000 at index 2
>>>
>>> list1
[1, 2, 1000, 3, 4, 5, 6]   # now the last valid index is 6
>>>
>>>
>>> list1.insert(6, 4000)  # insert the item 4000 at index 6
>>>
>>> list1
[1, 2, 1000, 3, 4, 5, 4000, 6]  # now the last valid index is 7
>>>
>>>
>>> list1.insert(8, 8000)  # insert the item 8000 at index 8, which is beyond the last valid index
>>>
>>> list1
[1, 2, 1000, 3, 4, 5, 4000, 6, 8000]
>>> list1[8]
8000
>>>``````

### index() method #

``````>>>
>>> list1 = [1, 2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.index(1)  # index of first occurrence of element 1
0
>>> list1.index(3)  # index of first occurrence of element 3
2
>>> list1.index(90)  # an exception is raised, as value 90 doesn't exists in the list
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: 90 is not in list
>>>
>>>``````

### remove() method #

``````>>>
>>> list1 = [1, 2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.remove(1)   # remove first occurence of element 1
>>>
>>> list1
[2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.remove(2)   # remove first occurence of element 2
>>>
>>> list1
[3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.remove(100)  # an exception is raised, as value 100 doesn't exists in the list
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
ValueError: list.remove(x): x not in list
>>>
>>>``````

### count() method #

``````>>>
>>> list1 = [1, 2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> list1.count(2)   # count the appearance of element 2 in the list
2
>>> list1.count(100)  # count the appearance of element 100 in the list
0
>>> list1.count(1)  # count the appearance of element 1 in the list
2
>>>``````

### clear() method #

``````>>>
>>>
>>> list1 = [1, 2, 3, 4, 5, 6, 1, 2, 3]
>>>
>>> id(list1)
45738248
>>>
>>> list1.clear()   # clear all the elements in the list list1
>>>
>>> list1
[]
>>>
>>> id(list1)
45738248
>>>
>>>``````

### sort() method #

``````>>>
>>> list1 = [12, -2, 3, 4, 100, 50]
>>>
>>> list1.sort()   # sort the list in ascending order
>>>
>>> list1
[-2, 3, 4, 12, 50, 100]
>>>
>>> types = ["str", "float", "int", "list"]
>>>
>>> types.sort()  # sort the list, strings are sorted based on the ASCII values
>>>
>>> types
['float', 'int', 'list', 'str']
>>>
>>>``````

### reverse() method #

``````>>>
>>> list1 = [12, -2, 3, 4, 100, 50]
>>>
>>>
>>> list1.sort()  # sort the list in ascending order
>>>
>>> list1
[-2, 3, 4, 12, 50, 100]
>>>
>>> list1.reverse()  # sort the list in descending order
>>>
>>> list1
[100, 50, 12, 4, 3, -2]
>>>
>>>``````

### extends() method #

``````>>>
>>> list1 = [1, 2, 3]
>>>
>>> id(list1)
45738248
>>>
>>> list1.extend([100, 200, 300])   # append elements of list [100, 200, 300] to list1
>>>
>>> list1
[1, 2, 3, 100, 200, 300]
>>>
>>> id(list1)
45738248
>>>
>>>
>>> list1.extend("str")  # We can pass strings too
>>>
>>> list1
[1, 2, 3, 100, 200, 300, 's', 't', 'r']
>>>
>>>
>>> id(list1)
45738248
>>>
>>>``````

### pop() method #

``````>>>
>>> list1 = [1, 2, 3, 4, 5, 6]
>>>
>>> list1.pop(4)  # remove the element at index 4
5
>>> list1
[1, 2, 3, 4, 6]
>>>
>>> list1.pop()   # remove the last element
6
>>>
>>> list1
[1, 2, 3, 4]
>>>
>>>
>>> list1.pop(10)  # index specified is not valid
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
IndexError: pop index out of range
>>>
>>>``````