7-8.pdf
Document Details
Uploaded by GenerousChrysoprase
La Trobe University
Tags
Full Transcript
Lecture 7.2 Sets and Dictionaries Introduction ◎ In the previous lecture we looked at storing items in a list. ◎ A list is an example of a data structure---a collection of items that enables efficient access and modification. ◎ In this lecture we will learn about two more data structures: ○ Sets....
Lecture 7.2 Sets and Dictionaries Introduction ◎ In the previous lecture we looked at storing items in a list. ◎ A list is an example of a data structure---a collection of items that enables efficient access and modification. ◎ In this lecture we will learn about two more data structures: ○ Sets. ○ Dictionaries. 2 Lecture Overview 1. Sets 2. Dictionaries 3. Example Program: Most Frequent Word 3 1. Sets 4 What is a Set? ◎ A set is a data structure for storing an unordered collection of items with unique values. 5 What is a Set? ◎ Like a list: ○ A set is a data structure which can hold multiple items. ○ A set is mutable. ◎ Unlike a list: ○ The items within a set are unordered. ○ Two items with the same value cannot be stored in the same set. 6 Why Use Sets? ◎ The purpose of sets might not be obvious at first. ◎ However, they do have a few good uses: ○ Checking whether an item value is in a set is very, very, fast. This makes them useful for implementing things like blacklists. ○ If you want to remove duplicate values, sets can take care of that for you. ○ Mathematical sets operations 7 Set Literals ◎ In Python, a set can be created using curly brackets. # A set of strings. {'north', 'south', 'east', 'west'} # A set of integers. {2, 3, 5, 7, 11} 8 Empty Sets ◎ A set with no items is called an empty set. # An empty set. set() >>> s = set() ◎ You can't use curly brackets to create an empty set (we'll see why soon). ◎ Instead, the set constructor must be used. 9 Sets are Unordered ◎ Items in a set do not have an order. ○ To use a physical analogy, sets are like bags. ◎ There is no "start" or "end" of a set. >>> s = {'cat', 'duck', 'dog'} >>> s {'dog', 'duck', 'cat'} >>> s[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'set' object is not subscriptable ◎ You can't index or slice a set. 10 Set Items are Unique ◎ The items within a set are always unique. ○ That is, no two items in a set can have the same value. >>> my_set = {'nap', 'sleep', 'eat', 'sleep'} >>> my_set {'sleep', 'eat', 'nap'} ◎ Duplicate items specified in a set literal will be discarded. 11 Converting Between Sets and Lists ◎ A list can be converted into a set using the set function. ◎ Similarly, a set can be converted into a list using list function. ○ The initial ordering of items in the set is arbitrary. ◎ Converting a list to a set and then back again will remove duplicate items. >>> l = [3, 2, 1, 2, 3] >>> s = set(l) >>> s {1, 2, 3} >>> l2 = list(s) >>> l2 [1, 2, 3] 12 Converting Between Sets and Lists Beware! The code below removes an arbitrary item from the set (not 'bam'). >>> >>> >>> >>> >>> s = l = del s = s {'bam', 'bang', 'bing'} list(s) l[0] set(l) ◎ Remember: unordered. sets are ◎ Converting a set to a list will not somehow "restore the ordering" of the set literal. {'bam', 'bang'} 13 Set Operations ◎ The three most important set operations are: ○ Adding an item to the set, ○ Removing an item from the set, and ○ Checking whether an item value is in the set. 14 Adding Items ◎ The add set method adds a new item to a set. ○ Can you guess why the method is not called "append"? ◎ If the item value is already in the set, nothing happens. >>> colours = {'red', 'green'} >>> colours {'green', 'red’} >>> colours.add('blue') >>> colours {'green', 'blue', 'red’} >>> colours.add('blue') >>> colours {'green', 'blue', 'red'} 15 Removing Items ◎ The remove set method removes an item from a set. ◎ If the item value is not in the set, an error occurs. >>> nums = {1.1, 1.3, 1.7} >>> nums {1.7, 1.1, 1.3} >>> nums.remove(1.3) >>> nums {1.7, 1.1} >>> nums.remove(2.8) Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 2.8 16 Checking Membership ◎ The in keyword checks whether an item value is in a set. ○ We call this checking membership. ◎ Use not in to check whether an item value is not in a set. >>> dirs = {'up', 'down', 'left', 'right'} >>> dirs {'up', 'right', 'left', 'down’} >>> 'left' in dirs True >>> 'forwards' in dirs False >>> 'forwards' not in dirs True 17 Examples 18 Set Methods 19 Check Your Understanding Q. If the user inputs fred, will they be welcomed into the party? 1 2 3 4 5 6 7 guests = {'tim', 'betty', 'rod'} guests.add('fred') name = input('Enter name: ') if name in guests: print('Welcome to the party!') else: print('Not invited!') 20 Check Your Understanding Q. If the user inputs fred, will they be welcomed into the party? A. Yes. ◎ After line 2, guests will contain the items 'betty', 'fred', 'rod', and 'tim'. ◎ After user input, name will contain 'fred'. ◎ Since 'fred' is in guests, the condition on line 4 is true. 1 2 3 4 5 6 7 guests = {'tim', 'betty', 'rod'} guests.add('fred') name = input('Enter name: ') if name in guests: print('Welcome to the party!') else: print('Not invited!') 21 2. Dictionaries 22 What is a Dictionary? 23 What is a Dictionary? ◎ Values can be looked up by their associated keys. ○ This is like looking up a definition (value) of a word (key) in a physical dictionary. ○ The keys in a dictionary are always unique. ◎ Like sets and lists, dictionaries are mutable. ○ Items can be added and removed without creating a new dictionary. 24 What is a Dictionary? ◎ Another way of thinking about a dictionary is as a more general list. ○ In a list, the indices are sequential integers starting at 0. ○ In a dictionary, the indices (keys) can be almost anything. ◎ This allows us to write code like properties['colour'] = 'blue' 25 Dictionary Literals 26 Dictionary Literals ◎ In Python, a dictionary can be created using curly brackets and colons. ◎ The colon is used to separate keys from associated values. # A dictionary with string keys and values. {'Bob': '0491577644', 'Jim': '0491570156'} # A dictionary with integer keys and float values. {1: 10.0, 2: 18.5, 3: 25.0} 27 Empty Dictionaries ◎ Empty dictionaries are created using curly brackets with nothing between them. ○ This is why empty sets must be created using set(). # An empty dictionary. {} 28 Indexing Dictionaries ◎ Indexing a dictionary with keys can be used for getting and setting values. ◎ In this way, indexing works similarly to lists. ○ One difference is that indexing can be used to add a new item to a dictionary. 29 Indexing Dictionaries 30 Setting Values ◎ If a key does not exist in a dictionary, setting its value will add a new item to the dictionary. >>> pet = {'name': 'fido', 'legs': 4} >>> pet {'name': 'fido', 'legs': 4} >>> pet['colour'] = 'brown' >>> pet {'name': 'fido', 'legs': 4, 'colour': 'brown’} >>> pet['legs'] = 3 >>> pet ◎ If a key does exist in a dictionary, setting its value will replace the old value. {'name': 'fido', 'legs': 3, 'colour': 'brown'} 31 Getting Values 32 Getting Values ◎ Indexing by an existing key will return its associated value. ◎ Indexing by a key which does not exist in a dictionary will result in an error. >>> nums = {1: 'one', 2: 'two', 3: 'three'} >>> nums[2] 'two’ >>> nums[0] Traceback (most recent call last): File "<stdin>", line 1, in <module> KeyError: 0 33 Removing Items ◎ The del keyword can be used to remove an item from a dictionary (just like lists). >>> nums = {1: 'one', 2: 'two', 3: 'three’} >>> del nums[1] >>> nums {2: 'two', 3: 'three'} 34 Iterating Over Dictionaries ◎ You can iterate over the items in a dictionary using a for loop and the items dictionary method. # File: contacts.py contacts = {'Bob': '0491577644', 'Jim': '0491570156'} for name, phone in contacts.items(): print(f'{name}\'s phone number is {phone}') $ python contacts.py Bob's phone number is 0491577644 Jim's phone number is 0491570156 35 3. Example Program: Most Frequent Word 36 Task Definition Create a program which reads a text file called "words.txt" that contains one word per line. Display the word that appears most often and the number of times that it appears. If there is a tie, display any one of the tied words. Example play duck duck goose duck words.txt $ python word_count.py duck 3 37 Identifying Inputs and Outputs Input ◎ The words contained in the text file "words.txt". Output ◎ The most frequent word and its count. 38 Identifying Processing Steps ◎ By breaking up the problem we end up with smaller, easier sub-problems to solve. ◎ This particular problem can be naturally broken into two halves. ◎ The two halves are connected using what I call "intermediate" inputs and outputs. ○ Not "real" inputs and outputs (remain internal). ○ Indicate values which are passed from one subproblem to another. 39 Identifying Processing Steps 1. Read through the file and count how many times each word is seen. ◎ Input: the words in "words.txt". ◎ Intermediate output: words with counts. 2. Aggregate the word counts to find the most frequent word. ◎ Intermediate input: words with counts. ◎ Output: the most frequent word and its count. 40 Sub-problem 1: Counting Words ◎ The goal of the first half of the program is to get all words and their associated counts. ◎ We will now go through this process manually to help us identify the processing steps. ◎ At this stage, we want to pay close attention to the steps that we are taking as we work through the example input. 41 Identifying Processing Steps play duck duck goose duck WORD COUNT words.txt 42 Identifying Processing Steps play duck duck goose duck WORD COUNT play 1 words.txt ◎ This is the first time we've seen the word "play". ◎ Write down "play" with a count of 1. 43 Identifying Processing Steps play duck duck goose duck WORD COUNT play 1 duck 1 words.txt ◎ This is the first time we've seen the word "duck". ◎ Write down "duck" with a count of 1. 44 Identifying Processing Steps play duck duck goose duck WORD COUNT play 1 duck 2 words.txt ◎ We've already seen the word "duck" (it's in the table). ◎ Calculate the new count for "duck" by adding one to the previous count (which was 1). ○ The new count is 1 + 1 = 2. ◎ Replace the count for "duck" with 2. 45 Identifying Processing Steps play duck duck goose duck WORD COUNT play 1 duck 2 words.txt goose 1 ◎ This is the first time we've seen the word "goose". ◎ Write down "goose" with a count of 1. 46 Identifying Processing Steps play duck duck goose duck WORD COUNT play 1 duck 3 words.txt goose 1 ◎ We've already seen the word "duck" (it's in the table). ◎ Calculate the new count for "duck" by adding one to the previous count (which was 2). ○ The new count is 2 + 1 = 3. ◎ Replace the count for "duck" with 3. 47 Analyse Processing Steps ◎ We consider one word at a time. ◎ We make a decision every time we encounter a word (have we not seen this word before?). ○ If the word has not been seen, associate a count of 1 with the word. ○ If the word has been seen, add one to the existing count for the word. 48 Analyse Processing Steps ◎ We consider one word at a time. ◎ We make a decision every time we encounter a word (have we not seen this word before?). ○ If the word has not been seen, associate a count of 1 with the word. ○ If the word has been seen, add one to the existing count for the word. for loop if condition if block else block 49 Selecting a Data Structure ◎ During our manual processing we were tracking word/count pairs. ○ Each count was associated with a word. ◎ We know that dictionaries allow us to associate values with keys. ◎ Therefore a dictionary would be an appropriate data structure to use for word counts. ○ The keys are the words (strings). ○ The values are the counts (integers). 50 Coding from Processing Steps ◎ We consider one word at a time. ◎ We make a decision every time we encounter a word (have we not seen this word before?). ○ If the word has not been seen, associate a count of 1 with the word. ○ If the word has been seen, add one to the existing count for the word. 51 Coding from Processing Steps ◎ We consider one word at a time. ◎ We make a decision every time we encounter a word (have we not seen this word before?). ○ If the word has not been seen, associate a count of 1 with the word. ○ If the word has been seen, add one to the existing count for the word. word_file = open('words.txt') counts = {} for line in word_file: word = line[:-1] if word not in counts: counts[word] = 1 else: old_count = counts[word] counts[word] = old_count + 1 52 Coding from Processing Steps ◎ We consider one word at a time. ◎ We make a decision every time we encounter a word (have we not seen this word before?). ○ If the word has not been seen, associate a count of 1 with the word. ○ If the word has been seen, add one to the existing count for the word. word_file = open('words.txt') counts = {} for line in word_file: word = line[:-1] if word not in counts: counts[word] = 1 else: old_count = counts[word] counts[word] = old_count + 1 53 Coding from Processing Steps ◎ We consider one word at a time. ◎ We make a decision every time we encounter a word (have we not seen this word before?). ○ If the word has not been seen, associate a count of 1 with the word. ○ If the word has been seen, add one to the existing count for the word. word_file = open('words.txt') counts = {} for line in word_file: word = line[:-1] if word not in counts: counts[word] = 1 else: old_count = counts[word] counts[word] = old_count + 1 54 Coding from Processing Steps ◎ We consider one word at a time. ◎ We make a decision every time we encounter a word (have we not seen this word before?). ○ If the word has not been seen, associate a count of 1 with the word. ○ If the word has been seen, add one to the existing count for the word. word_file = open('words.txt') counts = {} for line in word_file: word = line[:-1] if word not in counts: counts[word] = 1 else: old_count = counts[word] counts[word] = old_count + 1 55 Coding from Processing Steps ◎ We consider one word at a time. ◎ We make a decision every time we encounter a word (have we not seen this word before?). ○ If the word has not been seen, associate a count of 1 with the word. ○ If the word has been seen, add one to the existing count for the word. word_file = open('words.txt') counts = {} for line in word_file: word = line[:-1] if word not in counts: counts[word] = 1 else: old_count = counts[word] counts[word] = old_count + 1 56 Sub-problem 1: Completed ◎ This code will get the counts of all words in "words.txt". ◎ Our "intermediate output" is the variable counts, which contains the count for each word. word_file = open('words.txt') counts = {} for line in word_file: word = line[:-1] if word not in counts: counts[word] = 1 else: old_count = counts[word] counts[word] = old_count + 1 ◎ counts will be used by the second sub-problem. 57 Sub-problem 2: Finding the Most Frequent Word ◎ The goal of the second half of the program is to find the most frequent word. ○ This is a form of aggregation (we are looking for the maximum value word count). ◎ We have already seen how to find the maximum value using a for loop in an earlier lecture. ◎ The twist here is that we also want to know the word associated with the highest count. 58 Identifying Processing Steps WORD COUNT MAX. COUNT play 1 0 duck 3 goose 1 MAX. WORD ◎ Start with a maximum word count of 0 and a blank maximum word. 59 Identifying Processing Steps WORD COUNT MAX. COUNT MAX. WORD play 1 01 play duck 3 goose 1 ◎ The word count for "play" (1) is higher than our current maximum (0). ○ Set our maximum count to 1. ○ Set our maximum word to "play". 60 Identifying Processing Steps WORD COUNT MAX. COUNT MAX. WORD play 1 13 duck duck 3 goose 1 ◎ The word count for "duck" (3) is higher than our current maximum (1). ○ Set our maximum count to 3. ○ Set our maximum word to "duck". 61 Identifying Processing Steps WORD COUNT MAX. COUNT MAX. WORD play 1 3 duck duck 3 goose 1 ◎ The word count for "goose" (1) is not higher than our current maximum (3). ○ Do nothing. 62 Analysing Processing Steps ◎ Start with a maximum word count of 0 (and a blank maximum word). ◎ We consider one word/count pair at a time. ◎ If the count is higher than our current maximum, update our current maximum. 63 Coding from Processing Steps ◎ Start with a maximum word count of 0 (and a blank maximum word). ◎ We consider one word/count pair at a time. ◎ If the count is higher than our current maximum, update our current maximum. 64 Coding from Processing Steps ◎ Start with a maximum word count of 0 (and a blank maximum word). ◎ We consider one word/count pair at a time. ◎ If the count is higher than our current maximum, update our current maximum. max_count = 0 max_word = '' for word, count in counts.items(): if count > max_count: max_count = count max_word = word 65 Coding from Processing Steps ◎ Start with a maximum word count of 0 (and a blank maximum word). ◎ We consider one word/count pair at a time. ◎ If the count is higher than our current maximum, update our current maximum. max_count = 0 max_word = '' for word, count in counts.items(): if count > max_count: max_count = count max_word = word 66 Coding from Processing Steps ◎ Start with a maximum word count of 0 (and a blank maximum word). ◎ We consider one word/count pair at a time. ◎ If the count is higher than our current maximum, update our current maximum. max_count = 0 max_word = '' for word, count in counts.items(): if count > max_count: max_count = count max_word = word 67 Coding from Processing Steps ◎ Start with a maximum word count of 0 (and a blank maximum word). ◎ We consider one word/count pair at a time. ◎ If the count is higher than our current maximum, update our current maximum. max_count = 0 max_word = '' for word, count in counts.items(): if count > max_count: max_count = count max_word = word 68 Putting Everything Together ◎ Now that we've solved both sub-problems, all that's left is to put everything together in the one script file. ◎ We also add two print statements at the end to display the most frequent word and its count. 69 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 word_file = open('words.txt') counts = {} for line in word_file: word = line[:-1] if word not in counts: counts[word] = 1 else: old_count = counts[word] counts[word] = old_count + 1 # # # # # # # # # Open the words file. Create an empty dictionary. Loop over lines in the file. Remove the newline character. If we haven't seen the word... ...give it a count of 1. Otherwise... ...get the old count... ...and add 1 to it. max_count = 0 max_word = '' for word, count in counts.items(): if count > max_count: max_count = count max_word = word # # # # # # Start with a max count of 0. Start with a blank max word. Loop over word/count pairs. Whenever we find a higher count... ...update max count... ...and update max word. print(max_word) # Display max word. print(max_count) # Display max count. 70 Check Your Understanding Q. If the initial max_count was set to 3 instead of 0, what would the program output be? WORD COUNT play 1 duck 3 goose 1 1 2 3 4 5 6 7 8 9 max_count = 3 max_word = '' for word, count in counts.items(): if count > max_count: max_count = count max_word = word print(max_word) print(max_count) 71 Check Your Understanding Q. If the initial max_count was set to 3 instead of 0, what would the program output be? A. Word: (blank), count: 3. ◎ No word in the dictionary has a count higher than 3. ◎ This means that max_count and max_word never change. WORD COUNT play 1 duck 3 goose 1 1 2 3 4 5 6 7 8 9 max_count = 3 max_word = '' for word, count in counts.items(): if count > max_count: max_count = count max_word = word print(max_word) print(max_count) 72 Summary 73 In This Lecture We... ◎ Discovered the set and dictionary data structures. ◎ Learnt about how these data structures differ from lists, and how to use them. ◎ Solved a programming task using a dictionary as the main data structure. 74 Summary The following table summaries the main features of Python data structures. 75 Next Lecture We Will... ◎ Learn about the different kinds of software errors. 76 Thanks for your attention! The slides and lecture recording will be made available on LMS. The “Cordelia” presentation template by Jimena Catalina is licensed under CC BY 4.0. PPT Acknowledgement: Dr Aiden Nibali, CS&IT LTU. 77 Lecture 8.1 Software Errors Topic 8 Intended Learning Outcomes ◎ By the end of week 8 you should be able to: ○ Understand the difference between syntax, runtime, and logic errors, ○ Recognise and fix common syntax errors in Python, ○ Uncover logic errors using assert statements and test cases, and ○ Handle and raise runtime errors using exceptions. 2 Introduction ◎ Even the most skilled programmers must deal with errors regularly. ◎ In this lecture we will learn about the three main kinds of errors: syntax errors, runtime errors, and logic errors. ◎ We will also discuss strategies for debugging (finding and fixing errors). 3 Lecture Overview 1. Kinds of Errors 2. Asserts 3. Testing Code 4 1. Kinds of Errors 1.1 Syntax Errors 1.2 Runtime Errors 1.3 Logic Errors 5 1.1 Syntax Errors ◎ A syntax error is an error in the way that the source code of a program is written. ○ It is a violation of the strict set of rules that a programming language lays out. ○ Syntax errors are commonly caused accidentally typing the wrong character. by 6 1.1 Syntax Errors ◎ Syntax errors often prevent a program from being run at all. ◎ Syntax errors are more common amongst beginner programmers. ○ You will make fewer syntax errors as your familiarity with a programming language develops (just like natural languages). ◎ Usually the easiest kind of error to find and fix. 7 Python's SyntaxError ◎ In Python, a SyntaxError is raised when there is a syntax error in code. ○ IndentationError and TabError are more specific types of SyntaxError. ◎ Python will try to explain where the error is. ○ The error message includes a line number. bad_bracket.py 1 2 3 4 my_dict = {'A': 65, 'B': 66} my_dict['C') = 67 my_dict['D'] = 68 print(my_dict) File "bad_bracket.py", line 2 my_dict['C') = 67 ^ SyntaxError: invalid syntax 8 Common Syntax Errors ◎ Common causes of Python syntax errors include: ○ Incorrect use of assignment, ○ Misspelled/misused/missing keywords, ○ Misused/missing symbols, ○ Mismatched parentheses, brackets, and quotes, ○ Incorrect indentation, and ○ Empty blocks. ◎ We will now look at examples of these. 9 Incorrect use of Assignment # Incorrect # Fixed 'Wilfred' = name name = 'Wilfred' SyntaxError: can't assign to literal ◎ The variable receiving the reference must always be on the left side of the assignment statement. ○ It might help to read the "=" as "is assigned" or "now refers to". 10 Incorrect use of Assignment # Incorrect if age = 16: print('Sweet sixteen!') # Fixed if age == 16: print('Sweet sixteen!') SyntaxError: invalid syntax ◎ To test whether two objects have the same value, use double equals (==). ○ In Python, single equals (=) is used for assignment only. 11 Misspelled Keywords # Incorrect if t < 10: print('Cold') elsif t > 30: print('Hot') # Fixed if t < 10: print('Cold') elif t > 30: print('Hot') SyntaxError: invalid syntax ◎ Ensure that keywords are spelled correctly. ◎ Syntax highlighting in your text editor helps, since keywords are shown in a different colour. 12 Misused Keywords # Incorrect return = 5 SyntaxError: invalid syntax # Fixed (if defining a variable) return_val = 5 # Fixed (if returning a value) return 5 ◎ Don't use reserved keywords as variable or function names. ◎ Ensure that keywords are used correctly. ○ e.g. break and continue only make sense in loops. 13 Mismatched Quotes # Incorrect # Fixed store = 'Bunning's' store = 'Bunning\'s' SyntaxError: invalid syntax ◎ Ensure that escape characters are used when necessary. ◎ Don't forget to end strings with quotation marks. ◎ Ensure that you start and end string literals with the same kind of quotation mark (i.e. double or single). 14 Incorrect Indentation # Incorrect if cost > 10: print('Too expensive') else: print('Buy!') # Fixed if cost > 10: print('Too expensive') else: print('Buy!') IndentationError: unindent does not ⮩ match any outer indentation level ◎ Always indent/de-indent by 4 spaces at a time (don't mix different types of indentation). ◎ Indent statements after if, elif, else, while, for, etc. 15 Empty Blocks # Incorrect def do_nothing(): IndentationError: expected an # Fixed def do_nothing(): pass ⮩ indented block ◎ Use the pass keyword if you want to have a block that doesn't do anything. 16 1.2 Runtime Errors ◎ A runtime error is an error which arises while a program is running. ○ The program has correct syntax. ◎ Runtime errors can cause a program to crash (exit unexpectedly and display an error message). 17 Common Runtime Errors ◎ Common causes of Python runtime error include: ○ Opening a file for reading that doesn't exist, ○ Dividing a number by zero, ○ Attempting to use an undefined variable, and ○ Using a non-existent index with a list/dictionary. 18 Handling Runtime Errors ◎ Sometimes runtime errors can be avoided by performing a check beforehand. ○ e.g. Does the file exist? ◎ Alternatively, runtime errors can be handled by the program to recover from the error. ◎ We will learn about handling runtime errors in the next lecture. 19 1.3 Logic Errors ◎ A logic error is an error that causes incorrect behaviour of a program. ○ The program has correct syntax and perhaps even runs without crashing, but does not behave as expected. 20 1.3 Logic Errors ◎ Logic errors are caused by poorly designed algorithms. ◎ Logic errors are typically the most difficult kind of error to find and fix, since they don't result in informative error messages being displayed. ◎ The rest of the lecture cover techniques for revealing logic errors. 21 Avoiding Logic Errors ◎ The best way of avoiding logic errors is to think carefully about your algorithm design. ○ A sloppy or incomplete flowchart is much more likely to result in logic errors. ◎ Additionally, try to ensure that the code you write matches your intent. ○ e.g. If a person must be "over 18", you probably want to use age >= 18, not age > 18. 22 2. Asserts 23 Assert Statements ◎ Logic errors are often difficult to identify because they don't produce obvious error messages. ◎ Using assert statements it is possible to expose some logic errors as runtime errors. 24 Assert Statements ◎ An assert statement contains a condition (boolean expression) which is expected to always be true. ○ If the condition evaluates to false, a runtime error will occur. ◎ By adding assert statements to your code based on what you expect to be true, you will be alerted to flaws in your program's logic. 25 Assert Statements ◎ In Python, an assert statement is written using the assert keyword and a condition. ○ If the condition is true, nothing happens. ○ If the condition is false, an AssertionError is raised. >>> msg = 'Hello' >>> assert len(msg) > 1 >>> assert len(msg) == 4 Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError 26 Example: Assert Debugging ◎ Let's say that we want to write a program which reverses a string, then replaces the last character of the reversed string with an asterisk. ◎ We have a go at this, but find that our code does not produce the results that we expect. 27 Example: Assert Debugging s = 'heart' # Reverse the string. r = '' for i in range(-1, -len(s), -1): r = r + s[i] # Replace the last letter. f = r[:-1] + '*' Output: Expected: tra* trae* print(f) 28 Example: Assert Debugging ◎ One too many characters has been removed! ○ Something is wrong with the logic of our program. ◎ But which part of the program contains the problem? ○ Is it in the reversal part? ○ Is it in the replacement part? ◎ Adding assert statements can help us narrow in on the error. 29 Example: Assert Debugging 1 2 3 4 5 6 7 8 9 10 s = 'heart' # Reverse the string. r = '' for i in range(-1, -len(s), -1): r = r + s[i] assert len(r) == len(s) # Replace the last letter. f = r[:-1] + '*' assert len(f) == len(r) print(f) ◎ Here we have added two assert statements. 1. The length of the string should not change after reversing. 2. The length of the string should not change after replacing the last character. 30 Example: Assert Debugging asserts.py 1 2 3 4 5 6 7 8 9 10 s = 'heart' # Reverse the string. r = '' for i in range(-1, -len(s), -1): r = r + s[i] assert len(r) == len(s) # Replace the last letter. f = r[:-1] + '*' assert len(f) == len(r) print(f) $ python asserts.py Traceback (most recent call last): File "asserts.py", line 6, in <module> assert len(r) == len(s) AssertionError ◎ It was the first assertion (line 6) which failed! ◎ So there is an issue with the string reversal part of our program. 31 Example: Assert Debugging ◎ We can further investigate the string reversal code using a temporary print statement. ◎ Here's what we find: ○ The final expected repetition of the for loop is missing! ○ This means that the range sequence is too short. 1 2 3 4 5 6 7 s = 'heart' # Reverse the string. r = '' for i in range(-1, -len(s), -1): r = r + s[i] print(r) assert len(r) == len(s) ... $ python asserts.py We never get to t tr traeh tra trae Traceback (most recent call last): File "asserts.py", line 7, in <module> assert len(r) == len(s) AssertionError 32 Example: Assert Debugging ◎ The fix is to adjust the stopping value of the range. ◎ We then remove the temporary print statements, since it affects the program's output. ◎ However, we can keep the assert statements. ○ They have no effect on program output when satisfied. s = 'heart' # Reverse the string. r = '' for i in range(-1, -(len(s) + 1), -1): r = r + s[i] assert len(r) == len(s) # Replace the last letter. f = r[:-1] + '*' assert len(f) == len(r) print(f) 33 Assert Messages ◎ With many asserts it might become difficult to immediately spot what a failed assert means. ◎ For clarity you can include a custom message in an assert statement. >>> x = -5 >>> assert x > 0, 'expected x to be positive' Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: expected x to be positive ◎ The message is printed if the assertion fails. 34 Assert Messages ◎ Pro tip: You can use f-strings to include variable values in your assert message. >>> speed = 3.5 >>> time = -4 >>> dist = speed * time >>> assert dist > 0, f'expected dist to be positive, got {dist:.2f}' Traceback (most recent call last): File "<stdin>", line 1, in <module> AssertionError: expected dist to be positive, got -14.00 35 3. Testing Code 36 Why Test? ◎ Even the best programmers make mistakes. ○ Debugging is a normal part of programming. ◎ Making mistakes while programming generally isn't a problem unless those mistakes go unnoticed. ◎ Testing programs helps us find mistakes. ○ Yes, this includes logic errors. 37 Why Test? ◎ Real-world programs can have complex algorithms and behave differently based on different inputs. ◎ Just because a program works for one specific input doesn't mean that it will work for all possible inputs. ◎ Comprehensive testing increases our confidence that software actually works correctly. 38 Software Testing ◎ Software testing is the process of investigating a program to ensure its quality. ◎ Good software testing will reveal any flaws in a program's logic. ◎ When testing reveals an error, it is the programmer's job to modify the program's code to fix it. 39 Black-Box Testing ◎ We will focus on a common form of software testing called black-box testing. ◎ The program is treated as a "black-box" which which accepts inputs and produces outputs. ◎ Inputs are supplied, and the actual program outputs are compared to expected program outputs. ◎ Black-box testing is concerned with what a program does (the result), not the details of how it does it (the steps taken). 40 Designing Test Cases ◎ A test case describes what a program is expected to do given a particular set of inputs. ○ Consists of defined inputs and expected outputs. ◎ For example, a test case for a program which squares number might be: ○ Example input: 4 ○ Expected output: 16 41 Designing Test Cases ◎ In general, it is impossible to test every possible input. ◎ So, in practice, we must choose a set of test cases to cover the behaviour of the program. ◎ Test design techniques can help us decide on which test cases to choose. ◎ We will consider two important test design techniques: ○ Equivalence partitioning, and ○ Boundary-value analysis. 42 Example: Income Tax Calculator ◎ To further explore test design, we will consider the example of an income tax calculator. ◎ The program is expected to take a dollar amount as input, and output the amount of income tax for that dollar amount. Source: ATO resident tax rates 2020-21 43 Equivalence Partitioning ◎ Equivalence partitioning is a test design technique which involves partitioning all possible inputs into equivalence classes for which the software is expected to behave similarly. ◎ One test case is created per equivalence class by selecting representative input(s) for that class. ◎ For the tax calculator example, each tax bracket is an equivalence class. 44 Example: Income Tax Calculator ◎ From equivalence partitioning, we have created 5 test cases. ○ Expected outputs are calculated manually. ◎ Notice that we select input values which are well within each equivalence class. EQUIVALENCE CLASS EXAMPLE INPUT EXPECTED OUTPUT 0–$18,200 bracket 5,000 0 $18,201–$45,000 bracket 35,000 3,192 $45,001–$120,000 bracket 90,000 19,717 $120,001–$180,000 bracket 150,000 40,567 $180,001+ bracket 900,000 375,667 45 Boundary-Value Analysis ◎ Boundary-value analysis is a test design technique which involves identifying inputs which are close to the boundaries of equivalence classes. ◎ Test cases are created for the minimum and maximum edges of each equivalence class. 46 Boundary-Value Analysis ◎ Boundary-value analysis helps to catch errors which may arise from things like: ○ Including/omitting equality in a comparison. ◉ e.g. using <= instead of <. ○ Off-by-one errors ◉ e.g. using range(1, n) instead of range(1, n+1). 47 Example: Income Tax Calculator EQUIVALENCE CLASS 0–$18,200 bracket $18,201–$45,000 bracket $45,001–$120,000 bracket $120,001–$180,000 bracket $180,001+ bracket BOUNDARY EXAMPLE INPUT EXPECTED OUTPUT Minimum 0 0 Maximum 18,200 0 Minimum 18,201 0.19 Maximum 45,000 5,092 Minimum 45,001 5,092.325 Maximum 120,000 29,467 Minimum 120,001 29,467.37 Maximum 180,000 51,667 Minimum 180,001 51,667.45 48 Check Your Understanding Q. How many equivalence classes are there for the shown task definition? Task definition Create a train ticketing program which displays the kind of fare that a passenger must pay. If the passenger is age 12 or under, they are considered a child. If the passenger is age 65 or older, they are considered a senior. Everyone else is full fare. 49 Check Your Understanding Q. How many equivalence classes are there for the shown task definition? A. 3. ◎ Children (age <= 12) ◎ Seniors (age >= 65) ◎ Everyone else (12 < age < 65) Task definition Create a train ticketing program which displays the kind of fare that a passenger must pay. If the passenger is age 12 or under, they are considered a child. If the passenger is age 65 or older, they are considered a senior. Everyone else is full fare. 50 Summary 51 In This Lecture We... ◎ Learnt the three main kinds of software errors: syntax errors, runtime errors, and logic errors. ◎ Used assert statements to expose logic errors using runtime errors. ◎ Explored techniques for testing software to reveal errors. 52 Next Lecture We Will... ◎ Use Python's exception system to handle runtime errors and raise our own. 53 Thanks for your attention! The slides and lecture recording will be made available on LMS. The “Cordelia” presentation template by Jimena Catalina is licensed under CC BY 4.0. PPT Acknowledgement: Dr Aiden Nibali, CS&IT LTU. 54 Lecture 8.2 Exceptions Lecture Overview 1. Exceptions and Stack Traces 2. Handling Exceptions 3. Raising Exceptions 2 1. Exceptions and Stack Traces 3 Exceptions ◎ An exception is a runtime error that can be recovered from. ◎ We say that code which produces an exception "raises an exception". ◎ We say that code which anticipates and recovers from an exception "handles an exception". 4 Exceptions ◎ An exception which is not handled will typically cause the program to crash and display information about the exception. ◎ Programmers can anticipate exceptions when writing code and explicitly handle them. ○ This can be done in such a way that the runtime error will not cause the program to crash. 5 Types of Exceptions ◎ Python uses different types of exceptions for different kinds of runtime errors. ◎ Examples include: ○ ZeroDivisionError (e.g. dividing a number by zero). ○ IndexError (e.g. using an index not in a list). ○ KeyError (e.g. using a key not in a dictionary). ○ NameError (e.g. using an undefined variable). ○ FileNotFoundError (e.g. opening a file that doesn't exist for reading). 6 Exception Messages ◎ In addition to its type, an exception can also carry a message with more information. ◎ The type and message will be displayed together when an exception is not handled. >>> open('i_dont_exist.txt') Traceback (most recent call last): File "<stdin>", line 1, in <module> FileNotFoundError: [Errno 2] No such file or directory: 'i_dont_exist.txt' Type Message 7 Exception Messages ◎ But what is this stuff here? ◎ To understand the first part of the exception output, we must first learn about the call stack. >>> open('i_dont_exist.txt') Traceback (most recent call last): File "<stdin>", line 1, in <module> ??? FileNotFoundError: [Errno 2] No such file or directory: 'i_dont_exist.txt' 8 The Call Stack ◎ Recall that when a function is called, the control flow moves into that function. ◎ When the function is finished, control flow moves back to the place that the function was called from (the call site). # This is a function. def print_greeting(): print('Hello!') # This is a call site. print_greeting() # This is a second call site. print_greeting() 9 The Call Stack ◎ Functions can call other functions, resulting in a call stack describing how to return to each call site. ◎ In the example shown, line 10 calls foo which calls bar which calls baz. ○ The call stack keeps track of all of these locations so that Python knows where to go when each function finishes. 1 2 3 4 5 6 7 8 9 10 def baz() print('Baz') def bar() baz() def foo() bar() foo() 10 Stack Traces ◎ The confusing-looking part of the exception output starting with "Traceback" is called a stack trace. ◎ The stack trace is a text-based description of the call stack at the time an exception is raised. >>> open('i_dont_exist.txt') Traceback (most recent call last): File "<stdin>", line 1, in <module> Stack trace FileNotFoundError: [Errno 2] No such file or directory: 'i_dont_exist.txt' 11 Reading Stack Traces ◎ Stack traces describe the chain of call sites that lead to the exception, in order. ◎ Each entry in a stack trace "zooms in" further on the source of the exception. ◎ The true cause of the error may be more evident on some levels than on others. ○ Which part of the stack trace you focus your attention on will change depending on the program. ◎ The place where the exception is first raised is shown last. 12 Example: Reading Stack Traces 1 2 3 4 5 6 7 8 9 def get_apple_count(fruit): return fruit['apples'] def print_apple_count(fruit): count = get_apple_count(fruit) print(f'Apple count: {count}') Traceback (most recent call last): File "fruit.py", line 9, in <module> print_apple_count(fruit) File "fruit.py", line 5, in print_apple_count count = get_apple_count(fruit) File "fruit.py", line 2, in get_apple_count return fruit['apples'] KeyError: 'apples' fruit = {'pears': 2, 'bananas': 7} print_apple_count(fruit) ◎ The code (above left) is written in a script file called "fruit.py" and crashes with an error message (above right) when run. 13 Example: Reading Stack Traces 1 2 3 4 5 6 7 8 9 def get_apple_count(fruit): return fruit['apples'] def print_apple_count(fruit): count = get_apple_count(fruit) print(f'Apple count: {count}') Traceback (most recent call last): File "fruit.py", line 9, in <module> print_apple_count(fruit) File "fruit.py", line 5, in print_apple_count count = get_apple_count(fruit) File "fruit.py", line 2, in get_apple_count return fruit['apples'] KeyError: 'apples' fruit = {'pears': 2, 'bananas': 7} print_apple_count(fruit) ◎ The exception type and message gives us information about the error, but does not tell us where to look in the code. ○ That information is contained in the stack trace. 14 Example: Reading Stack Traces 1 2 3 4 5 6 7 8 9 def get_apple_count(fruit): return fruit['apples'] def print_apple_count(fruit): count = get_apple_count(fruit) print(f'Apple count: {count}') Traceback (most recent call last): File "fruit.py", line 9, in <module> print_apple_count(fruit) File "fruit.py", line 5, in print_apple_count count = get_apple_count(fruit) File "fruit.py", line 2, in get_apple_count return fruit['apples'] KeyError: 'apples' fruit = {'pears': 2, 'bananas': 7} print_apple_count(fruit) ◎ The last entry in the stack trace tells us where the exception is raised from. ○ In this case, it's a statement in get_apple_count. ◎ But where was get_apple_count called from? 15 Example: Reading Stack Traces 1 2 3 4 5 6 7 8 9 def get_apple_count(fruit): return fruit['apples'] def print_apple_count(fruit): count = get_apple_count(fruit) print(f'Apple count: {count}') Traceback (most recent call last): File "fruit.py", line 9, in <module> print_apple_count(fruit) File "fruit.py", line 5, in print_apple_count count = get_apple_count(fruit) File "fruit.py", line 2, in get_apple_count return fruit['apples'] KeyError: 'apples' fruit = {'pears': 2, 'bananas': 7} print_apple_count(fruit) ◎ Moving up in the stack trace, we can find where get_apple_count was called from when the error occurred. ○ In this case, it's a statement in print_apple_count. ◎ But where was print_apple_count called from? 16 Example: Reading Stack Traces 1 2 3 4 5 6 7 8 9 def get_apple_count(fruit): return fruit['apples'] def print_apple_count(fruit): count = get_apple_count(fruit) print(f'Apple count: {count}') Traceback (most recent call last): File "fruit.py", line 9, in <module> print_apple_count(fruit) File "fruit.py", line 5, in print_apple_count count = get_apple_count(fruit) File "fruit.py", line 2, in get_apple_count return fruit['apples'] KeyError: 'apples' fruit = {'pears': 2, 'bananas': 7} print_apple_count(fruit) ◎ Moving up in the stack trace, we can find where print_apple_count was called from when the error occurred. ◎ In this case, we might find this to be a particularly useful place to look since we can see that the fruit dict does not contain apples. 17 2. Handling Exceptions 18 Exception Handling ◎ In Python, exceptions can be handled using the try and except keywords. ◎ The try block surrounds the statements which may raise an exception. ◎ The except block contains statements to execute if an exception is raised in the associated try block. ◎ When an exception is raised in the try block, control flow immediately moves to an associated except block. 19 Example: Handling Invalid User Input 1 2 3 # File: age.py age = int(input('Enter your age: ')) print('Thank you.') $ python age.py Enter your age: twenty-five Traceback (most recent call last): File "age.py", line 2, in <module> age = int(input('Enter your age: ')) ValueError: invalid literal for int() with base 10: 'twenty-five' ◎ When the user enters text that can't be converted into an integer, the program will crash. 20 Example: Handling Invalid User Input 1 2 3 4 5 6 7 8 # File: age.py while True: try: age = int(input('Enter your age: ')) break except: print('Invalid input, try again please.') print('Thank you.') ◎ When line 4 raises an exception, control flow will immediately jump to the except block. ○ The break statement will not be executed when this happens, causing the loop to repeat. 21 Example: Handling Invalid User Input 1 2 3 4 5 6 7 8 # File: age.py while True: try: age = int(input('Enter your age: ')) break except: print('Invalid input, try again please.') print('Thank you.') $ python age.py Enter your age: twenty-five Invalid input, try again please. Enter your age: 25.0 Invalid input, try again please. Enter your age: 25 Thank you. 22 Handling Specific Exception Types ◎ By default, an except block handles any type of exception. ◎ If you specify the type of exception to be handled, the except block will only handle exceptions of that type. ○ Other types of exceptions will not be handled by that block. ◎ You can specify multiple except blocks for one try block. ◎ Remember, the output produced by unhandled exceptions will tell you their type. 23 Example: Reciprocal Calculator while True: try: denom = int(input('Enter a number: ')) result = str(1 / denom) break except: print('Invalid input, or perhaps division by zero?') print(f'The reciprocal of {denom} is {result}.') ◎ There are two kinds of errors that could occur (invalid conversion to an integer or division by zero). ◎ Currently we can't tell them apart (they are all handled by the same except block). 24 Example: Reciprocal Calculator while True: try: denom = int(input('Enter a number: ')) result = str(1 / denom) break except ZeroDivisionError: result = 'infinity' break except ValueError: print('Invalid input, try again please.') print(f'The reciprocal of {denom} is {result}.') ◎ With multiple except blocks that handle different types of exceptions, we can handle the errors with different code. 25 Example: Reciprocal Calculator while True: try: denom = int(input('Enter a number: ')) result = str(1 / denom) break except ZeroDivisionError: result = 'infinity' break except ValueError: print('Invalid input, try again please.') print(f'The reciprocal of {denom} is {result}.') Enter a number: five Invalid input, try again please. Enter a number: 0 The reciprocal of 0 is infinity. 26 To Check or to Handle? ◎ There are two different principles you can follow for dealing with runtime errors: EAFP and LBYL. 27 To Check or to Handle? ◎ EAFP = "it's easier to ask for forgiveness than permission". ◎ This means that you write code that attempts to do something, handling exceptions using try/except blocks. ◎ LBYL = "look before you leap". ◎ This means that you write code which performs a check before attempting to do something. 28 Example: Reading a File # EAFP-style code. filename = input('Filename: ') try: file = open(filename) contents = file.read() print(contents) except FileNotFoundError: print('File not found.') # LBYL-style code. import os filename = input('Filename: ') if os.path.isfile(filename): file = open(filename) contents = file.read() print(contents) else: print('File not found.') ◎ These programs essentially do the same thing. ◎ The "official" recommendation in Python is to follow EAFP. ◎ My recommendation is to follow your own preference on a caseby-case basis. 29 Check Your Understanding Q. How would you rewrite the following snippet of code to take a LBYL (“look before you leap”) approach? denom = int(input('Enter a number: ')) try: result = str(1 / denom) except ZeroDivisionError: result = 'infinity' 30 Check Your Understanding Q. How would you rewrite the following snippet of code to take a LBYL (“look before you leap”) approach? denom = int(input('Enter a number: ')) try: result = str(1 / denom) except ZeroDivisionError: result = 'infinity' A. An LBYL equivalent is as follows: denom = int(input('Enter a number: ')) if denom != 0: result = str(1 / denom) else: result = 'infinity' ◎ A ZeroDivisionError only occurs when denom is 0. ◎ So we can check whether denom is 0 instead of handling that exception. 31 3. Raising Exceptions 32 Raise Statements ◎ In Python, exceptions can be raised using the raise keyword. ◎ This gives us access to Python's exception system for our own sources of errors. ◎ When writing your own functions, you should consider raising a ValueError for invalid argument values. ○ This will help you track down errors more easily later on. 33 Example: Factorials ◎ The "factorial" of an integer is the result of multiplying itself with all positive integers less than it. ○ e.g. "5 factorial" is 5×4×3×2×1=120. ◎ "0 factorial" is defined as 1. ◎ The factorial of a negative number is not defined. ◎ Let's write our own function for calculating factorials. 34 Example: Factorials def factorial(n): result = 1 while n > 1 result = result * n n = n - 1 return result ◎ This code works, but can be called with negative numbers (which are not valid). ◎ We would like to know when our function is called with invalid input so that we can fix the problem in the calling code. 35 Example: Factorials def factorial(n): if n < 0: raise ValueError('negatives not allowed') result = 1 while n > 1 result = result * n n = n - 1 return result ◎ Here we raise a ValueError if the input to our factorial function is negative. ◎ This will tell us when our function is being used incorrectly. 36 Raising Exceptions of Different Types ◎ Of course, you can raise other types of exceptions (not just ValueError). ◎ The type of the exception is mainly for conveying meaning about the nature of the error. ◎ ValueError implies that an invalid value was encountered. ◎ Raise a RuntimeError when you are unsure of which type of exception to raise. 37 Check Your Understanding Q. What is the output of the shown Python program? def print_odd_only(n): if n % 2 == 0: raise ValueError(f'{n} is even!') print(n) try: print_odd_only(5) print_odd_only(4) print_odd_only(3) except ValueError: print('Oops') print('Done') 38 Check Your Understanding Q. What is the output of the shown Python program? A. 5, Oops, Done. ◎ print_odd_only(5) displays an output of 5. ◎ print_odd_only(4) raises a ValueError. Control flow jumps directly to the except block which prints Oops. ◎ Since the exception was handled, the program does not crash. Therefore Done is printed also. def print_odd_only(n): if n % 2 == 0: raise ValueError(f'{n} is even!') print(n) try: print_odd_only(5) print_odd_only(4) print_odd_only(3) except ValueError: print('Oops') print('Done') 39 Summary 40 In This Lecture We... ◎ Learnt how to read Python stack traces. ◎ Handled exceptions using try-except structures to recover from expected runtime errors. ◎ Raised exceptions from our own code. 41 Next Lecture We Will... ◎ Import additional functionality into our programs using modules. 42 Thanks for your attention! The slides and lecture recording will be made available on LMS. The “Cordelia” presentation template by Jimena Catalina is licensed under CC BY 4.0. PPT Acknowledgement: Dr Aiden Nibali, CS&IT LTU. 43 Lecture 7.1 Lists Topic 7 Intended Learning Outcomes ◎ By the end of week 7 you should be able to: ○ Create and manipulate lists, ○ Understand the difference between sets and lists, ○ Create dictionaries for storing key/value pairs, and ○ Understand what kinds of problems can be solved using lists, sets, and dictionaries. 2 Introduction ◎ The sequences we've worked with so far have had very specific purposes. ○ A string is a sequence of characters. ○ S=“CSE4IP” ○ A range is a counting sequence of integers. ○ r= range(5) ◎ What if we want something more customised, like a sequence of temperatures or customers? 3 Introduction 4 Introduction 5 Introduction Python variable stores only one value, whereas Python data structure stores more than one values. ◎ Python data structures are lists, sets and dictionaries. ◎ In this lecture we will learn how to build our own sequences using lists. 6 Lecture Overview 1. Basics of Lists 2. Working with Lists 3. Lists and References 7 1. Basics of Lists 8 List Literals ◎ In Python, a list literal is created using square brackets. ◎ The items within a list can be of any type. # A list of floats. [3.5, 7.8, 9.2, 1.7] # A list of strings. ['paper', 'scissors', 'rock'] # A list of booleans. [True, False, True, True, False] 9 Mixed Type Lists ◎ A list can contain items with mixed data types. ◎ This is usually a bad idea. ○ Lists are best used for sequences of similar items. # A list of mixed types. [370, True, 'rocket', False] ○ Objects are a better choice for grouping different (but related) variables. 10 Nested Lists ◎ Lists can contain other lists. ◎ A list inside another list is called a nested list. ◎ This could be useful for representing a collection of shopping lists, for example. # A list of lists of integers. [[1, 2], [5, 6], [12, 13]] 11 Empty Lists ◎ A list with no items is called an empty list. # An empty list. ◎ You might want to start with an empty list if you plan on building a list one item at a time. [] 12 List Operations ◎ In many ways lists can be worked with in the same way as strings. ○ Just mentally replace "string" with "list" and "characters" with "items". ◎ This mental substitution works for: ○ Concatenation ○ Indexing ○ Finding length ○ Slicing ○ Iterating with a for loop 13 Concatenation String Concatenation Combine the characters from two strings to create a new string. List Concatenation Combine the items from two lists to create a new list. >>> 'abc' + 'de' >>> [1, 2, 3] + [4, 5] 'abcde' [1, 2, 3, 4, 5] 14 Indexing String Indexing Get the character at the given index in the string. List Indexing Get the item at the given index in the list. >>> s = 'abc' >>> s[1] >>> l = [7, 8, 9] >>> l[1] 'b' 8 15 Length String Length Get the number of characters in the string. List Length Get the number of items in the list. >>> len('abc') 3 >>> len('') >>> len([7, 8, 9]) 3 >>> len([]) 0 0 16 Lists are Mutable ◎ Unlike strings, lists are mutable. ○ This means that a list's contents can change. ◎ Without creating a new list you can do things like: ○ Replace items, ○ Append new items, and ○ Remove existing items. 17 Replacing Items ◎ You can use indexing in combination with assignment to replace an item in a list. >>> names = ['alice', 'bob', 'charlie'] >>> names ['alice', 'bob', 'charlie’] >>> names[-1] = 'craig' >>> names ['alice', 'bob', 'craig'] ◎ This will mutate (change) the list. 18 Appending Items (adding items) ◎ Appending to a list will insert a new item at the end. ◎ Appending is a common way of building up lists. >>> >>> >>> >>> days = [] days.append('Mon') days.append('Tue') days.append('Wed’) >>> days ['Mon', 'Tue', 'Wed'] ◎ Use the append list method. 19 Example: List of Square Numbers # File: squares_list.py squares = [] for n in range(1, 11): squares.append(n ** 2) print(squares) $ python squares_list.py [1, 4, 9, 16, 25, 36, 49, 64, 81, 100] ◎ This program builds a list of square numbers. ◎ It works by using a for loop to iterate over the numbers from 1 to 10 and squaring them. ◎ The squared numbers are appended to the list using append. 20 Removing Items ◎ Items can be removed from a list using the del keyword. ◎ The part of the list to be removed can be specified using an index or slice. >>> nums = [10, 11, 12, 13, 14, 15, 16] >>> del nums[2] >>> nums [10, 11, 13, 14, 15, 16] >>> del nums[-3:] >>> nums [10, 11, 13] 21 Sorting Items ◎ A list can be sorted using the sort list method. ◎ The items in the list will be arranged in ascending order. ◎ The reverse keyword argument can be used to reverse the sort order. >>> >>> >>> [1, items = [3, 4, 12, 1, 5] items.sort() items 3, 4, 5, 12] >>> letters = ['x', 'z', 'y'] >>> letters.sort(reverse=True) >>> letters ['z', 'y', 'x'] 22 Check Your Understanding Q. What is the result of this Python expression? len([2, 3, 4] + [8] + []) 23 Check Your Understanding Q. What is the result of this Python expression? len([2, 3, 4] + [8] + []) A. 4 ◎ The result of the concatenation is [2, 3, 4, 8]. ◎ Concatenating with an empty list does not produce a list with any more items in it. ◎ The list [2, 3, 4, 8] contains 4 items, so its length is 4. 24 2. Working with Lists 25 Filtering ◎ A common thing that you might want a program to do is filter a list. ◎ Filtering a list means selecting items based on a certain condition to create a new list. ◎ Example: selecting all odd numbers from a list. 26 Example: Filtering # File: filter_odd.py numbers = [3, 4, 12, 1, 5] odds = [] for num in numbers: if num % 2 == 1: odds.append(num) print('Odd numbers:') ◎ Loop over every item. ◎ Items which pass a certain test (in this case, oddness) are appended to a new list. print(odds) $ python filter_odd.py Odd numbers: [3, 1, 5] 27 Mapping ◎ Another common thing that you might want a program to do is map a list. ◎ Mapping a list means transforming each item to create a new list. ◎ Example: doubling all numbers in a list. 28 Example: Mapping # File: map_double.py numbers = [3, 4, 12, 1, 5] doubled = [] for num in numbers: doubled.append(num * 2) ◎ Loop over every item. ◎ Items are transformed and appended to a new list. print('Doubled numbers:') print(doubled) $ python map_double.py Doubled numbers: [6, 8, 24, 2, 10] 29 Incorrect Example: Mapping Beware! ◎ This program will not print out doubled numbers. numbers = [3, 4, 12, 1, 5] for num in numbers: num = num * 2 print('Doubled numbers:') print(numbers) ◎ Assigning to the variable in the for loop (num) will not affect the list (numbers). ✗ ◎ So, in this program, the for loop is useless! 30 From File to List ◎ A file can contain many items of data. ◎ In many cases, it is useful to represent this data using a list in a Python program. ○ This gives us access to all of the list handling techniques discussed earlier. ◎ One way of going from a file to a list is by reading lineby-line and appending to a list. 31 Example: House Prices # File: houses.py prices = [] for line in open('house_prices.txt'): price = float(line) prices.append(price) print(prices) 770000 647500 748000 716000 751000 715000 house_prices.txt $ python houses.py [770000.0, 647500.0, 748000.0, 716000.0, 751000.0, 715000.0] 32 Example: Cheapest Houses ◎ Once we have read the file data as a list, we can process it however we want. # File: cheap_houses.py prices = [] for line in open('house_prices.txt'): price = float(line) prices.append(price) prices.sort() ◎ For example, if w