Basic Concepts

Introduction

Text

Lists

List Basics

A list stores a series of items in a particular order

It allows you to store sets of information in one place, whether you have just a few items or millions of items

 
 
# Lists are instantiated with brackets like so

 
# Lists are instantiated with brackets like so
>>> my_list = []

 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
>>> my_other_list[0]
 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
>>> my_other_list[0]
5

 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
>>> my_other_list[0]
5

>>> my_other_list[2]

 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
>>> my_other_list[0]
5

>>> my_other_list[2]
9

 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
>>> my_other_list[0]
5

>>> my_other_list[2]
9

# Just like in c++, indices in Python start at 0


 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
>>> my_other_list[0]
5

>>> my_other_list[2]
9

# Just like in c++, indices in Python start at 0
# meaning there is no element at index 3 for our list


 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
>>> my_other_list[0]
5

>>> my_other_list[2]
9

# Just like in c++, indices in Python start at 0
# meaning there is no element at index 3 for our list
>>> my_other_list[3]

 
# Lists are instantiated with brackets like so
>>> my_list = []
>>> my_other_list = [5, 7, 9]

# Elements can be accessed by their indices 
>>> my_other_list[0]
5

>>> my_other_list[2]
9

# Just like in c++, indices in Python start at 0
# meaning there is no element at index 3 for our list
>>> my_other_list[3]
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
IndexError: list index out of range
 
 
# Negative indices reference elements 
 
# Negative indices reference elements 
# starting from the back of the list
 
# Negative indices reference elements 
# starting from the back of the list
>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
 
# Negative indices reference elements 
# starting from the back of the list
>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> my_list[-1]
 
# Negative indices reference elements 
# starting from the back of the list
>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> my_list[-1]
9


 
# Negative indices reference elements 
# starting from the back of the list
>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> my_list[-1]
9

>>> my_list[-3]
 
# Negative indices reference elements 
# starting from the back of the list
>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> my_list[-1]
9

>>> my_list[-3]
7
 
# Negative indices reference elements 
# starting from the back of the list
>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> my_list[-1]
9

>>> my_list[-3]
7

>>> my_list[-9]
 
# Negative indices reference elements 
# starting from the back of the list
>>> my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9]
>>> my_list[-1]
9

>>> my_list[-3]
7

>>> my_list[-9]
1

 
 
# You can set the values of individual elements
 
# You can set the values of individual elements
# by referencing them in the same way

 
# You can set the values of individual elements
# by referencing them in the same way

>>> my_list = [0, 0, 0, 0, 0]

 
# You can set the values of individual elements
# by referencing them in the same way

>>> my_list = [0, 0, 0, 0, 0]

>>> my_list[2] = 1
 
# You can set the values of individual elements
# by referencing them in the same way

>>> my_list = [0, 0, 0, 0, 0]

>>> my_list[2] = 1
>>> my_list
 
# You can set the values of individual elements
# by referencing them in the same way

>>> my_list = [0, 0, 0, 0, 0]

>>> my_list[2] = 1
>>> my_list
[0, 0, 1, 0, 0]
 
# You can set the values of individual elements
# by referencing them in the same way

>>> my_list = [0, 0, 0, 0, 0]

>>> my_list[2] = 1
>>> my_list
[0, 0, 1, 0, 0]

>>> my_list[4] = 2
 
# You can set the values of individual elements
# by referencing them in the same way

>>> my_list = [0, 0, 0, 0, 0]

>>> my_list[2] = 1
>>> my_list
[0, 0, 1, 0, 0]

>>> my_list[4] = 2
>>> my_list
 
# You can set the values of individual elements
# by referencing them in the same way

>>> my_list = [0, 0, 0, 0, 0]

>>> my_list[2] = 1
>>> my_list
[0, 0, 1, 0, 0]

>>> my_list[4] = 2
>>> my_list
[0, 0, 1, 0, 2]
 
# Side Note
# Side Note
# You can experiment with Python 3 in the same way I do here

# Side Note
# You can experiment with Python 3 in the same way I do here

>>> a = []
>>> a
[]
# Side Note
# You can experiment with Python 3 in the same way I do here

>>> a = []
>>> a
[]

# by simply typing 'python3' on the Pi and Mac terminals
# to activate the Python Shell
# Side Note
# You can experiment with Python 3 in the same way I do here

>>> a = []
>>> a
[]

# by simply typing 'python3' on the Pi and Mac terminals
# to activate the Python Shell

# Try it out yourself!
# Side Note
# You can experiment with Python 3 in the same way I do here

>>> a = []
>>> a
[]

# by simply typing 'python3' on the Pi and Mac terminals
# to activate the Python Shell

# Try it out yourself!

# Type 'exit()' to exit out of the shell

List Modification

Lists also have built-in functions you can use

 

# append() attaches an element to the end of the list
>>> my_list = [1, 2, 3]


# append() attaches an element to the end of the list
>>> my_list = [1, 2, 3]

>>> my_list.append(5)
>>> my_list

# append() attaches an element to the end of the list
>>> my_list = [1, 2, 3]

>>> my_list.append(5)
>>> my_list
[1, 2, 3, 5]


# append() attaches an element to the end of the list
>>> my_list = [1, 2, 3]

>>> my_list.append(5)
>>> my_list
[1, 2, 3, 5]

