Lists

A list is an indexable and mutable sequence of objects. In short, lists are capable of holding various types of objects. Understand that this is a data structure, as it holds data for us.

In this lecture we will cover:

1. Initializing a List
2. Indexing, Slicing and Combining Lists
3. List Functions and Methods
4. Pythonic Code
    > List Comprehensions
    > Other nitty-gritties
5. Nesting Lists

Initializing Lists

You initialize a list with bracket notation, with each object in the list seperated with a comma.


In [122]:
# Creating a list with the numbers 1 to 5
my_list = [1, 2, 3, 4, 5]

Recall that we mentioned about lists having the ability to store multiple types of objects. If you are familiar with other programming languages, this is not always the case. Python, however, has allowed us to do so.


In [123]:
# Creating a list with an integer, string, and float.
my_new_list = [3, "Houston", 4.5]

Lastly, you can also create lists that have lists in them. An example of this is provided in the next cell, but we will talk about it further in the "Nesting Lists" section. Here, the list [3, 4, 5] is also considered one single object inside of a_list.


In [124]:
# This list contains 1, 2, and the list [3, 4, 5]
a_list = [1, 2, [3, 4, 5]]

Indexing Lists

Lists are contiguous, monolithic data structures. When we talk about objects in memory, we mean that every object has some dedicated space in memory. In a list, objects are "next" to eachother in memory. This means that accessing the next list object is as simple as finding the index of that object in the list, since it can only be some constant defined in memory. Consider this list:

lst = [1, "hello", 2.3]

Here, the number 1 comes before "hello" in memory, and the number 2.3 comes after "hello" in memory. They are seperated by some given space, and are thus contiguous in memory. Why does contiguousness matter? It means we can index lists and get their objects out. Recall indexing a string:


In [125]:
# Declaring a string
my_str = "hello"

In [126]:
# Writing a print function with comma seperation (each object is printed with a space next to the previous)
print(my_str[0], my_str[1], my_str[2], my_str[3], my_str[4])


h e l l o

Let us now declare a list and try working with the same indexing constructs that functioned with a string.


In [127]:
# Declare a list with varying object types
lst = [1, "hello", 2.3]

In [128]:
# printing the first object in a list
print(lst[0])


1

In [129]:
# printing all of the objects in the list
print(lst[0], lst[1], lst[2])


1 hello 2.3

Slicing Lists

A slice of a sequence is the act of accessing multiple objects within it simultaneously. The notation may look familiar.

my_list[start:end:skip]

Recall the behavior when working with strings (sequence of characters).


In [130]:
# Declaring a string
my_str = "goodbye"

In [131]:
# Access first 3 objects in list (item 0, 1, 2)
print(my_str[0:3])


goo

Let's also review notation. We could right the above cell this way, too.


In [132]:
print(my_str[:3])


goo

A few more examples.


In [133]:
# Access beginning to 2nd character to end
print(my_str[1:])


oodbye

In [134]:
# Access every other character (skipping)
print(my_str[::2])


gobe

In [135]:
# Access backwards
print(my_str[::-1])


eybdoog

Lists work no differently. Slicing lists is shown below.


In [136]:
# A list with strings, integer, and float
my_list = ["a", "collection", "of", 4, "version", 5.0, "glasses"]

In [137]:
# To end
print(my_list[0:])


['a', 'collection', 'of', 4, 'version', 5.0, 'glasses']

In [138]:
# middle 3
print(my_list[2:5])


['of', 4, 'version']

In [139]:
# backwards
print(my_list[::-1])


['glasses', 5.0, 'version', 4, 'of', 'collection', 'a']

In general, string and list behavior is similar because they are both objects that are sequences. More specifically, lists are data structures - strings are not.

Combining Lists

Like numbers, lists can use mathematical operators (some of them). Consider the following.

Addition - appending a new list to the end
Multiplying - appending the list itself by provided integer constant

In [140]:
my_list = ["wow", 1, 2]

Adding two lists is what you would expect. The list is appended (added) to the end. It could be a list of a single object - it does not matter.


In [141]:
# Adding two lists
my_list = my_list + my_list
print(my_list)


['wow', 1, 2, 'wow', 1, 2]

Or, we can just append an item. (Note that we are chaning my_list permanently as we go to each cell)


In [142]:
# Appending the string "work" to our list
my_list = my_list + ["work"]
print(my_list)


['wow', 1, 2, 'wow', 1, 2, 'work']

Multiplication on a list can be done with a constant number. You cannot multiply two lists together (only a constant to replicate the list at hand).


In [143]:
my_list = ["wow", 1, 2]
my_list = my_list * 3
print(my_list)


