Lab 9: Python Practice

Learning Objectives

Our goal for lab this week is to get you comfortable with Python syntax, for loops, debugging, and Python's built-in function for sorting lists.

If you don't feel confident about any of these topics after your lab session, definitely come to TA Hours! We'd love to help you.

We've put a lot of problems into this lab since students will be working through Python at different paces. Don't worry if you don't finish them all.

For the first 15 minutes, you'll participate in a small group discussion about how the issue of consent plays out in societal contexts.

Task: Go to this Google Form and answer the first question. Then wait until the TAs tell you to go to the next section of the form.

Wait for the TAs instructions to open the following scenarios

Scenarios (don't expand until TAs tell you to)

Scenario 1, wildlife park (adapted from Public Debate on Facial Recognition Technologies in China · Summer 2021)

At Hangzhou Wildlife Park in China, the entrance process for members is about to get an upgrade. When purchasing the annual membership cards, all members agreed to have the park collect their fingerprints and take their pictures. Last year, members’ entry to the park was through fingerprint identification.This year, the park’s management team decided to upgrade to using facial recognition (and abandon the fingerprints system), arguing that facial recognition would help move the crowd faster during the peak seasons. The management team believes this will be a quick and smooth transition, since the members’ pictures are already collected, and they’ve found a third-party company specializing in building facial recognition systems.

Note: Scenario 1 is based on a real lawsuit. You can read more about it here.

Scenario 2, neighborhood watch app (adapted from The Case of the Nosy Neighbors · Winter 2021)

In NIMBY, a fictional neighborhood-focused social media company, users can upload photos and videos of suspicious happenings in the neighborhood, and tag the suspicious individual. The users have complained about the high degree of manual effort required and the resulting time delay, especially in time-sensitive situations like potential burglary. NIMBY has developed an algorithm to automatically identify people who have already been tagged multiple times within NIMBY’s database. To improve the algorithm, the CTO (chief technology officer) proposed using public social media profiles of people living in certain locales to enlarge the database of photos they can identify.


Task: Each person in your group should fill in one of the discussion summary boxes, reflecting what your group talked about. Your TAs will give you a group number to use on your form (so Kathi can gather up responses from the same group across the different submissions).

Task: Post something on this Jamboard. Take a moment to see what your labmates have come up with for this discussion.

Part 2: Debugging

Debugging in VSCode

Consider the following function, add_underscores(word: str) -> str, which takes in a word and returns that same word, but with underscores added before and after each letter in the word (i.e. "hello" -> "_h_e_l_l_o_"):

def add_underscores(word):
    """adds underscores before & after each letter in word"""
    new_word = "_"
    for i in range(len(word)):
        new_word = word[i] + "_"
    return new_word


phrase = "hello"
print(add_underscores(phrase))  # should print out "_h_e_l_l_o_"

Copy and paste this snippet of code into a Python file, and run the program. You should see this output:

o_

Uh oh! We were expecting to get _h_e_l_l_o_, so this means our program has a bug! Let's use the VSCode Debugger to find it. Click on the arrow next to the play button on the top right of your window and click "Debug Python File":

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

After that, the debugger should open at the bottom of your screen.

The area where our bug must be occurring is at the line that says new_word = word[i] + "_", since that's where we're supposed to be adding underscores but are currently failing to do so properly.

To investigate this line of code in particular, we're going to use a breakpoint, which will stop our code at a certain line so we can see values of variables and how they change at a given moment in time.

To add a breakpoint, click on the line number to the left of your code.

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Now, click "Debug Python File" again. At your screen, you should see something like this:

Image Not Showing Possible Reasons
  • The image file may be corrupted
  • The server hosting the image is unavailable
  • The image path is incorrect
  • The image format is not supported
Learn More →

Now we can see the values of i, new_word, and word at the moment in time when our program reaches that line where we set the breakpoint!

There are a few important buttons here:

  • In the row of buttons at the top of the file:
    • The green arrow button will rerun your program in the debugger.
    • The blue play button will "unpause" your code if it hits the breakpoint again, it'll stop again, and otherwise it will stop after your program finishes execution.
    • The red square button stops running your code in the debugger.
    • The button that displays just a blue arrow pointing down ("Step Into") will allow you to move to the next line being executed by your program, whether or not that's a breakpoint.

Play around with the blue play button and the "Step Into" button. Watch the contents of the directory in the left panel. Can you find the bug? Once you find it, fix the bug in the code!


CHECKPOINT: Call over a TA once you reach this point. Note that the TA can ask either partner to answer, so please make sure both partners understand the bug.


Debugging with print

While VSCode's debugger can be useful, sometimes we want a quicker and lighter-weight option for debugging. Let's look at another program:

def mystery_fun(num_list: list) -> int:
  return_value = 0
  for num in num_list:
    if num_list[num] % 2 == 0:
      return_value += 1
    if num_list[num] > 5:
      return_value -= 1
    if num_list[num] > 1:
      return_value += num 
  return return_value

input = [2, 0, 7, 5, 1, 8, 4, 3, 5]
print(mystery_fun(input))

Copy and paste this piece of code into VSCode and run the program. What does it print?

The body of our function is a bit confusing let's try to figure out what it's doing. While the VSCode debugger could be used here, it is sometimes easier in practice to just let Python tell us the values of certain expressions as it's running, rather than have us track them manually by looking at the directory in the debugger.

To see what our function is actually doing, let's see how we can place print statements within our function to help us decipher what is changing and when.

Add this print statement within your for loop, above the if-statements:

print(num_list[num])

Now run your program before it prints out the result of calling mystery_fun(input), it should print a bunch of numbers that correspond to num_list[num] at each iteration of the loop.

Can you see more clearly how return_value changes throughout this function? Describe how return_value is modified as it progresses through each number in input.

HINT: If you're not sure yet, try adding other print statements to make it more clear what's going on.


CHECKPOINT:

Call over a TA once you reach this point.

Compare and contrast debugging with print vs. with the debugger. Note that the TA can ask either partner to answer, so please make sure both partners understand what's going on.


Part 2: Maps and Filters in Python

Like Pyret, Python has filter, map, and lam(bda). It also has different notations for working with strings. Since these are all very useful tools, this part of lab has you practice working with them.

Imagine that it's shopping period, and Katie has put many courses in her cart. We're going to write programs to help her organize and track them all.

course_list = ["CSCI0180", "CSCI0160", "CSCI0220", "CSCI0320",
               "CSCI1420", "CSCI1951", "APMA0330", "APMA0350", "APMA1650", 
               "ENGL0220", "ENGL0510", "CLPS0150", "CLPS0450", "CLPS0900", 
               "CLPS1360"]

Task: First, let's figure out how to find all courses in a given department. Write a function dept_courses(dept: str) that takes in a 4-letter department code and produces a list of all courses in department dept. Then, write a function course_num(num: str) that takes in a 4-digit course number (as a string) and produces a list of all courses with that number (regardless of department).

Note: to do this, you need to know how to select specific portions of strings. In Python (and other languages), this is called "string slicing". See the instructions below.


String Slicing (click to expand)

NOTE: To do many of the following problems, you will need to do something called string slicing. String slicing is the Python equivalent of Pyret's string-substring, but winds up being a lot more powerful.

Let's say you have the string str = "Beep!". Just like in Pyret, strings in Python are 0-indexed:

  • The "B" is at index 0
  • The "e"s are at indices 1 and 2
  • The "p" is at index 3
  • The "!" is at index 4

To access the characters at those indices, use the notation: str[index]. For example,

>>> str[0]
"B"
>>>str[4]
"!"

If you want to slice a string into a substring of more than one character, use the notation: str[start:end] where start represents the first index of the substring (inclusive) and end represents the last index of the substring (exclusive). For example,

>>> str[0:5]
"Beep!"
>>> str[1:4]
"eep"
>>> str[2:3]   # note that this is equivalent to str[2]
"e"
>>> str[2:2]   # no characters in range, so this outputs the empty string
""

That's all you need to know for the lab, but Python has a few additional shortcuts for string slicing that might be useful in the long run:

  • If you leave off the start or end index, Python assumes you want to start at 0 and end at the end of the string:

    ​​​​>>> str[:3]   # equivalent to str[0:3]
    ​​​​"Bee"
    ​​​​>>> str[3:]   # equivalent to str[3:5]
    ​​​​"p!"
    ​​​​>>> str[:]    # equivalent to str[0:5]
    ​​​​"Beep!"
    
  • Negative numbers let you index from the end of a string

    ​​​​>>> str[-1]      # equivalent to str[4]
    ​​​​"!"
    ​​​​>>> str[-5]      # equivalent to str[0]
    ​​​​"B"
    ​​​​>>> str[-3:-1]   # equivalent to str[2:4]
    ​​​​"ep"
    

Task: Write a function course_search(dept: str, min: int, max: int) that takes in a 4-letter department code, a minimum course code, and a maximum course code, and produces a list of all courses in department dept with numbers between min and max, inclusive.

Task: Write a function pretty_print(dept: str) that takes in a 4-letter department code and prints out the full name of a department followed by all of its courses. Each course should be on a separate line with "-" before the course name.

​​​​For example:
​​​​```
​​​​>>> pretty_print("CSCI")
​​​​Computer Science
​​​​- CSCI0180
​​​​- CSCI0160
​​​​- CSCI1420
​​​​- CSCI1951 
​​​​```

Task: Write a function compare_depts(dept1: str, dept2: str) that takes in the 4-letter department codes of two departments and returns the 4-letter department code of the department that is offering more courses. If both departments are offering the same number of courses, the function should return "equal" (str). If a department isn't offering any courses, raise an exception.


CHECKPOINT: Call over a TA once you reach this point. If one partner programmed the last few functions, the other partner should program for the next task.


Problems Beyond Courses

Task: Write a function obscure that takes a string and replaces certain letters with numbers (a common, but ineffective, password protection technique). Specifically, convert every e (or E) to 3, every i (or I) to 1, and every o (or O) to zero. Write a helper function that takes in a single character and converts it if necessary (and keeps it the same if not).

NOTE: You can use == to compare strings, or check whether a character is in a list (such as ["a", "A"]) of characters to be treated similarly. To check whether an item is in a list, use an in expression of the form item in list.

Task: Write a function sorted_increasing that determines whether a list of numbers is sorted into increasing order.

NOTE: The Boolean type is written as bool in Python; True and False (with first letter capital) are the two boolean values.

HINT: The challenge here is to figure out how to track the previous number in the list. Think about how a variable could help you do that.

Task: Write a function first_five_pos that takes a list and returns a list of the first five positive numbers from the input list.


CHECKPOINT: Call over a TA once you reach this point. If one partner programmed the last few functions, the other partner should program for the next task.


Part 3: Sorting Lists in Python

Basic Sorting (click to expand)

Python has a useful and customizable sorting function that operates on lists. In this section, we will explore how to use it.

The simplest version of the function is sorted(lst: list), which takes in a list and sorts it in ascending order.

  • Lists of integers are sorted numerically
  • Lists of strings are sorted alphabetically
  • Lists of booleans are sorted with all Falses before all Trues

For example:

>>> sorted([1,5,3,1])
[1,1,3,5]

>>> sorted(["bc", "abc", "d", "aa", "aaa"])
['aa', 'aaa', 'abc', 'bc', 'd']

>>> sorted([False, True, True, False, True])
[False, False, True, True, True]

To sort a list in descending order, add the input reverse=True to the function call. Here are the examples from above but sorted in reverse:

>>> sorted([1,5,3,1], reverse=True)
[5,3,1,1]

>>> sorted(["bc", "abc", "d", "aa", "aaa"], reverse=True)
['d', 'bc', 'abc', 'aaa', 'aa']

>>> sorted([False, True, True, False, True], reverse=True)
[True, True, True, False, False]

NOTE: Notation like reverse=True is used for optional inputs to a function, a concept we did not see in Pyret. Since an optional input might not be provided, we need the <name>= part to indicate which optional parameter is being used.


Custom Sorting (click to expand)

In most cases, default sorting is enough. However, what if you want to sort a list in a specific, custom way? To do so, add the input key=my_fun where my_fun represents a function that takes in a single list element. my_fun is called on each element in the list, and its output determines the sort order.

Let's take a look at a concrete example. The following function takes in a string and returns its length:

def key_fun(elem: str):
    return len(elem)

With key_fun defined, we can do the following to sort the list by string length:

>>> sorted(["Buddy Valastro", "does", "love", "sorting", "lists"], key=key_fun)
['does', 'love', 'lists', 'sorting', 'Buddy Valastro']

(Note that since string length is an integer, sorted defaults to sorting the elements in increasing order)


Let's apply the ideas of sorting discussed above to the course list examples from earlier:
Task: Sort course_list in alphabetical order by department code.

Task: Sort course_list by course number, regardless of department code.


CHECKPOINT: Call over a TA once you reach this point. If one partner programmed for this task, the other partner should program for the next task.


Let's explore sorting on keys other than numbers. Imagine that we are playing a board game in which the words you come up with earn points (like Scrabble, if you know it). You want to pick the highest-scoring word out of a list of options. In this section, we'll do this with several different scoring functions to practice Python skills.

Another way to think about what's happening is to imagine mapping the key function on the list, sorting, and then undoing the mapping. In other words:

Step 1 (the original list): ["BuzzAldrin", "really", "loves", "sorting"]

Step 2 (the mapped list): [10, 6, 5, 7]

Step 3 (the sorted order): [5, 6, 7, 10]

Step 4 (the "un-mapped" list): ["loves", "really", "sorting", "BuzzAldrin"]

NOTE: Note that if multiple elements mapped to the same thing in this case, multiple words had the same number of letters they would stay in their original order (this is called stable sorting).

Let's try more complicated scoring

  • Vowels (including y): 1 point per vowel
  • Consonants: 2 points per consonant
    • Unless the consonant is z/q/x, in which case it is worth 5 points

Reminder: if you need to convert a string into a list, you can do that by writing list("my string") (for whatever string you have in mind)

Task: Write a function sort_by_score(lst : list) that takes in a list of strings and sorts them by their score (in descending order, i.e. highest to lowest) using the updated scoring system. Assume all strings in the list are only made up of alphabetical characters (no numerical, punctuation, spaces, etc.).

​​​​For example, "beep" has a score of 2+1+1+2=6 and `sort_by_score(["aaa", "aba", "z", "beep", "BOOP"])` should return `["beep", "BOOP", "z", "aba", "aaa"]`.

CHECKPOINT: Call over a TA once you reach this point.


Brown University CSCI 0111 (Fall 2022)
Do you have feedback? Fill out this form.