>>> my_list.append(7)
>>> my_list.append(9)
>>> my_list.append(11)
>>> my_list

# append() attaches an element to the end of the list
>>> my_list = [1, 2, 3]

>>> my_list.append(5)
>>> my_list
[1, 2, 3, 5]

>>> my_list.append(7)
>>> my_list.append(9)
>>> my_list.append(11)
>>> my_list
[1, 2, 3, 5, 7, 9, 11]
 

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# del will remove an element by its index
>>> del my_list[2]
>>> my_list

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# del will remove an element by its index
>>> del my_list[2]
>>> my_list
[1, 2, 4, 5, 1]


# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# del will remove an element by its index
>>> del my_list[2]
>>> my_list
[1, 2, 4, 5, 1]

>>> del my_list[-1]
>>> my_list

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# del will remove an element by its index
>>> del my_list[2]
>>> my_list
[1, 2, 4, 5, 1]

>>> del my_list[-1]
>>> my_list
[1, 2, 4, 5]


# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# del will remove an element by its index
>>> del my_list[2]
>>> my_list
[1, 2, 4, 5, 1]

>>> del my_list[-1]
>>> my_list
[1, 2, 4, 5]

>>> del my_list[2]
>>> my_list

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# del will remove an element by its index
>>> del my_list[2]
>>> my_list
[1, 2, 4, 5, 1]

>>> del my_list[-1]
>>> my_list
[1, 2, 4, 5]

>>> del my_list[2]
>>> my_list
[1, 2, 5]

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# remove() will remove the first instance of an element
>>> my_list.remove(1)
>>> my_list


# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# remove() will remove the first instance of an element
>>> my_list.remove(1)
>>> my_list
[2, 3, 4, 5, 1]


# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# remove() will remove the first instance of an element
>>> my_list.remove(1)
>>> my_list
[2, 3, 4, 5, 1]

>>> my_list.remove(3)
>>> my_list

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# remove() will remove the first instance of an element
>>> my_list.remove(1)
>>> my_list
[2, 3, 4, 5, 1]

>>> my_list.remove(3)
>>> my_list
[2, 4, 5, 1]

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# remove() will remove the first instance of an element
>>> my_list.remove(1)
>>> my_list
[2, 3, 4, 5, 1]

>>> my_list.remove(3)
>>> my_list
[2, 4, 5, 1]

>>> my_list.remove(1)
>>> my_list

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# remove() will remove the first instance of an element
>>> my_list.remove(1)
>>> my_list
[2, 3, 4, 5, 1]

>>> my_list.remove(3)
>>> my_list
[2, 4, 5, 1]

>>> my_list.remove(1)
>>> my_list
[2, 4, 5]

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]


# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too
# but it also returns the element removed
>>> my_list.pop(1)

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too
# but it also returns the element removed
>>> my_list.pop(1)
2

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too
# but it also returns the element removed
>>> my_list.pop(1)
2
>>> my_list

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too
# but it also returns the element removed
>>> my_list.pop(1)
2
>>> my_list
[1, 3, 4, 5, 1]


# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too
# but it also returns the element removed
>>> my_list.pop(1)
2
>>> my_list
[1, 3, 4, 5, 1]

# calling pop() with no parameter is the equivalent of pop(-1)
>>> my_list.pop()

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too
# but it also returns the element removed
>>> my_list.pop(1)
2
>>> my_list
[1, 3, 4, 5, 1]

# calling pop() with no parameter is the equivalent of pop(-1)
>>> my_list.pop()
1

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too
# but it also returns the element removed
>>> my_list.pop(1)
2
>>> my_list
[1, 3, 4, 5, 1]

# calling pop() with no parameter is the equivalent of pop(-1)
>>> my_list.pop()
1
>>> my_list

# You can also delete elements from a list
>>> my_list = [1, 2, 3, 4, 5, 1]

# pop() removes elements by index too
# but it also returns the element removed
>>> my_list.pop(1)
2
>>> my_list
[1, 3, 4, 5, 1]

# calling pop() with no parameter is the equivalent of pop(-1)
>>> my_list.pop()
1
>>> my_list
[1, 3, 4, 5]

List Iteration

We can also iterate over the elements of a List



# We can use len() to get the size of a list
>>> my_list = [1, 4, 5, 7]


# We can use len() to get the size of a list
>>> my_list = [1, 4, 5, 7]

>>> len(my_list)

# We can use len() to get the size of a list
>>> my_list = [1, 4, 5, 7]

>>> len(my_list)
4


# We can use len() to get the size of a list
>>> my_list = [1, 4, 5, 7]

>>> len(my_list)
4

# We can use range() to generate consecutive numbers
>>> range(3)

# We can use len() to get the size of a list
>>> my_list = [1, 4, 5, 7]

>>> len(my_list)
4

# We can use range() to generate consecutive numbers
>>> range(3)

# In python 2 this would return a list from 0 to 2
[0, 1, 2]

# We can use len() to get the size of a list
>>> my_list = [1, 4, 5, 7]

>>> len(my_list)
4

# We can use range() to generate consecutive numbers
>>> range(3)

# In python 2 this would return a list from 0 to 2
[0, 1, 2]

# In python 3 it's a bit different

# We can use len() to get the size of a list
>>> my_list = [1, 4, 5, 7]

