# Python Concepts/Lists
## Objective

* Learn about Python lists.
* Learn about list indexing.
* Learn about list slicing.
* Learn how to manipulate a list dynamically.
* Learn about built-in list functions and methods.
* Learn when to use lists and when not to.

## Lesson

* Python Lists
* Lists are an important data type that allows you to keep a set of iterable item(s). Lists are mutable sequences. This literally means you can have a list of items, like 1, 2, 3 or "h", "e", "l", "l", "o", "!". A list is denoted by square brackets,. To create a list that holds actual items,
* put them within square brackets and use commas to separate the items.
* >>> [1]
* [1]
* >>> [1, 2, 3, 4 ,5]
* [1, 2, 3, 4, 5]
* >>> ["h", "e", "l", "l" ,"o", "!"]
* ['h', 'e', 'l', 'l', 'o', '!']
* >>> [1, 102, 2.22, "F", 2/2, 1+2j, False]
* [1, 102, 2.22, 'F', 1.0, (1+2j), False]
* >>> list("hello")
* ['h', 'e', 'l', 'l', 'o']
* The built-in function, list converts a non-list into a list:
* >>> a = 'defgh' ; a
* 'defgh'
* >>> b = list(a) ; b
* ['d', 'e', 'f', 'g', 'h']
* >>> a = b'defgh' ; a
* b'defgh'
* >>> b = list(a) ; b
* [100, 101, 102, 103, 104]
* >>> list(range(7,13))
* [7, 8, 9, 10, 11, 12]
* Lists can also hold multiple different data types, so you can mix numbers, strings, and complexes. Better yet, we can nest lists within each other.
* >>> [[1, 2, 3], [2, 1, 3], [True, False, True]]
* [[1, 2, 3], [2, 1, 3], [True, False, True]]
* >>> [[[[[[1, 2, 3]]]]]]
* [[[[[[1, 2, 3]]]]]]
* >>> [[[[ 1, 2, [[1, 2, 3]]]]]]
* [[[[1, 2, [[1, 2, 3]]]]]]
* A list can contain a two-dimensional array:
* >>> a = ['a1','a2']
* >>> b = ['b1','b2']
* >>> c = ['c1','c2']
* >>> L = [a,b,c] ; L
* [['a1', 'a2'], ['b1', 'b2'], ['c1', 'c2']]
* >>> L[0] ; L[1] ; L[2]
* ['a1', 'a2']
* ['b1', 'b2']
* ['c1', 'c2']
* >>> L[0][0] ; L[1][1] ; L[2][0]
* 'a1'
* 'b2'
* 'c1'
* A list can contain a three-dimensional array:
* >>> b = ['b1','b2']
* >>> c = ['c1','c2']
* >>> d = ['d1','d2']
* >>> a = [b,c,d]
* >>> B = ['B1','B2']
* >>> C = ['C1','C2']
* >>> D = ['D1','D2']
* >>> A = [B,C,D]
* >>> array = [a,A]
* >>> array
* [[['b1', 'b2'], ['c1', 'c2'], ['d1', 'd2']], [['B1', 'B2'], ['C1', 'C2'], ['D1', 'D2']]]
* >>> array[0]
* [['b1', 'b2'], ['c1', 'c2'], ['d1', 'd2']]
* >>> array[1]
* [['B1', 'B2'], ['C1', 'C2'], ['D1', 'D2']]
* >>> array[1][2]
* ['D1', 'D2']
* >>> array[1][2][1]
* 'D2'
* List nesting can get very complicated as the nesting gets deeper, but it creates a great amount of power that will be harnessed in the later parts of this course.

## List Indexing

* Like strings, lists can be indexed. Indexing can be used to get one item out of the list. For example, if you want to get "o" out of ["h", "e", "l", "l", "o", "!"] type ["h", "e", "l", "l", "o", "!"][4] although, for brevity, we'll put the lists in a variable first. Don't forget that indexing is zero-based in Python.
* >>> spam = ["h", "e", "l", "l", "o", "!"]
* >>> spam[1]
* 'e'
* >>> spam[5]
* >>> spam[3]
* 'l'
* Like strings, you can index starting with the last item by using a negative number. Don't forget that negative one is the start of the last item, not zero!
* >>> spam[-1]
* >>> spam[-3]
* 'l'
* >>> spam[-2]
* 'o'
* >>> spam[-5]
* 'e'
* If you index out of range, by trying to get an item that doesn't exist, you'll get an IndexError.
* >>> spam[1000]
* Traceback (most recent call last):
* File "<stdin>", line 1, in <module>
* IndexError: list index out of range
* >>> spam[-10]
* Traceback (most recent call last):
* File "<stdin>", line 1, in <module>
* IndexError: list index out of range

