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.

Maps and Filters in Python

Beep and Boop have grown super attached to you and your partner – woops. Now y’all are kind of inseparable. Like the dynamic duo. Or the three musketeers. Except there are four of you.
Anyway, Beep and Boop have decided to continue their studies (because being award-winning scientists isn’t enough). They enroll at Brown University and must register for classes, which is a scary feat for them. Luckily, they have been granted permission to register jointly and take all of their classes together, as well as register for as many courses as they’d like (they are special scientists, after all). This is their current course list:

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:

Beep and Boop got over-excited and have registered for so many courses that they can’t keep track of them anymore! Help Beep and Boop 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”. 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. Put the conversion code for a single letter into a helper function.

    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 obscure_test():
     test("lowercase & capital changes", obscure("hello my name is ELI"), "h3ll0 my nam3 1s 3L1")
     test("empty string", obscure(""), "")
     test("no change", obscure("what"), "what")
    
  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 sorted_increasing_test():
     test("increasing", sorted_increasing([1, 2, 3]), True)
     test("decreasing", sorted_increasing([3, 2, 1]), False)
     test("decrease at end", sorted_increasing([1, 2, 3, 2]), False)
    
  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:

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

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:

>>> sorted(["beep", "and", "boop", "love", "sorting", "lists"], key=key_fun)
['and', 'beep', 'boop', 'love', 'lists', 'sorting']

Beep and Boop recently signed up to go to a board game night event. Their absolute favorite board game is Boople (their variant of Scrabble), and 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): [“beep”, “and”, “boop”, “love”, “sorting”, “lists”]

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

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

Step 4 (the “un-mapped” list): [“and”, “beep”, “boop”, “love”, “lists”, “sorting”]

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.

  1. Sort course_list in alphabetical order by department code.
  2. Sort courses by course number, regardless of department code.

Beep and Boop 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

Beep and Boop had a blast at Board Game Night. Needless to say, Boople was a hit, and Beep and Boop 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 start their classes!