>>> len(my_list)
4

# We can use range() to generate consecutive numbers
>>> range(3)

# In python 2 this would return a list from 0 to 2
[0, 1, 2]

# In python 3 it's a bit different
# Don't worry too much about the difference for now

# We can use len() to get the size of a list
>>> my_list = [1, 4, 5, 7]

>>> len(my_list)
4

# We can use range() to generate consecutive numbers
>>> range(3)

# In python 2 this would return a list from 0 to 2
[0, 1, 2]

# In python 3 it's a bit different
# Don't worry too much about the difference for now

# Just know that both len() and range() are extremely useful 
# when iterating over the elements of a list


# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# One is by directly iterating over the elements

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# One is by directly iterating over the elements
>>> for n in my_list:
>>>    print(n)

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# One is by directly iterating over the elements
>>> for n in my_list:
>>>    print(n)
2
4
6

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# One is by directly iterating over the elements
>>> for n in my_list:
>>>    print(n)
2
4
6

# We can name our iterating variable whatever we want

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# One is by directly iterating over the elements
>>> for n in my_list:
>>>    print(n)
2
4
6

# We can name our iterating variable whatever we want
>>> for foo in my_list:
>>>    print(foo)

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# One is by directly iterating over the elements
>>> for n in my_list:
>>>    print(n)
2
4
6

# We can name our iterating variable whatever we want
>>> for foo in my_list:
>>>    print(foo)
2
4
6

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]


# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# The other is by iterating over the indices


# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# The other is by iterating over the indices
# We can get the indices like so..
>>> for i in range(len(my_list)):
>>>    print(i)

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# The other is by iterating over the indices
# We can get the indices like so..
>>> for i in range(len(my_list)):
>>>    print(i)
0
1
2


# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# The other is by iterating over the indices
# We can get the indices like so..
>>> for i in range(len(my_list)):
>>>    print(i)
0
1
2

# And get the actual elements of my_list like so..
>>> for i in range(len(my_list)):
>>>    print(my_list[i])

# There are two main ways to iterate over a list
>>> my_list = [2, 4, 6]

# The other is by iterating over the indices
# We can get the indices like so..
>>> for i in range(len(my_list)):
>>>    print(i)
0
1
2

# And get the actual elements of my_list like so..
>>> for i in range(len(my_list)):
>>>    print(my_list[i])
2
4
6

Useful Functions

Max Min Sum

max( ), min( ) and sum( ) are some common list functions

 

# These are pretty self explanatory
>>> my_list = [2, 4, 7, 9]


# These are pretty self explanatory
>>> my_list = [2, 4, 7, 9]

>>> max(my_list)

# These are pretty self explanatory
>>> my_list = [2, 4, 7, 9]

>>> max(my_list)
9

# These are pretty self explanatory
>>> my_list = [2, 4, 7, 9]

>>> max(my_list)
9

>>> min(my_list)

# These are pretty self explanatory
>>> my_list = [2, 4, 7, 9]

>>> max(my_list)
9

>>> min(my_list)
2

# These are pretty self explanatory
>>> my_list = [2, 4, 7, 9]

>>> max(my_list)
9

>>> min(my_list)
2

>>> sum(my_list)

# These are pretty self explanatory
>>> my_list = [2, 4, 7, 9]

>>> max(my_list)
9

>>> min(my_list)
2

>>> sum(my_list)
22

Slicing

There are instances when you only want part of a list

For those scenarios you can 'slice' a list

 

# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]


# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]

>>> my_list[2:]

# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]

>>> my_list[2:]
[6, 8, 10, 12]


# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]

>>> my_list[2:]
[6, 8, 10, 12]

>>> my_list[:3]

# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]

>>> my_list[2:]
[6, 8, 10, 12]

>>> my_list[:3]
[2, 4, 6]


# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]

>>> my_list[2:]
[6, 8, 10, 12]

>>> my_list[:3]
[2, 4, 6]

>>> my_list[-2:]

# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]

>>> my_list[2:]
[6, 8, 10, 12]

>>> my_list[:3]
[2, 4, 6]

>>> my_list[-2:]
[10, 12]


# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]

>>> my_list[2:]
[6, 8, 10, 12]

>>> my_list[:3]
[2, 4, 6]

>>> my_list[-2:]
[10, 12]

>>> my_list[2:4]

# Slicing is just getting a portion of a list
# from a specific index onward
>>> my_list = [2, 4, 6, 8, 10, 12]

>>> my_list[2:]
[6, 8, 10, 12]

>>> my_list[:3]
[2, 4, 6]

>>> my_list[-2:]
[10, 12]

>>> my_list[2:4]
[6, 8]

Map Operations

Previously we saw that for-loops can help us apply an operation to all elements of a list  

For simple operations there are more concise ways of writing this

 

# Say we want to convert each element of our list to 
# the square of itself
>>> my_list = [1, 2, 3, 4]

# Say we want to convert each element of our list to 
# the square of itself
>>> my_list = [1, 2, 3, 4]

# Previously we might have done it like so..
>>> for i in range(len(my_list)):
>>>    my_list[i] = my_list[i]**2

# Say we want to convert each element of our list to 
# the square of itself
>>> my_list = [1, 2, 3, 4]