## List Slicing

* Like strings, lists can be sliced. When you slice a list, it will return a new sliced copy of the original list.
* >>> spam = [1, 2, 3]
* >>> spam[0:3]
* [1, 2, 3]
* >>> spam[0:]
* [1, 2, 3]
* >>> spam[1:]
* [2, 3]
* >>> spam[:1]
* [1]
* >>> spam[:-1]
* [1, 2]
* >>> spam[:0]
* Lists also support extended slicing, where the third parameter acts as the "step". A step of 1 is the default value.
* >>> bacon = ["h", "e", "l", "l", "o", ",", " ", "w", "o", "r", "l", "d", "!"]
* >>> bacon[::-1]
* ['!', 'd', 'l', 'r', 'o', 'w', ' ', ',', 'o', 'l', 'l', 'e', 'h']
* >>> bacon[::1]
* ['h', 'e', 'l', 'l', 'o', ',', ' ', 'w', 'o', 'r', 'l', 'd', '!']
* >>> bacon[::2]
* ['h', 'l', 'o', ' ', 'o', 'l', '!']
* >>> bacon[::-2]
* ['!', 'l', 'o', ' ', 'o', 'l', 'h']
* >>> bacon[::-5]
* ['!', 'w', 'l']
* >>> bacon[:7:-2]
* ['!', 'l', 'o']
* The power of slicing
* >>> b = [0,1,2,3,4,5,6] ; b
* [0, 1, 2, 3, 4, 5, 6]
* >>> b[0:0] = [7,8,9] ; b # insert elements at beginning of list.
* [7, 8, 9, 0, 1, 2, 3, 4, 5, 6]
* >>> b[0:2] = ; b # delete elements at beginning of list.
* [9, 0, 1, 2, 3, 4, 5, 6]
* >>> b = [0,1,2,3,4,5,6] ; b
* [0, 1, 2, 3, 4, 5, 6]
* >>> b += [7,8,9] ; b # add elements at end of list.
* [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
* >>> b[-2:] = ; b # delete elements at end of list.
* [0, 1, 2, 3, 4, 5, 6, 7]
* >>> b = [0,1,2,3,4,5,6] ; b
* [0, 1, 2, 3, 4, 5, 6]
* >>> b[2:5] = [7,8,9,10] ; b # modify interior of list
* [0, 1, 7, 8, 9, 10, 5, 6]
* >>> # use of brackets '':
* >>> b = [[1,2],[3,4],[5,6]] ; b
* [[1, 2], [3, 4], [5, 6]]
* >>> b[1:2] = [7,8]+[9,10] ; b
* [[1, 2], 7, 8, 9, 10, [5, 6]]
* >>> b = [[1,2],[3,4],[5,6]] ; b
* [[1, 2], [3, 4], [5, 6]]
* >>> b[1:2] = [[7,8]+[9,10]] ; b
* [[1, 2], [7, 8, 9, 10], [5, 6]]
* >>> b = [[1,2],[3,4],[5,6]] ; b
* [[1, 2], [3, 4], [5, 6]]
* >>> b[1:2] = [[7,8],[9,10]] ; b
* [[1, 2], [7, 8], [9, 10], [5, 6]]

## List manipulation

* Adding a new item to a list is pretty basic. The best way to add a new item is by using the built-in list method append. This can be easily done as shown below.
* >>> spam = [1, 2, 3, 4]
* >>> spam.append(5)
* >>> spam
* [1, 2, 3, 4, 5]
* >>> spam.append(12)
* >>> spam
* [1, 2, 3, 4, 5, 12]
* >>> spam.append(6)
* >>> spam
* [1, 2, 3, 4, 5, 12, 6]
* also:
* >>> spam = [1,2,3] ; eggs = [8,7,6] ; spam ; eggs
* [1, 2, 3]
* [8, 7, 6]
* >>> spam += eggs ; spam
* [1, 2, 3, 8, 7, 6]
* Unlike strings, lists are mutable (can be changed). The best way to change an item in a list is to do so by indexing.
* >>> toast = [1, 2, 3]
* >>> toast[0] = 100
* >>> toast
* [100, 2, 3]
* >>> toast[1] = 200
* >>> toast
* [100, 200, 3]
* >>> toast[2] = 300
* >>> toast
* [100, 200, 300]
* There are a few ways to remove an item from a list, but because Python culture dictates that there should be one preferable way to do something, I'll give an example that's fast and uses less code.
* >>> juice = ["a", "b", "c"]
* >>> del juice[1]
* >>> juice
* ['a', 'c']
* >>> del juice[0]
* >>> juice
* ['c']
* >>> del juice[0]
* >>> juice
* >>> dog = ["bark", "talk", "beg"]
* >>> del dog[:]
* >>> dog
* You should notice that when you delete an item, the items to the right of the deleted item move over to the left (if possible). So you could keep deleting item zero to remove all of the items. An easier way to delete a whole list is to follow the example with dog. This completely deletes all of the items in the list.
* Note: In the previous example, del dog[:] deletes the contents of the list, not the variable. If you wanted to delete the variable, but not the list, you would use del dog. This will be taught later in the course, so you don't have to worry too much about it.
* As stated earlier, lists are mutable types, which means their content can dynamically change. Take spam for example. Spam really isn't the list, it just points to the location of the list. Take an address book for another example. It contains the location of a house. It doesn't know what's in the house, it just knows where the house is. spam and the list have the same relationship as the address book and the house.
* Now this leads to some important questions, primarily how would you copy spam if it contains the address of the list? Since spam contains the address, giving another variable the same address isn't going to help you.
* >>> spam = [1, 2, 3]
* >>> eggs = spam
* >>> spam
* [1, 2, 3]
* >>> eggs
* [1, 2, 3]
* >>> spam[1] = 22
* >>> spam
* [1, 22, 3]
* >>> eggs
* [1, 22, 3]
* As you can see from the above example, spam and eggs both point to the same list and
* any change to one affects the other. This is called a shallow copy. If you want two completely different lists, then you create a deep copy. When slicing, the return is always a deep copy, so we could perform a simple slice to create a new list for eggs.
* >>> spam = [1, 2, 3]
* >>> eggs = spam[:]
* >>> spam
* [1, 2, 3]
* >>> eggs
* [1, 2, 3]
* >>> spam[1] = 22
* >>> spam
* [1, 22, 3]
* >>> eggs
* [1, 2, 3]
* This is an important concept to remember early on so you don't run into any problems in the future.
* While the above example is accurate, it seems to be true only for small lists. NB the following:
* Deep copy
* >>> b = [ [4, 4, -119, 16], [-1, 1, -1, 1], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16] ] ; b
* [[4, 4, -119, 16], [-1, 1, -1, 1], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16]]
* >>> c = b[:] ; c
* [[4, 4, -119, 16], [-1, 1, -1, 1], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16]] # a deep copy of c????
* >>> b[0][1] = 23 ; b ; c
* [[4, 23, -119, 16], [-1, 1, -1, 1], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16]] # both have changed
* [[4, 23, -119, 16], [-1, 1, -1, 1], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16]] # not a deep copy
* >>> c = copy.deepcopy(b) ; b ; c
* [[4, 23, -119, 16], [-1, 1, -1, 1], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16]]
* [[4, 23, -119, 16], [-1, 1, -1, 1], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16]]
* >>> b[1][3] = 23 ; b ; c
* [[4, 23, -119, 16], [-1, 1, -1, 23], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16]] # only b has changed
* [[4, 23, -119, 16], [-1, 1, -1, 1], [0, 14, -17, 0], [1, 1, -13, 1], [2, 4, 8, 16]] # a true deep copy
* See the reference: " Shallow and deep copy operations"

