**Homework 6: Joining Splits and Other Things** **Reed CSCI 121 Fall 2022** *complete the exercises by 9am on 10/11* This week's lab exercises are a mix of problems that have you work with Python lists and with Python dictionaries. We ask you to write a function for each of them. Let me just remind you that lists and dictionaries are both *passed by reference* to a function. This means that it is possible to change the values stored in them, and to increase and decrease their size by applying certain operations to them, within a function's code. These changes can be noticed by the client code that uses the function that changes them. An example of this comes from lecture. We wrote the code for `rotate_right` as below ~~~ Python def rotate_right(xs): if len(xs) > 1: last = xs.pop() xs.insert(0,last) ~~~ The function does not return anything, but it takes the list `xs` and modifies it by performing a `pop` to remove the last value in the list, followed by an `insert` that puts that value at the front. Here it is in action: ~~~ none >>> some_values = [11, 7, 17, -3, 5] >>> rotate_right(some_values) >>> some_values [5, 11, 7, 17, -3] ~~~ Calling the function with a list has the effect of "rotating" the contents of the list it is passed. Because of this ability to modify lists and dictionaries, you need to pay careful attention as to whether we want your function to change the data structure it is given, and also whether or not we want you to build and return a new data structure. (#) Problems (##) `[HW6 P1]` make even Write a function `def make_even(xs)` that, when given a list of integers `xs`, modifies the elements of the list to make them all divisible by 2. If a list element is even, it should not be changed. If a list element is odd, it should be decremented by one. The `make_even` function should modify the list it is given, and not return a list back. Have it return the value `None`, and have the list be modified as a *side effect* of being executed. Here is an example of its behavior: ~~~ none >>> ys = [1, 3, 2, -7, 0, -8, 11] >>> zs = [10, 0, 1, 1, -1, 100] >>> make_even(ys) >>> make_even(zs) >>> ys [0, 2, 2, -8, 0, -8, 10] >>> zs [10, 0, 0, 0, -2, 100] ~~~ (##) `[HW6 P2]` remove duplicates Write a function `def remove_duplicates(xs)` that takes a list of integers and modifies it so that it has the same set of integer items, but with no items duplicated. For example: ~~~ none >>> zs1 = [100, 0, 1, -1, 1, 100] >>> remove_duplicates(zs1) >>> zs1 [100, 0, 1, -1] >>> zs2 = [100, 100, 100, 100] >>> remove_duplicates(zs2) >>> zs2 [100] >>> zs3 = [] >>> remove_duplicates(zs3) >>> zs3 [] ~~~ The list that results should have the same ordering as the ordering of the first occurrences of each integer as they appeared before the duplicates were removed. (##) `[HW6 P3]` diagonal table Below is a list of lists, where each list has the same length: ~~~none [[1, 2, 0, 1], [5, 3, 1, 4], [8, 7, 1, 5]] ~~~ The "outer" list has three elememts in it, and each such element is a four-element list. If that list is called `table`, then accessing the list slot with the value 7 can be done with the notation ~~~none table[2][1] ~~~ because it is held in the last of the three lists, and it is item 1 of that last list. We can think of a lists of lists as a table. Below I've laid out its contents to suggest its tabular form: ~~~none [[1, 2, 0, 1], [5, 3, 1, 4], [8, 7, 1, 5]] ~~~ It has three rows and four columns. We see that 7 is in row number 2 (the top row is row 0), and in column 1 (the left column is column 0). In general, we can access the item in row `r` and column `c` with the notation ~~~none table[r][c] ~~~ Write a function `def diagonal_table(size)` that, when given a positive integer `size`, returns a list of lists that is a table with the same number of rows and columns in it, with each dimension of length `size`. It should be full of 0s, excepting that all the entries along the table's diagonal have the value 1. For example ~~~none >>> t = diagonal_table(4) >>> t [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] ~~~ Laying the result out as a table, we get ~~~none [[1, 0, 0, 0], [0, 1, 0, 0], [0, 0, 1, 0], [0, 0, 0, 1]] ~~~ Note that all the non-zero entries are `t[i][i]`, all the locations where the row number `i` is the same as the column number `i`. (##) `[HW6 P4]` word count A Python string made up of words separated by spaces can be split into a list of strings made up of the words as they were strung together. To do so, you use a string operation called `split`, passing it the space character `' '` like so: ~~~ none >>> sentence = "the quick brown fox jumped over the lazy dogs" >>> print(sentence) the quick brown fox jumped over the lazy dogs >>> words = sentence.split(' ') >>> words ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dogs'] >>> "hello there".split(' ') ['hello', 'there'] ~~~ Write a function `def word_count(s)` that takes a string `s` and gives back a dictionary that gives an integer count of the number of times each word appears in `s`. There should be a dictionary entry for every word that appears in `s` and each should have a value of 1 or more that corresponds to the number of times that word appears. ~~~ none >>> word_count("hello there") {'hello':1, 'there':1} >>> word_count("buffalo buffalo buffalo buffalo buffalo buffalo buffalo buffalo") {'buffalo':8} >>> word_count("b a n a n a s") {'a': 3, 'b':1, 'n':2, 's':1} >>> word_count("i am who i am as far as i know" {'as':2, 'am':2, 'far':1, 'i':3, 'know':1, 'who':1} ~~~ You can assume that the string consists of only the letters from `'a'` to `'z'` and spaces, that there are no spaces at the front or end of the string, and that there are never two consecutive spaces. Note that the ordering of dictionary entries produced by Python's report does not matter. Dictionary entries, unlike list entires, are unordered. This means that ~~~ none >>> word_count("hello there") {'there':1, 'hello':1} ~~~ is just as correct a result as the result listed before. (##) `[HW6 P5]` replace using A list of strings can be joined together into a single string by using the string `join` operation as shown below ~~~ none >>> words = ['the', 'quick', 'brown', 'fox', 'jumped', 'over', 'the', 'lazy', 'dogs'] >>> sentence = ' '.join(words) >>> sentence 'the quick brown fox jumped over the lazy dogs' >>> ' '.join(['ba','nan','a','s']) 'ba nan a s' ~~~ Write a function `def replace_using(s,d)` that takes a string `s` that is a space-separated string of words like in problem `[HW6 P4]`. It also takes a dictionary of word substitutions. It returns the string that results from applying the replacements in `d` to the string `s`. ~~~ none >>> replace_using("i am who i am as far as i know", {'i':'am','am':'was','know':'now'}) 'am was who am was as far as am now' >>> replace_using("i am who i am as far as i know", {'i':'as', 'as':'yass'}) 'as am who as am yass far yass as know' ~~~ Notice that the replacement occurs as a result of one pass through the string. In the second example, we replaced each `'i'` in the original string with `'as'` and we replaced each `'as'` in the original string with `'yass'`. We didn't then replace the `'as'` words that were placed by that one pass with any `'yass'` words with some second pass. You can assume that the entry words and their replacements are made up of one or more of the letters from `'a'` to `'z'`. (##) `[HW6 P6]` inverse Write a function `def inverse(d)` which takes a dictionary `d` and returns its "inverse" dictionary, a dictionary mapping values of entries in the given dictionary to lists of keys in the given dictionary. The keys of the dictionary it returns are the the values of the original dictionary and whose entries are lists of keys which mapped to that value in the original dictionary. For example: ~~~ none >>> inverse({'a':'b', 'c':'b', 'cats':'rest', 'world':'hello'}) {'hello':['world'], 'rest':['cats'], 'b':['a', 'c']} >>> inverse({}) {} >>> inverse({'a':'1','b':'1','c':'1','d':'1','e':'1','f':'1','g':'1'}) {'1':['e', 'c', 'f', 'g', 'a', 'd', 'b']} ~~~~ Note that the ordering of dictionary entries produced by Python's report does not matter. Dictionary entries, unlike list entires, are unordered. This means that ~~~ none >>> inverse({'a':'b', 'c':'b', 'cats':'rest', 'world':'hello'}) {'b':['a', 'c'], 'hello':['world'], 'rest':['cats']} ~~~ is just as correct a result as the result listed before. The ordering of the listed values in the inverse dictionary also does not matter. That means that the entry for `'b'` could instead have been `['c', 'a']` in the result reported just above.