['wow', 1, 2, 'wow', 1, 2, 'wow', 1, 2]

List Functions and Methods

In the Python Standard Library, each built-in object has its own set of functions and methods. The mathematical operators used with numbers +, -, *, and / actually correspond to the "add", "sub", "mul" and "div" functions. Lists are no different. They also have their own functions.

List Functions

The function len() is one you will use very often. Recall that functions are not associated with specific objects.


In [144]:
# Length of my list
my_list = [1, 2, "baby", "just"]

In [145]:
# Get the length
print(len(my_list))


4

Often, you will find yourself trying to find the last index of a list. Well, if know that "n" represents the length of a list, the last element's position can be identified as indice "n - 1".


In [146]:
# Should print "just"
last_indice = len(my_list) - 1
print(my_list[last_indice])


just

List Methods

There are many important list methods. Recall that methods are functions that have an association with an object.

We'll cover most of the methods you can use on a list:

1. append(object) - returns and appends given item to a list
2. clear() - clears (emptys) a list
3. copy() - returns a replicated list object of implicit list object
4. count(object) - returns count of object occurences in list
5. extend(iterable object) - returns a list object with objects appended from explicit iterable
6. index(object) - returns index of the first occurence of object in list
7. insert(inidice, object) - object added at given position
8. pop - returns and removes the last object in a list
9. remove - returns a list with object removed
10. reverse - returns a reversed list
11. sort - returns sorted list

In [147]:
# Appending an item to a list
my_list = ["charlotte", "winnipeg", "sydney"]
print("before:", my_list)
my_list.append("chicago")
print("after: ", my_list)


before: ['charlotte', 'winnipeg', 'sydney']
after:  ['charlotte', 'winnipeg', 'sydney', 'chicago']

In [148]:
# clearing a list
my_list = ["charlotte", "winnipeg", "sydney"]
print("before:", my_list)
my_list.clear()
print("after:", my_list)


before: ['charlotte', 'winnipeg', 'sydney']
after: []

In [149]:
# Copying a list
my_list = ["charlotte", "winnipeg", "sydney"]
print("before:", my_list)
my_new_list = my_list.copy()
print("after:", my_list)
print("my_new_list:", my_new_list)


before: ['charlotte', 'winnipeg', 'sydney']
after: ['charlotte', 'winnipeg', 'sydney']
my_new_list: ['charlotte', 'winnipeg', 'sydney']

In [150]:
# counting a list
my_list = ["charlotte", "winnipeg", "sydney"]
print("before:", my_list.count("winipeg"))
my_list.append("winipeg")
print("after:", my_list.count("winipeg"))


before: 0
after: 1

In [151]:
# extending a list
my_list = ["charlotte", "winipeg", "sydney"]
print("before:", my_list)
my_list.extend(["chicago", "houston"])
print("after:", my_list)

# what would this do? Look at the markdown cell a few below.
# my_list.extend("chicago")


before: ['charlotte', 'winipeg', 'sydney']
after: ['charlotte', 'winipeg', 'sydney', 'chicago', 'houston']

In [152]:
# indexing a list
my_list = ["charlotte", "winipeg", "sydney"]
print("before:", my_list.index("sydney"))
my_list.append("sydney")
print("before:", my_list.index("sydney"))


before: 2
before: 2

In [153]:
# inserting into a list
my_list = ["charlotte", "winipeg", "sydney"]
print("before:", my_list)
my_list.insert(1, "chicago")
print("after:", my_list)


before: ['charlotte', 'winipeg', 'sydney']
after: ['charlotte', 'chicago', 'winipeg', 'sydney']

In [154]:
# popping from a list
my_list = ["charlotte", "winnipeg", "sydney"]
print("before:", my_list)
my_list.pop()
print("after:", my_list)


before: ['charlotte', 'winnipeg', 'sydney']
after: ['charlotte', 'winnipeg']

In [155]:
# removing from a list
my_list = ["charlotte", "winnipeg", "sydney"]
print("before:", my_list)
my_list.remove("sydney")
print("after:", my_list, '\n')

# another example of removing
my_list = ["charlotte", "winnipeg", "sydney"]
print("before:", my_list)
my_list.insert(2, "charlotte")
print("after inserting:", my_list)
my_list.remove("charlotte")
print("after removing:", my_list)

# which "charlotte" got removed? (Hint: first occurences, only!)


before: ['charlotte', 'winnipeg', 'sydney']
after: ['charlotte', 'winnipeg'] 

before: ['charlotte', 'winnipeg', 'sydney']
after inserting: ['charlotte', 'winnipeg', 'charlotte', 'sydney']
after removing: ['winnipeg', 'charlotte', 'sydney']

