Today: lambda, lambda vs. def, custom sorting with lambda, dict.items, wordcount output
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 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!
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-1 section to start
This one shows that the input and output types don't need to be the same.
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()
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 and def are similar:
def double(n):
return 2 * n
Equivalent lambda
lambda n: 2 * n
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.
In lambda1, see the map_parens() problem.
['xx(hi)xx', 'abc(there)xyz', 'fish'] -> ['hi', 'there', 'fish']
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.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()
#!/usr/bin/env python3This 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'
import sysSay 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