## Built-in List Methods

* While the following methods have the appearance of functions and it can be tempting to call them "functions,"
* the correct description is "methods."
* list.append(x)
* Adds item x to the end of list list.
* >>> spam = [1, 2, 3]
* >>> spam.append(4)
* >>> spam
* [1, 2, 3, 4]
* equivalent to:
* >>> spam = [1, 2, 3]
* >>> spam += [4]
* >>> spam
* [1, 2, 3, 4]
* list.clear
* Removes all items from list list.
* >>> eggs = [1, 2, 3]
* >>> eggs.clear
* >>> eggs
* equivalent to:
* >>> eggs = [1,2,3] ; eggs
* [1, 2, 3]
* >>> eggs = ; eggs
* list.count(x)
* Returns the number of times a given item x is found in list list.
* >>> votes = ["yes", "no", "yes", "yes", "no"]
* >>> y = votes.count("yes") ; y
* 3
* >>> votes.count("no")
* 2
* list.extend(iterable)
* Extend list by appending all items from iterable
* >>> bacon = [1, 2, 3]
* >>> bacon.extend([4, 5, 6])
* >>> bacon
* [1, 2, 3, 4, 5, 6]
* equivalent to:
* >>> bacon = [1, 2, 3]
* >>> bacon += [4, 5, 6] ; bacon
* [1, 2, 3, 4, 5, 6]
* list.index(x[, start[, end]])
* Finds the first zero-based index of a given item x in list list.
* The square brackets around [, start[, end]] indicate that these two arguments are optional.
* start and end are interpreted as in slice notation and are used to limit the search to a particular subsequence of the list.
* Case1: list.index(x)
* >>> votes = ["yes", "no", "yes", "yes", "no"]
* >>> a = votes.index("no") ; a
* 1
* >>> votes.index("yes")
* 0
* >>> votes.index("maybe")
* Traceback (most recent call last):
* File "<stdin>", line 1, in <module>
* ValueError: 'maybe' is not in list
* Case2: list.index(x, start) equivalent to list[start:].index(x) + start.
* >>> votes
* ['yes', 'no', 'yes', 'yes', 'no']
* >>> votes.index("yes", 1)
* 2
* >>> votes.index("no", 2)
* 4
* >>> votes.index("yes", 4)
* Traceback (most recent call last):
* File "<stdin>", line 1, in <module>
* ValueError: 'yes' is not in list
* Case3: list.index(x, start, end) equivalent to list[start:end].index(x) + start.
* >>> votes
* ['yes', 'no', 'yes', 'yes', 'no']
* >>> votes.index("yes", 1, 3)
* 2
* >>> votes.index("no", 2, 5)
* 4
* >>> votes.index("no", 2, 4)
* Traceback (most recent call last):
* File "<stdin>", line 1, in <module>
* ValueError: 'no' is not in list
* list.insert(i, x)
* Inserts item x at position i where i is the zero-based index of the element before which to insert:
* >>> jam = [1, 2, 3, 4]
* >>> jam.insert(3, 3.5)
* >>> jam
* [1, 2, 3, 3.5, 4]
* equivalent to :
* >>> jam = [1, 2, 3, 4]
* >>> jam = jam[:3] + [3.5] + jam[3:] ; jam
* [1, 2, 3, 3.5, 4]
* or:
* >>> jam = [1,2,3,4] ; jam
* [1, 2, 3, 4]
* >>> jam[3:3] = [3.5] ; jam
* [1, 2, 3, 3.5, 4]
* Out of range:
* >>> jam
* [1, 2, 3, 3.5, 4, 7]
* >>> jam.insert(12,11) ; jam
* [1, 2, 3, 3.5, 4, 7, 11] # as in slicing, this doesn't produce an error.
* list.pop([i])
* Returns list[i] then deletes list[i]. If optional argument i is not provided i defaults to -1.
* >>> toast = [1, 2, 3, 4, 5, 6, 7]
* >>> jelly = toast.pop(2) ; jelly ; toast
* 3
* [1, 2, 4, 5, 6, 7]
* equivalent to:
* >>> toast = [1, 2, 3, 4, 5, 6, 7]
* >>> jelly = toast[2] ; del toast[2] ; jelly ; toast
* 3
* [1, 2, 4, 5, 6, 7]
* >>> toast = [1, 2, 3, 4, 5, 6, 7]
* >>> jelly = toast.pop ; jelly ; toast
* 7
* [1, 2, 3, 4, 5, 6]
* equivalent to:
* >>> toast = [1, 2, 3, 4, 5, 6, 7]
* >>> jelly = toast[-1] ; del toast[-1] ; jelly ; toast
* 7
* [1, 2, 3, 4, 5, 6]
* list.remove(x)
* Remove the first item from list whose value is x. It is an error if there is no such item.
* >>> bacon = [1, 2, 3]
* >>> bacon.remove(2) # Item removed was not at position 2. It had value 2.
* >>> bacon
* [1, 3]
* list.reverse
* This reverses the order of list in place.
* >>> coke = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
* >>> coke.reverse
* >>> coke
* [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]
* equivalent to:
* >>> coke = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
* >>> coke = coke[::-1] ; coke
* [10, 9, 8, 7, 6, 5, 4, 3, 2, 1]