In [156]:
# reversing a list
my_list = ["charlotte", "winnipeg", "sydney"]
print("before:", my_list)
my_list.reverse()
print("after:", my_list)


before: ['charlotte', 'winnipeg', 'sydney']
after: ['sydney', 'winnipeg', 'charlotte']

In [157]:
# sorting a list
my_list = ["charlotte", "winipeg", "sydney"]
print("before:", my_list)
my_list.sort()
print("after:", my_list)


before: ['charlotte', 'winipeg', 'sydney']
after: ['charlotte', 'sydney', 'winipeg']

Pythonic Code

Code that you write normally can be more than likely shortened if it is written "Pythonically". Idiomatic Python. Python is more than just a programming language, as the foundation has released specifications in the documents as to how to most efficiently write python code - mainly to cover "natural constructs" of otherwise messy statements. Pythonic code allows the programmer to drastically shorten operations, and Python can get away with this because of its nature of being a high-level programming language. It is essential you understand how to write code Pythonically, as many Python progammers use the notations specified very frequently. It also saves a lot of time, too. Explore the sections below to see how Pythonic code applies to lists and beyond.

List Comprehensions

List comprehensions allow for acting on lists in a faster, iterative fashion. Note: While we haven't discussed iterative constructs like the for-statement yet, it is enough to understand that a for-statement allows for a repetitive task to be accomplished. Take the following, self-explanatory example.


In [167]:
"""As you see, count assumes the values 0, 1... 10 and for each time it assumes a value, it prints "hello". Because
it assumes a value 10 times (the y number in range(x, y) is exclusive), it prints 10 times. """
my_list = []
for count in range(0, 10):
    my_list.append("hello")
print(my_list)


['hello', 'hello', 'hello', 'hello', 'hello', 'hello', 'hello', 'hello', 'hello', 'hello']

That look a total of 3 lines of code to do. We can reduce this 1 line with list comprehensions.


In [168]:
# Now, using a list comprehension
my_list = ["hello" for count in range(0,10)]
print(my_list)


['hello', 'hello', 'hello', 'hello', 'hello', 'hello', 'hello', 'hello', 'hello', 'hello']

Let's breakdown the usage of a list comprehension.

lst_here = [object_to_add for loop_control_variable in range(start, end)]

where "lst_here" is the new list, "object_to_add" is what is appended to the list after running the for-loop, where "loop_control_variable" assumes the values given in the range start to end, where end is exclusive. We will discuss Pythonic Code in the form of list comprehensions and other constructs much, much more in detail (a whole notebook) later in the course. Just keep it in mind, as we will be using it very often. Below are a few more examples of the list comprehesion - see if you can understand them!


In [169]:
# Storing the squares of 0-9 in a list with a list comprehension
my_list = [x**2 for x in range(0,10)]
print(my_list)


[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In [177]:
# Non-Pythonically
my_list = []
for name in ["miranda", "mike", "john"]:
    my_list.append(name + " is cool")
print("Non-pythonic:", my_list)

# Pythonically
my_list = [name+" is cool" for name in ["miranda", "mike", "john"]]
print("Pythonic:",my_list)


Non-pythonic: ['miranda is cool', 'mike is cool', 'john is cool']
Pythonic: ['miranda is cool', 'mike is cool', 'john is cool']

Other Pythonic Specifications

There are many Pythonic constructs, and we will use almost all of them. There will be a whole notebook covering Pythonic code after we cover the rest of the object and data structure and control flow lectures. For now, refer to this guide: http://python.net/~goodger/projects/pycon/2007/idiomatic/handout.html

Nesting Lists

Lists can be nested into one another. A nested lists acts like any other object in a list, except that a list object inside a list has a "deeper" level of access. Firstly, you can access the list object itself, and then access the objects in the list object. Consider the following example.

We declare a list the same way, seperating our internal list object with a commma, too.


In [179]:
# Declaring a nested list
my_list = [1, [2, 3, 4], 5]

How would be access the list object (literal) [2, 3, 4]? Like any other object, of course.


In [180]:
# Accessing the list object
my_list[1]


Out[180]:
[2, 3, 4]

How can we access the number object 3 in the list? We need to specify a deeper level of access.


In [181]:
# Accessing the object of a list's list
my_list[1][1]


Out[181]:
3

It worked, but how? Well, we first operated with

my_list[1]

which returned the list [2, 3, 4]. We now have the internal list, accessing this list's object is the same accessing any others...

my_list[1][1]

returns the number object 3, just what we wanted.

my_list[1] -> [2,3,4] -> my_list[1][1] -> 3