# Previously we might have done it like so..
>>> for i in range(len(my_list)):
>>>    my_list[i] = my_list[i]**2
>>> my_list
[1, 4, 9, 16]

# Say we want to convert each element of our list to 
# the square of itself
>>> my_list = [1, 2, 3, 4]

# Previously we might have done it like so..
>>> for i in range(len(my_list)):
>>>    my_list[i] = my_list[i]**2
>>> my_list
[1, 4, 9, 16]

# We can actually represent the same logic in a single line
>>> my_list = [1, 2, 3, 4]

# Say we want to convert each element of our list to 
# the square of itself
>>> my_list = [1, 2, 3, 4]

# Previously we might have done it like so..
>>> for i in range(len(my_list)):
>>>    my_list[i] = my_list[i]**2
>>> my_list
[1, 4, 9, 16]

# We can actually represent the same logic in a single line
>>> my_list = [1, 2, 3, 4]

>>> my_list = [n**2 for n in my_list]

# Say we want to convert each element of our list to 
# the square of itself
>>> my_list = [1, 2, 3, 4]

# Previously we might have done it like so..
>>> for i in range(len(my_list)):
>>>    my_list[i] = my_list[i]**2
>>> my_list
[1, 4, 9, 16]

# We can actually represent the same logic in a single line
>>> my_list = [1, 2, 3, 4]

>>> my_list = [n**2 for n in my_list]
>>> my_list
[1, 4, 9, 16]

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

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

# Again we have freedom of choice with variable names
>>> my_list2 = [bubba**2 for bubba in my_list]
>>> my_list2


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

# Again we have freedom of choice with variable names
>>> my_list2 = [bubba**2 for bubba in my_list]
>>> my_list2
[1, 4, 9, 16]

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

# Again we have freedom of choice with variable names
>>> my_list2 = [bubba**2 for bubba in my_list]
>>> my_list2
[1, 4, 9, 16]

# The resulting list does not necessarily need to be 
# assigned to a variable
>>> [batman + 1 for batman in my_list]

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

# Again we have freedom of choice with variable names
>>> my_list2 = [bubba**2 for bubba in my_list]
>>> my_list2
[1, 4, 9, 16]

# The resulting list does not necessarily need to be 
# assigned to a variable
>>> [batman + 1 for batman in my_list]
[2, 3, 4, 5]

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

# Again we have freedom of choice with variable names
>>> my_list2 = [bubba**2 for bubba in my_list]
>>> my_list2
[1, 4, 9, 16]

# The resulting list does not necessarily need to be 
# assigned to a variable
>>> [batman + 1 for batman in my_list]
[2, 3, 4, 5]

>>> [asdf / 2 for asdf in my_list]

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

# Again we have freedom of choice with variable names
>>> my_list2 = [bubba**2 for bubba in my_list]
>>> my_list2
[1, 4, 9, 16]

# The resulting list does not necessarily need to be 
# assigned to a variable
>>> [batman + 1 for batman in my_list]
[2, 3, 4, 5]

>>> [asdf / 2 for asdf in my_list]
[0.5, 1.0, 1.5, 2.0]

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

# Again we have freedom of choice with variable names
>>> my_list2 = [bubba**2 for bubba in my_list]
>>> my_list2
[1, 4, 9, 16]

# The resulting list does not necessarily need to be 
# assigned to a variable
>>> [batman + 1 for batman in my_list]
[2, 3, 4, 5]

>>> [asdf / 2 for asdf in my_list]
[0.5, 1.0, 1.5, 2.0]

>>> my_list

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

# Again we have freedom of choice with variable names
>>> my_list2 = [bubba**2 for bubba in my_list]
>>> my_list2
[1, 4, 9, 16]

# The resulting list does not necessarily need to be 
# assigned to a variable
>>> [batman + 1 for batman in my_list]
[2, 3, 4, 5]

>>> [asdf / 2 for asdf in my_list]
[0.5, 1.0, 1.5, 2.0]

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


Complexity

Analyzing Complexity

In the study of Computer Science, we attempt to measure how resource-intensive an algorithm is

In most cases the two primary resources-of-interest are the storage-space and execution-time required by the algorithm

These values are typically represent in terms of the size of the data on which we are executing the algorithm

Let's first view some examples of run-time complexity

 

# Observe the algorithm below
for x in my_list:
    print(x)

# Observe the algorithm below
for x in my_list:
    print(x)

# It takes in a List (dataset) of arbitrary size
# and prints each member

# Observe the algorithm below
for x in my_list:
    print(x)

# It takes in a List (dataset) of arbitrary size
# and prints each member

# Let's define n := len(my_list) 

# Observe the algorithm below
for x in my_list:
    print(x)

# It takes in a List (dataset) of arbitrary size
# and prints each member

# Let's define n := len(my_list) 
# By analysis we see the print statement executes 'n' times
 

# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)




# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# This one's trickier so let's use an example case
my_list = [0, 1]


# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# This one's trickier so let's use an example case
my_list = [0, 1]

# Upon each execution of the inner for-loop 
# the value outputted will be evaluated as follows



# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# This one's trickier so let's use an example case
my_list = [0, 1]

# Upon each execution of the inner for-loop 
# the value outputted will be evaluated as follows
0    <--    (0 + 0)

# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# This one's trickier so let's use an example case
my_list = [0, 1]

# Upon each execution of the inner for-loop 
# the value outputted will be evaluated as follows
0    <--    (0 + 0)
1    <--    (0 + 1)

# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# This one's trickier so let's use an example case
my_list = [0, 1]

# Upon each execution of the inner for-loop 
# the value outputted will be evaluated as follows
0    <--    (0 + 0)
1    <--    (0 + 1)
1    <--    (1 + 0)

# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# This one's trickier so let's use an example case
my_list = [0, 1]

# Upon each execution of the inner for-loop 
# the value outputted will be evaluated as follows
0    <--    (0 + 0)
1    <--    (0 + 1)
1    <--    (1 + 0)
2    <--    (1 + 1)


# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# For each element we iterate through every
# element of my_list to print a sum of all pairs

# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# For each element we iterate through every
# element of my_list to print a sum of all pairs

# so if my_list had a size of 3 we would execute 
# the inner print statement 9 times

# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# For each element we iterate through every
# element of my_list to print a sum of all pairs

# so if my_list had a size of 3 we would execute 
# the inner print statement 9 times

# if my_list had a size of 11 we would execute 
# the inner print statement 121 times

# How about this one?
for x in my_list:
    for y in my_list:
        print(x + y)

# For each element we iterate through every
# element of my_list to print a sum of all pairs

# so if my_list had a size of 3 we would execute 
# the inner print statement 9 times

# if my_list had a size of 11 we would execute 
# the inner print statement 121 times

# So the runtime complexity of this script can be
# represented as n^2, where n is the size of our list
 

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# What would the run-time complexity be here?

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# What would the run-time complexity be here?

# Well that depends..

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# What would the run-time complexity be here?

# Well that depends..
# What do we count as a time-consuming execution?

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# What would the run-time complexity be here?

# Well that depends..
# What do we count as a time-consuming execution?

# We have two types of operations to consider:

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# What would the run-time complexity be here?

# Well that depends..
# What do we count as a time-consuming execution?

# We have two types of operations to consider:
# print statements and addition operators

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# Let's say, for the sake of example, that on our computer
# addition operations take 0.1 seconds to execute but print 
# statements take 30.0 seconds to execute

# Inner executions aren't restricted to a single command
for x in my_list:
    for y in my_list:
        print(x)
        print(y)
        z = x + y
        print(z)

# Let's say, for the sake of example, that on our computer
# addition operations take 0.1 seconds to execute but print 
# statements take 30.0 seconds to execute

# In this scenario, we would likely ignore the addition
# and, instead, establish our run-time complexity as 3n^2
# since the inner code-block must execute n^2 times and 
# each execution requires 3 significant 'operations'
 

# Now let's try a trickier example.. Like last time, 
# let's count only print-statements as significant operations
for x in my_list:
    print(x)
    for y in my_list:
        print(x + y)
        print(x - y)

# Now let's try a trickier example.. Like last time, 
# let's count only print-statements as significant operations
for x in my_list:
    print(x)
    for y in my_list:
        print(x + y)
        print(x - y)

# Here we print twice for each execution of the inner loop
# but we also print once per execution of the outer loop

# Now let's try a trickier example.. Like last time, 
# let's count only print-statements as significant operations
for x in my_list:
    print(x)
    for y in my_list:
        print(x + y)
        print(x - y)

# Here we print twice for each execution of the inner loop
# but we also print once per execution of the outer loop

# Our run-time complexity will be (2n^2 + n)

# Now let's try a trickier example.. Like last time, 
# let's count only print-statements as significant operations
for x in my_list:
    print(x)
    for y in my_list:
        print(x + y)
        print(x - y)

# Here we print twice for each execution of the inner loop
# but we also print once per execution of the outer loop

# Our run-time complexity will be (2n^2 + n)
# Can you see why?
 

# Okay one last example

# Okay one last example
# Assume the same conditions assumed previously 
for x in my_list:
    print(x)
    print(y)
    for y in my_list:
        z = x + y

# Okay one last example
# Assume the same conditions assumed previously 
for x in my_list:
    print(x)
    print(y)
    for y in my_list:
        z = x + y

# Well, we defined a unit of time-complexity to be the
# number of print-statements, right?

# Okay one last example
# Assume the same conditions assumed previously 
for x in my_list:
    print(x)
    print(y)
    for y in my_list:
        z = x + y

# Well, we defined a unit of time-complexity to be the
# number of print-statements, right?

# So our complexity is just (2n) since the inner loop 
# requires no 'execution time' by our definition

Big-O Notation

When evaluating complexity for a particular algorithm we care about the largest order of 'n' (n^2, n, log(n), etc)

Lower-order terms are typically ignored

e.g.

n^2 + 20n = ~ (n^2)

n^3 - 200n^2 + 5n = ~ (n^3)

Why?

Because any lower order terms are eventually eclipsed by their higher-order counterparts

 

# Let's compare two algorithms, given input m of size n



# Let's compare two algorithms, given input m of size n

# func1(m) runs in 10n time
# func2(m) runs in n^2 time

# Let's compare two algorithms, given input m of size n

# func1(m) runs in 10n time
# func2(m) runs in n^2 time

# We would see the following run-time behavior..
   Input Size    func1() runtime    func2() runtime    

