Lab 9: Python Practice

Our goal for lab this week is just to let you get comfortable with Python syntax and loops, as well as showing you how to use Python’s built-in function for sorting lists.

Setting up: Pycharm and pytest

Make sure that pycharm and pytest are both installed and set up. There is a setup guide posted on Campuswire. If you need help or are unsure of setup, please call over a TA.

Make a folder inside of PycharmProjects (mine is located at c:/Users/Nicole/PycharmProjects, but yours might be somewhere else) called Labs (you can name it something else – it doesn’t really matter what it’s called). This is where we will be putting all of our python code for our labs.

Starting from this lab, we recommend creating a python file called lab9.py (and subsequently lab10.py, lab11.py, etc.) inside of the Labs folder.

Maps and Filters in Python

ATTENTION!!! ALL MUFFINS HAVE BEEN STOLEN FROM THE BLUE ROOM (even the Bran ones…)!!!

This is a serious case, so Sherlock and Watson have been called in to catch the culprit and save the day. Since they’re professional sleuths, they’ve decided that their best plan of action is to pretend to be Brown students and find the thief from the inside. For Sherlock and Watson to believably pass as students, they must first enroll in some classes. Here is a list of the courses that they can enroll in this semester:

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

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:

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:

Shelock and Watson got over-excited and have registered for so many courses that they can’t keep track of them anymore! Help them sort through and organize their courses:

  1. 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 and produces a list of all courses with that number (regardless of department).

  2. 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.

  3. 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 
    
  4. Write a function compare_depts(dept1: str, dept2: str) that takes in the names of two departments and returns the name 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.

For each of the following problems, we have provided you with a set of tests. Copy and paste the tests into your code, and run them to ensure that it behaves as expected.

  1. 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.

    Tests:

    def test_obscure():
       assert obscure("hello my name is ELI") == "h3ll0 my nam3 1s 3L1", "lowercase & capital changes"
       assert obscure("") == "", "empty string"
       assert obscure("what") == "what", "no change"
    
  2. 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.

    Tests:

     def test_sorted_increasing():
         assert sorted_increasing([1, 2, 3]), "increasing"
         assert not(sorted_increasing([3, 2, 1])), "decreasing"
         assert not(sorted_increasing([1, 2, 3, 2])), "decrease at end"
    
  3. Write a function first_five_pos that takes a list and returns a list of the first five positive numbers from the input list.

    Tests:

    def test_first_five_pos():
       assert first_five_pos([1, 3, 5, 7, 9, 11, 13]) == [1, 3, 5, 7, 9], "all pos"
       assert first_five_pos([1, 3, 5, 7, 9]) == [1, 3, 5, 7, 9], "exactly five"
       assert first_five_pos([]) == [], "empty"
       assert first_five_pos([-3, -5, 2, -8, 3]) == [2, 3], "some neg"
    

As a reminder, to run your tests, you should use the terminal inside of PyCharm and run pytest <filename>. For example, I am coding in a file called lab9.py, so I will run pytest lab9.py to run my tests.
Pytest will run all of my functions that start with test_<func>(), such as test_first_five_pos().

The format of a pytest testing function should follow this template:

def test_<func_name>():
    assert <func_name>(<input>) == <expected_output>, "<comment about what is being tested>"

Sorting Lists in Python

Basic sorting

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.

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

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(["Sherlock", "does", "love", "sorting", "lists"], key=key_fun)
['does', 'love', 'lists', 'sorting', 'Sherlock']

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

Let’s apply this idea to the course list examples from earlier.

  1. Sort course_list in alphabetical order by department code.

  2. Sort courses by course number, regardless of department code.

Sherlock and Watson just signed up to compete in a Mystery-scrabble competition. While they are both excellent detectives, they happen to be very bad at this game and need your help to win. They want to use custom sorting to help them efficiently find the best word. In this version of Scrabble, point values for words are based solely on the length of a word, rather than the letters themselves. Given a list of possible words, they want to know which ones will secure them the most points – so instead of sorting the list alphabetically, they sort the list based on the length of each word in it.

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): [“Sherlock”, “does”, “love”, “sorting”, “lists”]

Step 2 (the mapped list): [8, 41, 42, 7, 5]

Step 3 (the sorted order): [41, 42, 5, 7, 8]

Step 4 (the “un-mapped” list): [“does”, “love”, “lists”, “sorting”, “Sherlock”]

NOTE: Notice that when multiple elements mapped to the same thing (in this case, multiple elements had four letters), they stayed in their original order. This is called stable sorting.

Sherlock and Watson have decided to up the ante by adding some new, more complex, rules to the game. Awarding a point value for a word has been adjusted to the following:

  1. Write a function sort-by-score(lst : list) that takes in a list of strings and sorts them by their score (in descending order), using the updated scoring system.

    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"].

Board Game Night Champs

Sherlock and Watson had a blast at Board Game Night. Needless to say, Mystery-scrabble was a hit, and Sherlock and Watson were declared the champions. They used the custom-sorting functions to win the game and figure out their courseload, and now they’re ready to take on the semester and find the missing muffins!