Beruflich Dokumente
Kultur Dokumente
Computer Programming
Final Exam Study Guide
Types
different kinds of information in Python are called types
every value has a type, which determines behaviour of values
two kinds are called floats and integers
Floats
short for floating point number
the floating point refers to the decimal
one slash / is used for floating point division, which produces a float result
e.g 10/3 = 3.33333333335 ->a close but inexact answer
Integers
numbers with no decimal points
use two slashes // for integer division, which produces an integer result
e.g: typing 9//3 will produce an integer result of 3
the results are not rounded but truncated
e.g 7/4 = 1.75 but 7//4 = 1
How to write comments
Use a number sign # to write comments in Wing IDE 101
everything that follows will not be executed by the program
Modulo Division
use a percentage sign % for modulo division, which returns a remainder
e.g 10 % 3 = 1
15 % 4 = 3
8 % 2 =0
5 %2=1
5 %3=2
Order of Precedence (from highest to lowest) :
Exponentiation (**)
Negation(-)
multiplication, integer, float, mod division ( * , / , // , %)
Work left to right
addition and subtraction ( + , - )
Use parentheses to override this, and to make equations clearer
Syntax Errors
typing 2 ** 2, 2 ** 2 both will produce the correct result
typing 2 * * 2 will produce a syntax error
Python assumes that anything directly following a * should be a value, unless its **
which means exponentiation
other syntax errors include:
3 +
-> unfinished expressions
4 + 5 ) -> extra parenthese
5/0
-> divide by 0 error
(2 + 4 - 1 -> gives you ellipses (...) because its waiting for the closing
parenthese
Absolute value
the function abs() gives you the absolute value of a number
e.g abs(-3.7) returns the value 3.7
Typing in more than one value results in error
Using negative numbers allows you to round to the left of the decimal
round(1234.5678,-2)
returns a value of 1200.0
round(4.5688, -2)
returns a value of 0.0
Variables
Form of a variable : <<variable>> = <<expression>>
variables are places to store information in a computers memory
giving variables a name allows you to refer back to it
assignment variables use the = sign
expressions on the right side of the = sign are evaluated first
the left hand side is set to refer to the value
memory addresses are assigned by Python, and we do not have access to them
e.g the expression x = 7 means:
x gets 7
x refers to value 7
x contains memory address id1
memory address id1 is stored in variable x
memory model diagram:
id1:int
X
id1
id
15
id1
id2
id2
id2
4
Type Contract
the type contract should be above the examples in the docstring
it provides information about the parameters that are passed in as an argument
and what kind of values are returned
e.g (number) -> int means that a float or an integer may be passed in, and an
integer value is always returned
Header
the header is the beginning portion of the function definition
def <<function_name>> ( <<parameters>> ) :
Description
include a description of what your function does
the description goes after the type contract but before the examples in a
docstring
the parameters should be mentioned in the description
Body
the body is where statements are added
if the print function is used, it should be added before the return statement
the return statement should be the last thing in the body
Example
def hypotenuse(a,b):
Using only print - no values are stored in memory addresses after printing
def a():
print(hello)
s1 = a()
(hello is printed, but s1 has a return value of none)
Using only return -the function calls are assigned to variables, nothing is printed
def b():
return(hello)
s2 = b()
(nothing is printed, but s2 has a return value hello)
Function Reuse
calling one function within another function definition can help simplify programs
e.g writing a function that switches the last name and first name of a person, and
assigning the name with their gender
def format_name(first_name,last_name):
the first function format_name switches first names and last names
the following function calls format_name from within the function definition
def name_and_gender(first_name,last_name,gender):
evaluating expressions using comparison operators return Boolean values True or False
Comparison Operator
Symbol
Example
greater than
>
4>3
True
less than
<
1<5
True
equal to
==
(single = is used for
assignment statements)
4 * 3 == 12
True
7 == 7.0
True
>=
5 >= 3
True
<=
4 <= 12
True
not equal to
!=
5 != 3
True
Logical Operators
Logical operators include not, and, and or
in expressions with logical operators, operands are Boolean values
when using the not operator, expressions that were originally True become False and
vice versa
e.g >>>grade = 90
>>>grade >= 50
True
>>>not(grade >= 50)
False
when using the and operator, both operands must be True in order to evaluate to True,
otherwise the result will be False
e.g >>>grade1 = 95
>>>grade2 = 40
>>>grade3 = 75
>>> grade1 >= 50 and grade2 >=50
False
when using the or operator, only one operand has to be true in order to evaluate to True
e.g >>>grade1 >=50 or grade2 >=50
True
Order of Evaluation
Consider the following statements:
>>>tuesday = true
>>>sunny = true
>>>not tuesday or sunny
True
True is returned because not tuesday or sunny was evaluated as: (not tuesday) or sunny
not was evaluated first, however adding parentheses can change the order of
evaluation:
>>>not (tuesday or sunny)
False
Short Circuit Evaluation
Python does short circuit evaluation, or lazy evaluation
when using the or operator, if the first expression is true, it doesnt matter what the
second one is
e.g >>>early = True
>>>early or 1/0 == 6
True
normally there would be a division by 0 error, but after evaluating early, True is returned
if the order was switched:
>>> 1/0 == 6 or early
->results in error
if the operator and is used, as soon as an expression is false, it doesnt matter what the
other expressions are
e.g >>>4 == 3 and 1/0 =6
False
short circuit evaluation can help prevent division by 0 errors
e.g >>> value != 0 and 10/value == 2
value != 0 prevents division by 0 error
True
>>> True == bool(0)
False
position number
(from right)
letter
position number
(from left)
-7
-6
-5
-4
-3
-2
-1
Slicing
a slice is a substring of a string from a start index up to but not including an end index
e.g >>> first_name[2:7]
chael
>>>first_name[0:3]
Mic
leaving the beginning or end position is default for go to end or beginning of string
e.g >>>>>> first_name[:7]
Michael
>>> first_name[0:]
Michael
Stride
slicing operation has a third parameter which determines the stride in the slice
the stride is the distance between characters
e.g >>>s = computer
>>>s[::2]
'cmue'
in the example above, every other character was produced, starting from the first
character in the string to the last character
use negative strides to work backwards
e.g >>>s[-1:-8:-2]
'rtpo'
>>>s[1:8:2]
'optr'
>>>s[-1:-8:-3]
'ruo'
>>>s[1:8:3]
'our'
CSC 108-Lecture 9-Stride examples and Creating Constants
More Stride Examples
e.g >>>a =jacqueline
>>>a[::-1]
'enileuqcaj'
>>>a[0:10:-1]
Creating Constants
consider function:
>>>def is_teen(age)
(int) -> boool
Return True if age represents a teenager between the ages of 13 and
18
(inclusive)
>>> is_teen(7)
False
>>>is_teen(40)
False
>>>is_teen(15)
True
>>>MAX_TEEN_AGE = 18
>>>def is_teen(age)
(int) -> boool
Return True if age represents a teenager between the ages of 13 and
18
(inclusive)
>>> is_teen(7)
False
>>>is_teen(40)
False
>>>is_teen(15)
True
s = 'HELLO THERE!'
>>>s.lower()
'hello there!'
(all the letters were changed to lowercase letters)
e.g
s = '1425 cats'
>>>s.isdigit()
False
(false was returned because the string is not made up of only digits)
e.g
s = computers
>>>for ch in s:
>>>
print(ch)
Creating a new string with the same letters in the original string:
>>>s_new =
>>>
for ch in s:
>>>s_new = s_new + ch
computers
We can use for loops in functions to perform repetitive tasks, such as checking for number
of vowels in a string
>>>def count_vowels(s):
>>>
num_vowels = 0
>>>
for char in s:
>>>
if char in aeiouAEIOU:
>>>
num_vowels = num_vowels + 1
>>>
return num_vowels
given task is to write a function and check if a string contains only dots or numbers
first we write the doc string and the header of the function definition
>>>def check_string(string):
>>> (str) -> bool
>>>check_string(123.14.5324234)
True
>>>check_string(40 St. George St.)
False
we will first check if this code will work:
>>>for char in string:
>>>
if char == .:
>>>
return True
>>>
elif char.isdigit():
>>>
return True
>>>
else:
>>>
return False
the code above does not work because it will only check the first character, and the
function will exit and not check
we can try adding a flag
>>>def check_string(string):
>>>
flag = True
>>>
for char in string:
>>>
if char == '.':
>>>
flag = True
>>>
elif char.isdigit():
>>>
flag = True
>>>
else:
>>>
return false
the code above does not work because it only returns whether the last character is True
or False
the code above will return false as soon as it detects a character that is not a dot or a
digit
This function returns True if every letter in a string appears in the word smile
>>>def check_string(string):
>>> (str) -> bool
>>> >>>check_string(smile)
>>>
True
>>> >>> check_string(frown)
>>>
False
>>>
>>>
>>>
>>>
>>>
This function returns a string that contains an underscore between every character
>>>def add_underscores(string):
>>> ''' (str) -> str
>>> >>>add_underscores(pizza)
>>>
p_i_z_z_a
>>> >>>add_underscores(cat)
>>>
c_a_t
>>>
'''
>>> new_string = ''
>>> for char in string[ : (len(string) - 1)]:
>>>
new_string = new_string + char + '_'
>>> return new_string + string[-1]
>>>
print(You won!)
>>> else:
>>>
print(Sorry, wrong answer)
since the int function converts guesses to integers, answers such as 11.45 would still
result in You won! being printed
a function that skips characters in a string would look like:
>>>def every_nth_character(string,n):
>>> (str,int) -> str
>>> precondition: n > 0
>>> Return a string that contains every nth character from string, starting at
>>> index 0
>>> >>> every_nth_character(Computer,3)
>>> Cpe
>>>
>>> result =
>>> i = 0
>>> while i < len(string)
>>>
result = result + string[i]
>>>
i=i+n
>>>
return result
another function that returns a string that includes a certain letter occurring a certain
amount of times
>>> def find_letter_n_times(string,letter,n):
>>>
(str,str,int) -> str
>>> precondition: letter occurs at least n times in string
>>> return the smallest substring of string starting from index 0 that
>>> contains n occurrences of the letter
>>> >>>find_letter_n_times(bubble tea,b,3)
>>> bubb
>>>
>>> i = 0
>>> count = 0
>>> while i < len(string) and count < n:
>>>
if string[i] == letter :
>>>
count = count + 1
>>>
i=i+1
>>>
return string[:i]
when one item in a list is changed to a different value, the old item keeps its memory
address
the new value has a new memory address, and the element will point to the new
memory address
e.g: >>>def function(list):
>>>list.append(a)
>>>list.append(b)
>>>list.append(c)
>>>list
[a,b,c]
>>>list[1] = hello
>>> function([1,2])
[1, 'hello', 'a', 'b', 'c']
the digit 2 is replaced with string hello, which has a new memory address
5
it is also possible to do accumulation, which is adding as we iterate
e.g >>>my_list = [2,3,4]
>>>double_list = []
>>>for item in my_list:
>>>
double_list.append(item*2)
>>>double_list
[4,6,8]
we must use range to get indices if we want to change items in a list
e.g >>>my_list = [2,3,4]
>>>for item in my_list:
>>>
item = item * 3
>>>my_list
[2,3,4]
values were not multiplied by 3 because even though items get new
values, their new values got new memory addresses
the operation in the function body does not affect the original items in the
list
when we use range however, we can change items in the list by using the index
e.g >>>for index in range(len(my_list)):
>>> my_list[index] = my_list[index] * 3
>>>my_list
>>>[6,9,12]
the address of A_list did not change even though we added a value to the list
some list methods return none
e.g >>>print(A_list.append(7))
>>>None
Aliasing
aliasing is when two or more variables refer to the same object
e.g >>>list1 = [1,2,3,4]
>>>list2 = list 1
>>>list1[1] = 5
>>>list1
[1,5,3,4]
>>>list2
[1,5,3,4]
when list1 changes, list2 also changes because they both refer to the same list
Parallel Strings
it is possible to use more than one list in a function call
e.g the following function returns a new list in which the new item in the new list is the
sum of items at the corresponding positions of list 1 and 2
the function adds items that are at the same position
e.g list1 = [1,2,3]
list2 = [4,5,6]
index: = 0 1 2
>>>def sum(list1,list2)
>>>
>>> sum([1,2,3],[4,5,6])
>>> [5,7,9]
>>>
>>> sum = []
>>> for i in range(len(list1)):
>>>
sum.append(list1[i] + list2[i])
>>> return sum
the following function returns a string with characters in s in the same original order, but
repeated a certain number of times indicated in the second string
>>>def stretchstring(s,stretchfactors):
(str,list of int) ->str
>>>stretchstring(whats , [2,4,3,2,2])
wwhhhhaaattss
>>>
newstring =
>>>
for i in range(len(s):
>>>
newstring = newstring + s[i] * stretchfactors[i]
>>>
return newstring
differences = []
for i in range(len(numlist1)):
differences.append(abs(numlist1[i] - numlist2[i]))
return max(differences)
Nested Lists
nested lists are lists that contain at least one other list
e.g >>>data = [['a', 'b',c,d], [1,2,3, 4], ["epsilon", "zeta"]]
you can access information in the nested lists by using square brackets to indicate which
indices you want to access
e.g data[2][0][1] gives value p because the first index refers to the position of the nested
list inside the list, so item ["epsilon", "zeta"] are at position 2 in the list
the second number in square brackets ( [0] ) refers to the position of the item inside the
nested list, which is item epsilon
the third number in square brackets [1] refers to the position of the character inside the
item which is p
Nested and Non-Nested Lists
>>>numbers =[0,1,2]
>>>letters = [a,nums,c,d]
>>>letters
[a,[0,1,2],c,d]
>>>numbers = []
>>>for i in range(6):
>>>
letters.append([i])
>>>numbers
[[0],[1],[2],[3],[4],[5]]
the form of the readline approach is: while not at the end of the section of interest,
process the current line, and read the next line of the interesting section
>>>line = f.readline()
>>>for line in f:
>>>print(line)
this code fragment will print every line except the first
>>>for line in f:
>>>
print(line)
>>>
f.readline()
this code fragment will print every second line
>>>print(f.readlines()[0])
this code fragment will print only the first line
the body of the function will result in False being returned when True should be returned,
because cats will be compared with cats\n and they are not equal
we could replace the if statement with: if word in line:
for line in file:
print(line)
if word in line:
return True
return False
this will return more True values than it should because True will return even if you have
part of a word, for example at is in cats
using strip will remove whitespace from the ends, and will return the correct results
for line in file:
print(line)
if line.strip() == word:
return True
return False
Type Dict
dict: Pythons dictionary type
general form of a dictionary:
{key1: value1, key2: value2, key3: value3...keyN: valueN}
each entry in the dictionary has 2 parts which are separated by colons
e.g grades = {A1 : 80, A2 : 90, A3: 100}
A1,A2,A3 are the keys
80,90,100 are the values associated with the keys
to look up values associated with keys, use brackets and enter the key
dictionaries are different than lists because they use keys instead of index numbers
>>>grades[A2]
90
dictionaries to retrieve information can be simpler than using lists because you would
need to use double indices
>>>grades = [['A1' , 80],[ 'A2' , 90],[ 'A3', 100]]
grades[1][1]
90
in order to be able to look up values, the keys must be unique
keys may only appear once, however the same values may appear multiple times
if we try to access a dictionary with a key that does not exist, an error will occur, however
we can check to see if a key is in a dictionary
>>>AF in grades
False
values in keys will also return False
>>>80 in grades
False
>>>len(animal_to_locomotion)
3
we cannot use index the same way as lists to get values from dictionaries
e.g >>>animal_to_locomotion[0]
Traceback (most recent call last):
Python Shell, prompt 71, line 1
builtins.KeyError: 0 -> this tells you that the key was in valid
dictionaries are mutable, similar to lists
we can add key value pairs by using assignment statements
e.g >>>animal_to_locomotion = {'bug': ['crawl'],
'kangaroo': ['hop'],
'butterfly': ['fly']}
>>>animal_to_locomotion[penguin] = [waddle]
>>>animal_to_locomotion
{'butterfly': ['fly'], 'kangaroo': ['hop'], 'penguin': ['waddle'], 'bug': ['crawl']}
we can remove a key by using method del
e.g e.g >>>del animal_to_locomotion[kangaroo] #remove key kangaroo
>>>animal_to_locomotion
{'butterfly': ['fly'], 'penguin': ['waddle'], 'bug': ['crawl']}
if a key does not exist yet, we cannot use method append, because to use append
Python needs to get the key first, which has not been added to dictionary yet
e.g >>>animal_to_locomotion[worm].append(wiggle)
Traceback (most recent call last):
Python Shell, prompt 3, line 1
builtins.KeyError: 'worm'
it is also possible to replace the values of keys
e.g>>>animal_to_locomotion[butterfly] = [flutter]
animal_to_locomotion
{'bug': ['crawl'], 'butterfly': ['flutter'], 'kangaroo': ['hop']}
Keys and values may be different types, however the type of a key must always be
immutable
lists cannot be used as keys, however we can use tuples if we want to use a sequence
as a key
e.g code such as >>>d = {(1,2,3) : banana} is valid
crow : black,}
>>>colour_to_animal = {}
>>>for animal in animal_to_colour:
colour = animal_to_colour[animal]
colour_to_animal[colour] = animal
when we check the new dictionary colour_to_animal, we end up missing some animals
since more than one animal is grey or black, existing entries are replaced when the for
loop is being executed
in order to avoid this problem we should append the animals to the existing list in the
dictionary instead of replacing them
>>>colour_to_animal = {}
>>>for animal in animal_to_colour:
colour = animal_to_colour[animal]
if not(colour in colour_to_animal):
colour_to_animal[colour] = [animal]
else:
colour_to_animal[colour].append[animal]
Building a Dictionary Example
we want to write a function that takes in an ordered lists of shoes worn by runners as
they passed a finish line of a marathon
the function will return a list that maps the shoe companies to a list of the placements
achieved by the runners wearing the shoes
we want the shoe brands to be the keys, and to appear only once
we want to update the keys without overwriting any of the previous values
e.g >>>def build_placements(shoes):
""" (list of str) -> dict of {str: list of int}
Return a dictionary where each key is a company and each value is a
list of placements by people wearing shoes made by that company.
>>> build_placements(['Brooks', 'Asics', 'Asics', 'NB', 'Brooks',
'Nike', 'Asics', 'Adidas', 'Brooks', 'Asics'])
{'Brooks': [1, 5, 9], 'Asics': [2, 3, 7, 10], 'NB': [4], 'Nike': [6], 'Adidas': [8]}
"""
result = {}
for i in range(len(shoes)):
if shoes[i] in result:
result[shoes[i]].append(i+1)
else:
result[shoes[i]] = [i+1]
return result
CSC 108 -Lecture 22-Writing More Complicated Programs
Palindrome Program
we want to write a program that determines whether a string is a palindrome
a palindrome is a string that is read the same backwards and forwards
e.g words like noon and racecar are palindromes
for complicated programs it is easier to write them when we have an Algorithm
an algorithm is a sequence of steps that accomplish a task
sometimes it is possible to have more than one algorithm
there are 3 algorithms that can be used to write this program:
Algorithm 1
step 1: Reverse the string
step 2: Compare the reversed string to the original string
The program:
>>>def reverse(s):
rev =
for ch in s:
rev = ch + rev #reverses the string
return rev
>>>def is_palindrome(s):
(str) -> bool
Return True if and only if s is a palindrome.
>>>is_palindrome(noon)
True
>>>is_palindrome(octopus)
False
The program
i -><- j
noon
we compare the letter values at index i and index j
and we stop when j is less than i
j i
noon
def is_palindrome3(s):
i=0
j = len(s) - 1
while i < j and s[i] == s[j]:
#comparing first character with last, i<j is the stopping position since we #need to
stop when i and j are next to each other, but j is less than i
i=i+1
j = j -1
return j <= i
CSC108-Lecture 23-Doctests
Doctests
a doctest is a python module that allows us to run tests in an automated way
rather than calling example calls in the Python Shell after running programs, it would be
more convenient to run them all at once
to run them all at once we need to import the module
>>>import doctest
>>>doctest.testmod() #to run all tests for this module
if four example calls were all valid, the results should look like:
>>>TestResults(failed = 0, attempted = 4)
The results will return the number of items that had failures, if the results do not match
the expected values
e.g for the function get_divisors, the doctest will return an error
>>>def get_divisors(num, possible_divisors):
(int, list of int) -> list of int
Return a list of the values from possible_divisors that are divisors of num.
>>>get_divisors(8,[1,2,3])
[1,2]
>>>get_divisors(4,[-2,0,2])
[2]
divisors = []
for item in possible_divisors:
if num % item == 0:
divisors.append(item)
return divisors
Since there is a divide by 0 error (second example call), we need to add a new boolean
expression to the condition inside the for loop
>>>def get_divisors(num, possible_divisors):
(int, list of int) -> list of int
Return a list of the values from possible_divisors that are divisors of num.
>>>get_divisors(8,[1,2,3])
[1,2]
>>>get_divisors(4,[-2,0,2])
[2]
divisors = []
for item in possible_divisors:
if item != 0 and num % item == 0:
divisors.append(item)
return divisors
since Python uses lazy evaluation, which occurs if the first operand in an and
expression is false, and the expression evaluates to False, the second operand is not
evaluated, so there will not be a divide by 0 error.
when we check using the doctest, the results would look like:
>>>import doctest
>>>doctest.testmod()
File _main_, line 9, in_main_.get_divisors
Failed example:
get_divisors(4,[-2,0,2])
Expected:
[2]
Got:
[-2,2]
This result shows that there was an error with the test case inside the docstring, since -2
is also a divisor of 4, so the example inside the docstring should be altered
Similarities to a Doctest
same as in a Doctest, we must also import a a module
we also write code the way it would appear in the shell as well as the expected result
Example Unitest
file named divisors.py contains function def get_divisors
the function def get_divisors takes in an int, and list of int, and returns a list of int
it returns a list of values that are possible divisors of the integer
>>>import unittest
>>>import divisors
>>>class TestDivisors(unittest.Testcase):
>>>
Example unittest test methods for get_divisors.
>>>
def test_divisors_examples_1(self):
>>>
Test get_divisors with 8 and [1,2,3].
>>>
actual = divisors.get_divisors(8,[1,2,3])
>>>
expected = [1,2]
>>>
self.assertEqual(actual,expected) #comparing expected value and actual value
>>>
>>>
>>>
>>>
>>>
def test_divisors_example_2(self):
Test get_divisors with 4 and [-2,0,2].
actual = divisors.ge_divisors(4,[-2,0,2])
expected = [-2,2]
self.assertEqual(actual,expected)
>>>if_name_ == main:
>>>
unittest.main(exit = False) #looks through test cases in class for methods beginning with
#test and reports any unexpected results
running the unitest will result in 2 dots, because the two example tests were successful
if there is a failure you will get two Fs
a result of F is caused by incorrect assertion
when there is an error, we are told names of each method that had a failure and shown
its docstring
we are shown the Traceback, which is series of functions of method calls that led to the
error
we also get AssertionError, which shows the expected and actual values
>>>AssertionError: Lists differ: [8,1,2] != [1,2]
unitests can be more useful than doctests because we get more feedback
for example, if a function returns a bool, you would need at least 2 test cases to
represent the True case and the False case
Things to Consider:
Size
if your program collects items, you should test with an empty collection, a collection with
1 item, the smallest interesting case, and a collection with several items
Dichotomies
its important to test values from different possible categories that your program uses, for
example vowels/non vowels, even/odd, positive/negative, empty/full etc.
Boundaries
if the function behaves differently for values near a particular threshold, perform tests at
that threshold
e.g if the function checks if a value is less than 18, 18 is a threshold
Order
if a function behaves differently when the values are in different orders, identify and test
each of those orders
Example of different Test Cases for Example Functions
Dichotomy and Boundary Example
the following function returns True if two integers have the same absolute value
>>>def same_abs(int1,int2):
>>> (int, int) -> bool
>>>
Return True iff int1 and int2 have the same absolute value.
>>>
Int1
Int2
Expected Result
2 zeroes
True
True
-2
-2
True
-2
True
22
False
-22
-2
False
22
-2
False
Boundaries Example
the following function returns True if and only if the age entered is a teen age
>>>def is_teenager(age):
>>>
(int) -> bool
>>>
precondition: age >= 0
>>>
Return True iff age is a teenager between 13 and 18 inclusive
>>>
Age
Expected Result
Below 13
12
False
13
True
In Between
14
True
18
True
Above 18
19
False
the following function returns true if all the letters are fluffy
>>>def all_fluffy(s):
>>>
(str) -> bool
>>>
Return True iff every letter in s is fluffy. Fluffy letters are those that appear
>>>
in the word fluffy.
>>>
Expected Result
False
1 letter, fluffy
True
fabcd
False
abcdf
False
abcde
False
fluffy
True
if the value of n was 10, the numbers 1 to 10 will be printed since the for loop iterates 10
times
since the for loop iterates 10 times, print is called 10 times
for all values of n, the number of steps is proportional to the size of n
the amount of times print is called is equal to the number of n
another example would be a function that prints odd integers
e.g >>>def print_odd_ints(n):
>>>Print the odd integers from 1 to n, inclusive
>>>
for i in range(1,n+1,2):
>>>
print(i)
if the value of n was 10, the numbers 1,3,5,7,9 will be printed
the number of print function calls is about half of n, depending on if n is even or odd
functions print_ints and print_odd_ints both have linear run times (run time grows linearly
with respect to the input size (n))
if n and the number of steps was plotted on a graph with the number of steps as the
dependent variable on the y-axis, both functions would have a linear graph
print is called n2 times since the inner loop is executed n times, and the outer loop is
executed n times as well
the total number of steps is proportional to the size of input squared
print is called log2n times since the number of steps change on when n is doubled
the total number of steps is proportional to the size of log2n
number of steps
what is printed
1,2,4
n=5
n=6
n=7
1,2,4
1,2,4,8
16
1,2,4,16
32
1,2,4,16,32
the following 3 types of algorithms for sorting functions all sort list of numbers from
smallest to largest
Bubble Sort
bubble sort involves bubbling up the largest value in the unsorted part of the list
In pass 1 of the sort, we start off with numbers 7,3,5,2
7
7 is compared with 3
in pass 2 of the sort, we start off looking at the first index and we compare values of the
integers with the next integer up to the part of the list that is already sorted, which would
be up to and excluding integer 7
in pass 3, we look at the first index and compare values of the integers with the next
integer up to the part of the list that is already sorted, which would be up to and
excluding 5
the part of the list that contains 3 and 2 are unsorted, and the
part beyond integer 5 is sorted
Another example of bubble sort and passes would be sort of the list [8,2,8,7,3,1,2]:
the bolded integers represent the part of the list that is already sorted
After the 1st pass
[2,8,7,3,1,2,8]
[2,7,3,1,2,8,8]
[2,3,1,2,7,8,8]
[2,1,2,3,7,8,8]
[1,2,2,3,7,8,8]
[1,2,2,3,7,8,8]
Selection Sort
in selection sort, the smallest value in the list is found and swapped with the first index of
the unsorted part of the list
in pass 1 of the sort, we start off with the list [3,7,2,5]
the smallest integer is found and swapped with the first index
3
in pass 2 of the sort, the unsorted part of the list is the part after integer 2
the next smallest integer is found and swapped with the first index of the unsorted part of
the list
in pass 3 of the sort, the unsorted part of the list is the part after integer 3
the next smallest integer is found and swapped with the first index of the unsorted part of
the list
3
index of 7
2
5 is swapped with 7
def selection_sort(L):
""" (list) -> NoneType
Sort the items of L into non-descending order.
>>> L = [4, 2, 5, 6, 7, 3, 1]
>>> selection_sort(L)
>>> L
[1, 2, 3, 4, 5, 6, 7]
"""
print("Selection sort")
for i in range(len(L)):
# Find the index of the smallest item in L[i:] and swap that
# item with the item at index i.
index_of_smallest = get_index_of_smallest(L, i)
L[index_of_smallest], L[i] = L[i], L[index_of_smallest]
print(L)
Another example of bubble sort and passes would be sort of the list [1,5,8,7,6,1,7]:
the bolded integers represent the part of the list that is already sorted
After the 1st pass
[1,5,8,7,6,1,7]
[1,1,8,7,6,5,7]
[1,1,5,7,6,8,7]
[1,1,5,6,7,8,7]
[1,1,5,6,7,7,8]
[1,1,5,6,7,7,8]
[1,1,5,6,7,7,8]
Insertion Sort
in insertion sort, the smallest value in the unsorted part of the list is found and inserted in
its correct position in the sorted part of the list
in pass 1 of the sort, we start off with the list [3,7,2,5]
7
def insertion_sort(L):
""" (list) -> NoneType
Sort the items of L into non-descending order.
>>> L = [4, 2, 5, 6, 7, 3, 1]
>>> insertion_sort(L)
>>> L
[1, 2, 3, 4, 5, 6, 7]
"""
print("Insertion sort")
for i in range(len(L)):
insert(L, i)
print(L)
Another example of insertion sort and passes would be sort of the list [6,8,2,1,1,9,4]:
the bolded integers represent the part of the list that is already sorted
After the 1st pass
[6,8,2,1,1,9,4]
[6,8,2,1,1,9,4]
[2,6,8,1,1,9,4]
[1,2,6,8,1,9,4]
[1,1,2,6,8,9,4]
[1,1,2,4,6,8,9]
i
3
------------------sorted--------------------------| |---------------------unsorted--------------------------|
i is a missing value, and in the worst case, i will ensure that the most number of steps
will be executed when insert(L,i) is called
for the worst case, all the numbers in the sorted area will have to shift over, so in this
case i can be anything less than 3, for example i can = 2
when insert(L,i) is executed, the while loop must iterate 4 times since index of i is at
position 4
the number of assignment statements are executed would be 2x4 + 2 so 10 times since
there are 2 assignment statements in the loop, and the loop iterates 4 times and the
other 2 are from the beginning at end of the code
generally, in the worst case on pass i of insertion sort, the while loop iterates i times,
since for the while loop to exit i must equal 0 and each time 1 is subtracted from i
in the worst case on pass i of insertion sort, 2i + 2 assignment statements are executed
since there are 2 assignment statements in the while loop x i iterations of loop + the 2
assignment statements outside of the loop
in terms of i in the worst case, function insert has linear running time
in function insertion_sort, the last time that function insert is called, i has value len(L) - 1
for the call inserion_sort(L), a formula that expresses the number of comparisons during
insert calls would be n2
there are n2 comparisons because in insert the comparisons are while i > 0 and L[i-1]
> value
which can be written as 1 + (2x1 + 1) + ( 2x2 + 1) +....+(2(n-1)+1)
n
2
n2
value of i
number of comparisons
the comparisons
2 + 1 = 3 comparisons
4 + 1 = 5 comparisons
i
1
-----------------sorted--------------------------| |---------------------unsorted--------------------------|
number at index i can be filled with a value that will cause insert(L,i) to perform the
fewest number of steps, which is the best case
i can be anything greater than 4, like 6 for example
when insert(L,i) is executed, the while loop iterates 0 times because since 6 is greater
than 4, while loop exits
when insert(L,i) is called on the example list, 2 assignments statements are executed
( the ones outside the loop)
In general, in the best case on pass i of insertion sort, the while loop iterates 0 times
In genera., in the best case on pass i of insertion sort, 2 assignment statements are
executed
In the best case, insert has constant running time
for the best case, a formula expressing the number of comparisons that are made during
all the calls to insert would be 2(n-1) + 1 or 2n - 1.
in the best case L(i-1) > value must be false when except when i = 0 only one
comparison is made, otherwise there will be 2 comparisons
In the best case, insertion_sort has linear running time
# Find the index of the smallest item in L[i:] and swap that
# item with the item at index i.
index_of_smallest = get_index_of_smallest(L, i)
L[index_of_smallest], L[i] = L[i], L[index_of_smallest]
print(L)
in the list below, i passes of the selection sort algorithm have been completed
the double bar separates the sorted and unsorted parts of the list
i
sorted
unsorted
Analysis
get_index_of_smallest(L,i) compares pairs of items from the unsorted part of the list
n2n
2
index
number of comparisons
i=0
n-0-1
n-1
i=1
n-1-1
n-2
i=2
n-2-1
n-3
i = n-1
n-(n-1)-1
(n1) x n
2
which is
n2n
2
total would be
in terms of the length of the list, selection_sort has quadratic running time
classes help us represent more complicated information, and allows us to create types
to name classes, start with a capital letter, and separate words with capital letters
instead of underscores
for example to create a new time called CashRegister, we would write the following
code:
class CashRegister:
A class register
def_init_(self,loonies,toonies,fives,tens,twenties):
(CashRegister,int,int,int,int,int) -> NoneType
A cash register with loonies, toonies,fives,tens and twenties.
(A loonie is a Canadian 1$ coin and a toonie is a Canadian $2
coin)
>>>register = CashRegister(5,2,6,3,1)
>>>register.loonies
5
>>>register.toonies
2
>>>register.fives
6
>>>register.tens
3
>>>register.twenties
1
self.loonies = loonies
self.toonies = toonies
self.fives = fives
self.tens = tens
self.twenties = twenties
the first parameter of a method is usually called self, and self refers to the object that is
being initialized
in the method above, self refers to CashRegister
each object is an instance of its class
__init__: is the method that is called to initialize an object, and its first parameter is self
a class describes the behaviour and features of instances of that class
an object is an instance of a class
Instance Variables
assigning to a new variable using dot notation creates that variable inside the object
variables inside an object are called instance variables
for example loonies,toonies,fives,tens, and twenties are instance variables, they exist
inside an instance
Steps for Executing CashRegister(5,2,6,3,1)
1. Create an object of type CashRegister
2. Call that objects __init__ method, which creates instance variables inside the object
3. Produce that objects memory address
Other Methods
other methods such as add and get_total, can be added
e.g def get_total(self):
(CashRegister) -> int
Return the total amount of cash in the register
>>>register = CashRegister(5,5,5,5,5)
>>>register.get_total()
190
if denomination == loonies:
self.loonies += count
elif denomination == toonies:
self.toonies += count
elif denomination == fives:
self.fives += count
elif denomination == tens:
self.tens += count
else:
self.twenties += count
CSC 108 - Lec 31- Object-Oriented-Programming Examples
Creating Instance Variables
in the following method, dot assignment is used to create instance variables start_time,
end_time and event_name:
class Event:
"""A new calendar event."""
def __init__(self, start_time, end_time, event_name):
""" (Event, int, int, str) -> NoneType
Precondition: 0 <= start_time < end_time <= 23
Initialize a new event that starts at start_time,
ends at end_time, and is named event_name.
>>> e = Event(12, 13, 'Lunch')
>>> e.start_time 12
>>> e.end_time 13
>>> e.name 'Lunch
self.start_time = start_time
self.end_time = end_time
self.event_name = name
Reassigning Values Example
in the following method, the original name is assigned to new_name, which will change
the event name
def rename(self, new_name):
""" (Event, str) -> NoneType
Change the name of this event to new_name.
>>> e = Event(12, 13, 'Lunch')
>>>
>>>
"""
self.name = new_name
self.title = title
self.artist = artist
self.minutes = minutes
self.seconds = seconds
def __str__(self):
(Song) -> str
Return a string representation of this song.
>>>song = Song(Neil Young,Harvest Moon,5,3)
>>>str(song)
Neil Young, Harvest Moon (5:03)
the string method rjust(width,fillchar) returns a string justified of length width and fillchar
is the filler character
default of fillchar is space
e.g >>>3.rjust(2,0)
03
>>>14.rjust(2,0)
14
in the above example, rjust is used so that the time in minutes is always returned as two
digits, so 3 minutes is 03
self.title = title
self.songs = []
the add method adds songs to the playlist
we must specify the class that can be found in module song
songs instance variable is a list, self.songs =[] so we use append
def add(self,song):
(Playlist, Song) -> Nonetype
add song to this playlist
>>>stompa = song.song(Serena Ryder , Stompa, 3, 15)
>>>playlist = Playlist(Canadian Artists)
>>>playlist.add(stompa)
>>>playlist.songs[0] == stompa
True
self.songs.append(song)
we can write a method called get_duration(self): to get the duration of a playlist
def get_duration(self0:
(Playlist) -> tuple of (int,int)
Return the duration of this playlist as a tuple of minutes and seconds.
>>>playlist = Playlist(Canadian Artists)
>>>playlist.add(song.Song(Serena Ryder,Stompa,3,15))
>>>playlist.add(song.Song(Neil Young,Harvest Moon,5,3))
>>>playlist.get_duration()
(8,18)
total_minutes = 0
total_seconds = 0
duration = self.get_duration()
minutes = str(duration[0])
seconds = str(duration [1].rjust(2,0)
result = self.title + ( + minutes + : + seconds + )
song_num = 1
for song in self.songs:
result = result + \n + str(song_num) + . + str(song)
song_num = song_num + 1
return result
CSC 108 - Lec 33- Class Interaction Example
Class Day
the following program creates a new class day which interacts with class event that was
in lecture note 31
firstly, instance variables day, month, year, and events was created
import event
class Day:
"""A calendar day and its events."""
def __init__(self, day, month, year):
to write the method schedule_event, first the day was created( d = Day(27, 'November',
2015)
then the event was created by calling method __init__ from the event module which
created the object of type Event ( e = event.Event(11, 12, 'Meeting'))
to schedule an event, dot assignment was used (d.schedule_event(e))
events is part of object self, and it is a list that refers to all the events that were
scheduled (d.events[0] == e)
since events is a list, we can use append to add the new events to the list
def schedule_event(self, new_event):
""" (Day, Event) -> NoneType
Schedule new_event on this day, even if it overlaps with
an existing event.
>>> d = Day(27, 'November', 2015)
>>> e = event.Event(11, 12, 'Meeting')
>>> d.schedule_event(e)
>>> d.events[0] == e
True
"""
self.events.append(new_event)
to return the information in a string format, we must use a for loop to convert events to
strings
we must use a for loop because we are unsure about how many events are inside the
list
to change the event to a string, it can be written as either str(event) or event.__str__()
def __str__(self):
""" (Day) -> str
Return a string representation of this day.
>>> d = Day(8, 'December', 2015)
>>> d.schedule_event(event.Event(15, 16, 'Submit A3 work'))
>>> d.schedule_event(event.Event(16, 23, 'Celebrate end of classes'))
>>> print(d)
8 December 2015:
- Submit A3 work: from 15 to 16
- Celebrate end of classes: from 16 to 23
"""
day_string = '{0} {1} {2}:'.format(self.day, self.month, self.year)
for event in self.events:
day_string = day_string + '\n- ' + str(event)
return day_string
Creating Days and Adding Events
Default Parameters
Python allows you to specify default parameters to make it easier to change values, or
create similar results
for example the following code has two default parameters, subject and number
def favourite_course(subject='CSC', number=108):
return 'My favourite course is {0}{1}'.format(subject, number)
if no parameters are specified in the function call,the result will be the default values
which is CSC108
>>>favourite_course()
'My favourite course is CSC108'
If only one of the parameter values are specified, the other value will be the default value
>>>favourite_course('MAT')
'My favourite course is MAT108'
If both values are specified, the default values will not be used
>>>favourite_course('MAT', 135)
'My favourite course is MAT135'
the method below is the same schedule_event method from class day, however it is
improved so that when events are double-booked (they overlap with an existing event),
the method will return False
to check if an event overlaps with another event, we can call method overlaps from
another class, and to check every event we must iterate and use a for-loop
since the existing_event object has all the methods of class event, by referring to the
name of the object it allows us to access the methods
in the following example we used the overlaps method with the object existing_event
the following function returns the number of events that were successfully scheduled
without overlap
this function is an example of calling a method from inside a method
when we want to call methods from inside a method, self should be used as the object
name
in this function, the method schedule_event was used on object self
methods always need to be associated with the object being called on
Exceptional Situations may occur when input is not valid, or when preconditions of a
function are not met
For example ValueErrors may result when substrings are not found, or wrong arguments
are used
e.g >>>(abc,index(r))
ValueError: Substring not found
e.g >>>int(moogah)
ValueError: invalid literal for int() with base 10 : moogah
you can type help(ValueError) in the python shell for help on class ValueError
>>>help(ValueError)
class ValueError(Exception)
Inappropriate argument value of correct type
in the two examples above, the methods or functions were asked to do something they
couldnt do, which was an exceptional situation
use exceptions when you encounter a situation that is not part of the normal flow of
execution
Inheritance Hierarchy
the method resolution order is part of the results you get when you type help(ValueError)
in the Python shell
the method resolution order lists classes in inheritance hierarchy, which shows the
subclass order of exceptions
Method resolution order:
ValueError
Exception
BaseException
Object
ValueError is a subclass of Exception, which is a subclass of BaseException, which is a
subclass of Object
ZeroDivisionError
typing help(ZeroDivisionError) in the Python shell can give you more information about it
>>>help(ZeroDivisionError)
class ZeroDivisionError(Arithmetic Error)
Second argument to a division or modulo operation was zero
Method resolution order:
ZeroDivisionError
ArithmeticError
Exception
BaseException
Object
ZeroDivisionError is a subclass of ArithmeticError, which is a subclass of Exception,
which is a subclass of BaseException
Handling Statements
Python provides exception handling statements:
try statement(simple form)
try: -> try to execute code
statements
except: ->except when there is a problem
statements
e.g:
>>>try:
1/0
print(Does this get printed?)
>>>except:
print(Divided by zero.)
Divided by Zero
if a statement in the try block raises an exception, the remaining statements in the block
are not executed, which is why Does this get printed? was not printed
except Exception:
#the first clause matches
print(Exception happened)
except ZeroDivisionError:
#the second clause is not reached
print(Divided by Zero)
Exception happened
Warning: If you want to handle two kinds of exceptions, and one is a subclass of another,
catch the subclass first
Naming Exceptions
Every exception is an object and we can give them names
>>>try:
1/0
except ValueError:
print(Value error. )
except ZeroDivisionError as zde:
print(Divided by zero.{}.format(zde))
Divided by zero. Division by zero