# Let's compare two algorithms, given input m of size n

# func1(m) runs in 10n time
# func2(m) runs in n^2 time

# We would see the following run-time behavior..
   Input Size    func1() runtime    func2() runtime    
    1             10                 1

# Let's compare two algorithms, given input m of size n

# func1(m) runs in 10n time
# func2(m) runs in n^2 time

# We would see the following run-time behavior..
   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    2             20                 4

# Let's compare two algorithms, given input m of size n

# func1(m) runs in 10n time
# func2(m) runs in n^2 time

# We would see the following run-time behavior..
   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    2             20                 4
    5             50                 25

# Let's compare two algorithms, given input m of size n

# func1(m) runs in 10n time
# func2(m) runs in n^2 time

# We would see the following run-time behavior..
   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    2             20                 4
    5             50                 25
    10            100                100

# Let's compare two algorithms, given input m of size n

# func1(m) runs in 10n time
# func2(m) runs in n^2 time

# We would see the following run-time behavior..
   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    2             20                 4
    5             50                 25
    10            100                100
    100           1e3                1e4

# Let's compare two algorithms, given input m of size n

# func1(m) runs in 10n time
# func2(m) runs in n^2 time

# We would see the following run-time behavior..
   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    2             20                 4
    5             50                 25
    10            100                100
    100           1e3                1e4
    1000          1e4                1e6
    100,000       1e6                1e10


   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    10            100                100
    100,000       1e6                1e10

   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    10            100                100
    100,000       1e6                1e10

# func1 begins with a higher runtime than func2

   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    10            100                100
    100,000       1e6                1e10

# func1 begins with a higher runtime than func2
# but as we increase the input size, the growth in
# runtime of func2 vastly outpaces that of func1

   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    10            100                100
    100,000       1e6                1e10

# func1 begins with a higher runtime than func2
# but as we increase the input size, the growth in
# runtime of func2 vastly outpaces that of func1

# With an input size of 100,000 func2 takes 10,000 
# times as long as func1 to execute!

   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    10            100                100
    100,000       1e6                1e10

# func1 begins with a higher runtime than func2
# but as we increase the input size, the growth in
# runtime of func2 vastly outpaces that of func1

# With an input size of 100,000 func2 takes 10,000 
# times as long as func1 to execute!

# This is the difference between 
# a program that runs for 1 minute

   Input Size    func1() runtime    func2() runtime    
    1             10                 1
    10            100                100
    100,000       1e6                1e10

# func1 begins with a higher runtime than func2
# but as we increase the input size, the growth in
# runtime of func2 vastly outpaces that of func1

# With an input size of 100,000 func2 takes 10,000 
# times as long as func1 to execute!

# This is the difference between 
# a program that runs for 1 minute
# and a program that runs for 1 week
 

# Can you see why the runtime complexity is so important?

# Can you see why the runtime complexity is so important?

# Especially for time-sensitive applications such as processing
# sensor-data to evaluate the state of a dynamic environment, 

# Can you see why the runtime complexity is so important?

# Especially for time-sensitive applications such as processing
# sensor-data to evaluate the state of a dynamic environment, 
# the runtime complexity of the algorithm is critical to 
# defining its performance

# Can you see why the runtime complexity is so important?

# Especially for time-sensitive applications such as processing
# sensor-data to evaluate the state of a dynamic environment, 
# the runtime complexity of the algorithm is critical to 
# defining its performance

# For any dataset of non-trivial size, the largest-order term
# will dictate the speed of execution

# Can you see why the runtime complexity is so important?

# Especially for time-sensitive applications such as processing
# sensor-data to evaluate the state of a dynamic environment, 
# the runtime complexity of the algorithm is critical to 
# defining its performance

# For any dataset of non-trivial size, the largest-order term
# will dictate the speed of execution

# In practice we will typically focus on the order of the
# largest-order term, ignoring any coefficients

# Can you see why the runtime complexity is so important?

# Especially for time-sensitive applications such as processing
# sensor-data to evaluate the state of a dynamic environment, 
# the runtime complexity of the algorithm is critical to 
# defining its performance

# For any dataset of non-trivial size, the largest-order term
# will dictate the speed of execution

# In practice we will typically focus on the order of the
# largest-order term, ignoring any coefficients

# e.g.     3n^2            -->    ~ n^2

# Can you see why the runtime complexity is so important?

# Especially for time-sensitive applications such as processing
# sensor-data to evaluate the state of a dynamic environment, 
# the runtime complexity of the algorithm is critical to 
# defining its performance

# For any dataset of non-trivial size, the largest-order term
# will dictate the speed of execution

# In practice we will typically focus on the order of the
# largest-order term, ignoring any coefficients

# e.g.     3n^2            -->    ~ n^2
#          20n^3 + 500n    -->    ~ n^3

# Can you see why the runtime complexity is so important?

# Especially for time-sensitive applications such as processing
# sensor-data to evaluate the state of a dynamic environment, 
# the runtime complexity of the algorithm is critical to 
# defining its performance

# For any dataset of non-trivial size, the largest-order term
# will dictate the speed of execution

# In practice we will typically focus on the order of the
# largest-order term, ignoring any coefficients

# e.g.     3n^2            -->    ~ n^2
#          20n^3 + 500n    -->    ~ n^3
#          4n^2 + 20n^4    -->    ~ n^4
 

