More Complex Functions

This discussion we're going to work on some more complicated exercises together to get more practice writing longer (or otherwise harder) functions in Python.

Exercise 1: Write a function reverse_dict that takes in a dictionary d and reverses it. Specifically, for every value v in the dictionary, we want a mapping in the new dictionary that is v: ks where ks is a set containing all the keys that map to v.

(solution below)

.

.

.

.

.

.

.

In [1]:
def reverse_dict(d):
    new_d = {}
    for v in d.values():
        s = {k for k, v2 in d.items() if v2 == v}
        new_d[v] = s
    return new_d

reverse_dict({'a': 1, 'b': 2, 'c': 2})
Out[1]:
{1: {'a'}, 2: {'b', 'c'}}

In your next homework, you'll be asked to work with a dictionary by treating it as a simple directed graph. That is, you can "travel" from a key to its corresponding value, which might appear again in the dictionary as something you can travel from.

Exercise 2: Write a function edges that takes in a dictionary d and returns a dictionary that for each element in the dictionary (key or value) associates it with the number of elements you can get either to or from it.

For example, if
graph = {'a': 'b', 'b': 'd', 'c': 'd'}
then
edges(graph) = {'a': 1, 'b': 2, 'c': 1, 'd': 2}
since a can reach b, b can be reached by a and can reach d, c can reach d, and d can be reached by b and c.

(solution below)

.

.

.

.

.

.

.

In [2]:
def edges(g):
    new_d = {}
    
    for k, v in g.items():
        if k in new_d.keys():
            new_d[k] += 1
        else:
            new_d[k] = 1
        if v != k: # don't count self edges twice
            if v in new_d.keys():
                new_d[v] += 1
            else:
                new_d[v] = 1
    return new_d

graph = {'a': 'b', 'b': 'd', 'c': 'd'}
edges(graph)
Out[2]:
{'a': 1, 'b': 2, 'd': 2, 'c': 1}

Higher-order functions can be pretty hard to reason about. Let's do some practice sorting lists to get a sense of how higher order functions might be used and then write one for ourselves.

Remember the Python method list.sort() has an optional parameter key. key is a function that takes in the elements of the list and returns a number (or any other value we can compare) and sorts based on this value.

Exercise 3: Given a list l1 of positive integers >= 10, sort them by their second digit. Then, given another list l2 of lowercase strings, sort them by the number of vowels ("a", "e", "i", "o", "u"). Lastly, given a third list l3, use a suitable function to randomize the list.

(solution below)

.

.

.

.

.

.

.

In [3]:
l1 = [190, 72, 81, 1000, 934]
l2 = ['pizza', 'ice cream', 'pasta']
l3 = [1, 2, 3, 4, 5]

import random

def count_vowels(x):
    # can also use x.count()
    total = 0
    for c in x:
        if c == 'a' or c == 'e' or c == 'i' or c == 'o' or c == 'u':
            total += 1
    return total

l1.sort(key = lambda x : int(str(x)[1]))
l2.sort(key = count_vowels)
l3.sort(key = lambda x : random.random())

print(l1)
print(l2)
print(l3)
[1000, 81, 72, 934, 190]
['pizza', 'pasta', 'ice cream']
[3, 2, 4, 5, 1]

Exercise 4: Write a function sort_comp that takes in a list l and a function comp. comp will take in two elements from the list a and b and return -1, 0, or 1. If
comp(a,b) < 0,
then assume a < b comp(a,b) > 0,
then assume a > b comp(a,b) == 0,
then assume a = b

(solution below)

.

.

.

.

.

.

.

In [4]:
def sort_comp(l, comp):
    new_l = []
    for elem in l:
        i = 0
        while i < len(new_l) and comp(elem, new_l[i]) >= 0:
            i += 1
        new_l.insert(i,elem)
    return new_l

l = [1,4,2,3,5]

def comp_fun(a,b):
    return a - b

sort_comp(l, comp_fun)
Out[4]:
[1, 2, 3, 4, 5]