Today: lambda, lambda vs. def, custom sorting with lambda, dict.items, wordcount output

Lambda - Code as a Parameter

Countless times, you have called a function and passed in some data for it to use. The function name is the verb, and the parameters are extra nouns to guide the computation:

e.g. "draw_line" is the verb, with these int coords

canvas.draw_line(0, 0, 100, 50, color='red')

With lambda, we open up a new category, passing in code as the parameter for the function to use, e.g. with map():

map(lambda s: s.upper() + '!',
    ['pass', 'code']) ->
 ['PASS!', 'CODE!']

Having an easy way to pass code between functions can be very handy.

Lambda - You The Power Hacker

Lambda is powerful feature, letting you express a lot of computation in very little space. As a result, it's weird looking at first, but when it clicks, you should feel like a Power Hacker when you wield it. Kind of a superpower.

These are well suited to little in-class exercises .. just one line long. Not easy, but they are short!

Temps Lambda Exercise

Say we have "temps" temperature data - a list of (low, high) measurements for a series of cities. Paste the temps= line into the interpreter and we'll try the following questions.

>>> temps = [(51, 65), (65, 80), (45, 64), (52, 66)]

Answer these questions with 1-liners - follow along or try it yourself.

1. Sort the list in increasing order by the high temp. Try storing the sorted list in a variable. Note original temps list is unchanged by calling sorted().

2. Pick out the temp pair with the highest high value. Use max() + lambda to pick out the one element. Computing max()/min() is faster than sorted() since sorting the whole list is more costly than finding one element.

3. Compute a list of just the low values list(map(lambda ..))

4. Recall the built-in function sum([1, 2, 3]) -> 6. What is an expression to compute the sum of the low values?

5. What is the average of the low values? Can be done with one line. Dense code is satisfying. However .. at some point it's so dense as to be hard to read. This may be approaching that point.

Solutions...

>>> temps = [(51, 65), (65, 80), (45, 64), (52, 66)]
>>> 
>>> sorted(temps, key=lambda temp: temp[1])
[(45, 64), (51, 65), (52, 66), (65, 80)]
>>> temps  # original unchanged
[(51, 65), (65, 80), (45, 64), (52, 66)]
>>>
>>> # store in var
>>> lst = sorted(temps, key=lambda temp: temp[1])
>>> 
>>> max(temps, key=lambda temp: temp[1])
(65, 80)
>>> 
>>> list(map(lambda temp: temp[0], temps))  # low vals
[51, 65, 45, 52]
>>> list(map(lambda temp: temp[1], temps))  # high vals
[65, 80, 64, 66]
>>> 
>>> sum(map(lambda temp: temp[0], temps))   # sum of lows
213
>>> # average low value - all on one line
>>> sum(map(lambda temp: temp[0], temps)) / len(temps)
53.25

Lambda Exercises

> lambda-1 section to start

This one shows that the input and output types don't need to be the same.

int_to_str()

> int_to_str()

Given list of ints nums. Return a list of strings, where each int has parenthesis added, so 3 becomes the string '(3)'. Note that str(n) converts an int to a str.

[13, 42, 0] -> ['(13)', '(42)', '(0)']

Solution

def int_to_str(nums):
    return map(lambda n: '(' + str(n) + ')', nums)

min_x()

> min_x()

Given a non-empty list of len-2 (x, y) tuples. What is the leftmost x among the tuples? Return the smallest x value among all the tuples, e.g. [(4, 2), (1, 2) (2, 3)] returns the value 1. Solve with a map/lambda and the builtin min(). Recall: min([4, 1, 2]) returns 1

[(4, 2), (1, 2), (2, 3)]  -> 1

Solution

def min_x(points):
    return min(map(lambda point: point[0], points))
    # Use map/lambda to form a list of
    # just the x coords. Feed that into min()

Lambda vs. Def

Lambda and def are similar:

def double(n):
    return 2 * n

Equivalent lambda

lambda n: 2 * n

Use Lambda For Everything?

Should you just use lambda for everything? Not at all! Lambda is good for cases where the code is really short. Your program will have situations like that sometimes, and lambda is great for that. But def can do many things lambda cannot.

Def Features

Def vs. Lambda

map/def Example - map_parens()

> map_parens()

In lambda1, see the map_parens() problem.

['xx(hi)xx', 'abc(there)xyz', 'fish'] ->
  ['hi', 'there', 'fish']

map_parens() Solution

Solution Code. map() works fine with "parens" by name

def parens(s):
    left = s.find('(')
    right = s.find(')', left)
    
    if left == -1 or right == -1:
        return s
    return s[left + 1:right]


def map_parens(strs):
    return map(parens, strs)

Look at wordcount.py - Whole Program

Look at wordcount.zip example

Look at the wordcount.py file, explain lines we have skipped over earlier

#!/usr/bin/env python3

"""
Stanford CS106A WordCount Example
...words...
"""

import sys


def clean(s):
   ...

.. many defs ..

def main():
    args = sys.argv[1:]

    if len(args) == 1:
        # filename
        counts = read_counts(args[0])
        print_counts(counts)


if __name__ == '__main__':
    main()

1. #!/usr/bin/env python3

2. Triple-Quote Words

This text at the top of the file is its an explanation of what the files is and how to use it, stored in triple quotes.

Aside: triple quotes can contain a multi-line string literal, e.g.:

s = """line 1
line 2
line 3
"""

# now s is 'line 1\nline 2\nline 3\n'

3. import sys

4. main() + boilerplate + args

Say we call the program like this - put print() + return in main() to see what's going on.

$ python3 wordcount.py poem.txt

Look at sys.argv value in main(). This is the value telling what's on the command line. Put in print(sys.argv) + return in main() to see its value.

Observe, run the program as python3 wordcount.py poem.txt. Then argv is ['wordcount.py', 'poem.txt']

So argv has the args we want, but includes the name of our program as [0]

Therefore, we set args with this standard line: args = sys.argv[1:]

This is just a slice to skip over the name of the program, setting args to be a list of the command line args after the program name. So now args == ['poem.txt'] &mdash we had one file named on the command line, and here it is in the args list. So in main(), the filename to read is in args[0]

The upshot of all this is that we always use the following line in main(), setting up the variable to args contain the command line arguments: args = sys.argv[1:]

Later: look at wordcount -top exercise