# Now that we've established what we're looking for
# when analyzing complexity we need to define a 
# notation to easily represent the concept

# Now that we've established what we're looking for
# when analyzing complexity we need to define a 
# notation to easily represent the concept

# The CS (Computer Science) community has three primary
# notations to represent the complexity of an algorithm:

# Now that we've established what we're looking for
# when analyzing complexity we need to define a 
# notation to easily represent the concept

# The CS (Computer Science) community has three primary
# notations to represent the complexity of an algorithm:
# O Ω and ϴ

# Now that we've established what we're looking for
# when analyzing complexity we need to define a 
# notation to easily represent the concept

# The CS (Computer Science) community has three primary
# notations to represent the complexity of an algorithm:
# O Ω and ϴ


# O is an upper bound on the order of the highest term

# Now that we've established what we're looking for
# when analyzing complexity we need to define a 
# notation to easily represent the concept

# The CS (Computer Science) community has three primary
# notations to represent the complexity of an algorithm:
# O Ω and ϴ


# O is an upper bound on the order of the highest term

# Ω is a lower bound on the order of the highest term 

# Now that we've established what we're looking for
# when analyzing complexity we need to define a 
# notation to easily represent the concept

# The CS (Computer Science) community has three primary
# notations to represent the complexity of an algorithm:
# O Ω and ϴ


# O is an upper bound on the order of the highest term

# Ω is a lower bound on the order of the highest term 

# ϴ is an exact order of the highest term
 

# O is an upper bound on the order of the highest term
 

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2
func1 is in big-O of n^2  

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2
func1 is in big-O of n^2  
func1 executes within order of n^2

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2
func1 is in big-O of n^2  
func1 executes within order of n^2

# Since O is just an upper bound on the highest-order term,
# the following would also be true:

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2
func1 is in big-O of n^2  
func1 executes within order of n^2

# Since O is just an upper bound on the highest-order term,
# the following would also be true:
O(func1) = O(n^2)

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2
func1 is in big-O of n^2  
func1 executes within order of n^2

# Since O is just an upper bound on the highest-order term,
# the following would also be true:
O(func1) = O(n^2)
O(func1) = n^3

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2
func1 is in big-O of n^2  
func1 executes within order of n^2

# Since O is just an upper bound on the highest-order term,
# the following would also be true:
O(func1) = O(n^2)
O(func1) = n^3
O(func1) = n^4

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2
func1 is in big-O of n^2  
func1 executes within order of n^2

# Since O is just an upper bound on the highest-order term,
# the following would also be true:
O(func1) = O(n^2)
O(func1) = n^3
O(func1) = n^4

# The following would not be true:

# O is an upper bound on the order of the highest term
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
O(func1) = n^2
func1 is in big-O of n^2  
func1 executes within order of n^2

# Since O is just an upper bound on the highest-order term,
# the following would also be true:
O(func1) = O(n^2)
O(func1) = n^3
O(func1) = n^4

# The following would not be true:
O(func1) = n
 
 

# Ω is a lower bound on the order of the highest term 

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2
func1 is in big-Omega of n^2 

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2
func1 is in big-Omega of n^2 
func1 runs at minimum in order of n^2 

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2
func1 is in big-Omega of n^2 
func1 runs at minimum in order of n^2 

# Since Ω is just a lower bound on the highest order term,
# the following would also be true:

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2
func1 is in big-Omega of n^2 
func1 runs at minimum in order of n^2 

# Since Ω is just a lower bound on the highest order term,
# the following would also be true:
Ω(func1) = Ω(n^2)

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2
func1 is in big-Omega of n^2 
func1 runs at minimum in order of n^2 

# Since Ω is just a lower bound on the highest order term,
# the following would also be true:
Ω(func1) = Ω(n^2)
Ω(func1) = n

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2
func1 is in big-Omega of n^2 
func1 runs at minimum in order of n^2 

# Since Ω is just a lower bound on the highest order term,
# the following would also be true:
Ω(func1) = Ω(n^2)
Ω(func1) = n
Ω(func1) = 1

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2
func1 is in big-Omega of n^2 
func1 runs at minimum in order of n^2 

# Since Ω is just a lower bound on the highest order term,
# the following would also be true:
Ω(func1) = Ω(n^2)
Ω(func1) = n
Ω(func1) = 1

# The following would not be true:

# Ω is a lower bound on the order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
Ω(func1) = n^2
func1 is in big-Omega of n^2 
func1 runs at minimum in order of n^2 

# Since Ω is just a lower bound on the highest order term,
# the following would also be true:
Ω(func1) = Ω(n^2)
Ω(func1) = n
Ω(func1) = 1

# The following would not be true:
Ω(func1) = n^3
 
 

# ϴ is an exact order of the highest term 

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2
func1 is in Theta of n^2

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2
func1 is in Theta of n^2
func1 runs in order of n^2

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2
func1 is in Theta of n^2
func1 runs in order of n^2

# Since ϴ is a strict evaluation of the highest order term, 
# the following is also true:

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2
func1 is in Theta of n^2
func1 runs in order of n^2

# Since ϴ is a strict evaluation of the highest order term, 
# the following is also true:
ϴ(func1) = ϴ(n^2)

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2
func1 is in Theta of n^2
func1 runs in order of n^2