## List Usage

* Although lists are useful, they are about four to seven times slower than tuples, because lists aren't as static as tuples. Large lists usually have a higher penalty on performance, so try keep them short. As a general rule of thumb, use lists only when you need to dynamically hold information and values.
* Using Lists as Stacks
* The list methods make it easy to use a list as a stack, where the last element added is the first element retrieved (“last-in, first-out”). To add an item to the top of the stack, use append. To retrieve an item from the top of the stack,
* use pop without an explicit index. For example:
* >>> stack = [3, 4, 5]
* >>> stack.append(6) # push 6 onto stack
* >>> stack += [7] # push 7 onto stack. 7 is last in.
* >>> stack
* [3, 4, 5, 6, 7]
* >>> stack.pop
* 7 # 7 is first out.
* >>> stack
* [3, 4, 5, 6]
* >>> stack.pop
* 6
* >>> stack.pop
* 5
* >>> stack
* [3, 4]
* Using Lists as Queues
* While lists are not efficient for this purpose, they can be used to illustrate the concept quite well.
* A queue is a sequence of elements where the first element added is the first element retrieved (“first-in, first-out”).
* >>> queue = # empty queue
* >>> queue.append('John') ; queue # 'John' is first to arrive
* ['John']
* >>> queue += ['Alex'] ; queue # then 'Alex' arrives and goes to end-of-line.
* ['John', 'Alex']
* >>> queue.append('Terry') ; queue # then 'Terry' arrives and goes to end-of-line.
* ['John', 'Alex', 'Terry']
* >>> nextOut = queue[0] ; del queue[0] ; nextOut ; queue # 'John' was first in and first out.
* 'John'
* ['Alex', 'Terry']
* >>> nextOut = queue.pop(0) ; nextOut ; queue # 'Alex' was 2nd in and 2nd out.
* 'Alex'
* ['Terry']
* >>> queue.append('Graham') ; queue # then 'Graham' arrives and goes to end-of-line.
* ['Terry', 'Graham']
* List Comprehensions
* List comprehensions provide a concise way to create lists. Common applications are to make new lists where each element
* satisfies a certain condition applied to each element of a given list.
* Given:
* >>> a = list(range(-5,7)) ; a
* [-5, -4, -3, -2, -1, 0, 1, 2, 3, 4, 5, 6]
* List all of the negative numbers in list a:
* >>> b = [x for x in a if x < 0] ; b
* [-5, -4, -3, -2, -1]
* The above is equivalent to:
* >>> b
* >>> for x in a :
* ... if x < 0 : b += [x]
* >>> b
* [-5, -4, -3, -2, -1]
* How many floats are there in list a?
* >>> len([x for x in a if isinstance(x,float)])
* 0
* List the position and value of all odd numbers in list a:
* >>> [(i,a[i]) for i in range(len(a)) if a[i] % 2]
* [(0, -5), (2, -3), (4, -1), (6, 1), (8, 3), (10, 5)]

## Assignments

* Work with some lists. Try creating a list of lists. How could this be used in a real life situation?
* Work with some of the list's methods. Try them all out at least three times.
* Does Python lists resemble real life lists? Could it be made into a simple grocery or to do list?

## Further Reading or Review

Completion status: Ready for testing by learners and teachers. Please begin!
Previous Lesson: Strings
This Lesson:Lists
Next Lesson: Bytes objects and Bytearrays
Course Home Page :