# Since ϴ is a strict evaluation of the highest order term, 
# the following is also true:
ϴ(func1) = ϴ(n^2)

# The following would not be true:

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2
func1 is in Theta of n^2
func1 runs in order of n^2

# Since ϴ is a strict evaluation of the highest order term, 
# the following is also true:
ϴ(func1) = ϴ(n^2)

# The following would not be true:
ϴ(func1) = n

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2
func1 is in Theta of n^2
func1 runs in order of n^2

# Since ϴ is a strict evaluation of the highest order term, 
# the following is also true:
ϴ(func1) = ϴ(n^2)

# The following would not be true:
ϴ(func1) = n
ϴ(func1) = n^3

# ϴ is an exact order of the highest term 
# Here are some examples on how it's used:

# Let's say func1() runs in (2n^2 + n) time, in respect to 
# input size n. The following are equivalent statements:
ϴ(func1) = n^2
func1 is in Theta of n^2
func1 runs in order of n^2

# Since ϴ is a strict evaluation of the highest order term, 
# the following is also true:
ϴ(func1) = ϴ(n^2)

# The following would not be true:
ϴ(func1) = n
ϴ(func1) = n^3
ϴ(func1) = n^4

Time and Space

So we've been focusing on time (runtime) complexity so far

Alternatively, we could measure the amount of storage space an algorithm requires in respect to the input size

This is called space (storage) complexity

 

# Say we have the following algorithm using the same input
for elem in my_list:
    multiples.append([elem * i for i in my_list]

# Say we have the following algorithm using the same input
for elem in my_list:
    multiples.append([elem * i for i in my_list]

# We see that for each element of my_list we are appending
# a list of that element times each of the other elements
# to our other list (multiples)

# Say we have the following algorithm using the same input
for elem in my_list:
    multiples.append([elem * i for i in my_list]

# We see that for each element of my_list we are appending
# a list of that element times each of the other elements
# to our other list (multiples)

# We can see our storage complexity, in respect to input-size 
# 'n' of my_list, is n^2 since each of the 'n' elements of 
#'multiples' will be of size 'n'


 

# Let's check the output with an example list
>>> my_list = [1, 2, 3]
>>> multiples = []
>>> for elem in my_list:
>>>    multiples.append([elem * i for i in my_list]

# Let's check the output with an example list
>>> my_list = [1, 2, 3]
>>> multiples = []
>>> for elem in my_list:
>>>    multiples.append([elem * i for i in my_list]
>>> multiples
[[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# Let's check the output with an example list
>>> my_list = [1, 2, 3]
>>> multiples = []
>>> for elem in my_list:
>>>    multiples.append([elem * i for i in my_list]
>>> multiples
[[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# We can check the dimensions of our "list" using the
# array class from the numpy module
>>> import numpy

# Let's check the output with an example list
>>> my_list = [1, 2, 3]
>>> multiples = []
>>> for elem in my_list:
>>>    multiples.append([elem * i for i in my_list]
>>> multiples
[[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# We can check the dimensions of our "list" using the
# array class from the numpy module
>>> import numpy
# convert list to array
>>> a = numpy.array(multiples)

# Let's check the output with an example list
>>> my_list = [1, 2, 3]
>>> multiples = []
>>> for elem in my_list:
>>>    multiples.append([elem * i for i in my_list]
>>> multiples
[[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# We can check the dimensions of our "list" using the
# array class from the numpy module
>>> import numpy
# convert list to array
>>> a = numpy.array(multiples)
# call shape attribute of the array object
>>> a.shape                       

# Let's check the output with an example list
>>> my_list = [1, 2, 3]
>>> multiples = []
>>> for elem in my_list:
>>>    multiples.append([elem * i for i in my_list]
>>> multiples
[[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# We can check the dimensions of our "list" using the
# array class from the numpy module
>>> import numpy
# convert list to array
>>> a = numpy.array(multiples)
# call shape attribute of the array object
>>> a.shape                       
(3, 3)
# Side Note
# Side Note
# You can experiment with Python 3 in the same way I do here
>>> a = []
# Side Note
# You can experiment with Python 3 in the same way I do here
>>> a = []
>>> a
# Side Note
# You can experiment with Python 3 in the same way I do here
>>> a = []
>>> a
[]
# Side Note
# You can experiment with Python 3 in the same way I do here
>>> a = []
>>> a
[]

# by simply typing 'python3' on the Pi and Mac terminals
# to activate the Python Shell
# Side Note
# You can experiment with Python 3 in the same way I do here
>>> a = []
>>> a
[]

# by simply typing 'python3' on the Pi and Mac terminals
# to activate the Python Shell

# Try it out yourself!
# Side Note
# You can experiment with Python 3 in the same way I do here
>>> a = []
>>> a
[]

# by simply typing 'python3' on the Pi and Mac terminals
# to activate the Python Shell

# Try it out yourself!

# Type 'exit()' to exit out of the shell

References

Lists

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nec metus justo. Aliquam erat volutpat.

Useful Functions

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nec metus justo. Aliquam erat volutpat.

Complexity

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi nec metus justo. Aliquam erat volutpat.

Lec 2 - Basic Concepts

By Brian J Lee

Lec 2 - Basic Concepts

  • 473