Today: TK GUI drawing, lambda/def, map-lambda, custom sorting with lambda

TK GUI Drawing

Map/Lambda vs. Boilerplate

Suppose I ask you to compute a new list, doubling each number

result = []
for num in nums:
    result.append(num * 2)
return result

Now I want one where each number is times -1

result = []
for num in nums:
    result.append(num * -1)
return result

Now I want one where each number is plus 100

result = []
for num in nums:
    result.append(num + 100)
return result

Foreach Pattern

lambda To The Rescue

The above three are now 1-liners:

>>> list(map(lambda num: num * 2, nums))
[2, 4, 6, 8]
>>> list(map(lambda num: num * -1, nums))
[-1, -2, -3, -4]
>>> list(map(lambda num: num + 100, nums))
[101, 102, 103, 104]

Lambda / Def Equivalent

The code of a def can be used instead of a lambda, like this:

>>> nums = [1, 2, 3, 4]
>>>
>>> list(map(lambda num: num * 2, nums))  # lambda
[2, 4, 6, 8]
>>>
>>> 
>>> def double(n):            # do it as a def
...     return n * 2
... 
>>> list(map(double, nums))   # specify "double", no parens!
[2, 4, 6, 8]

Where we would put a lambda, just put the name of the function. This is the rare case of referring to a function by name, but not putting () after it. The () calls the function, but here we want to identify the function by name but not call it.

If the code you want is several lines long, expressing it in a def is a better approach. Lambda's are best for truly short bits of code.

Next step: I need to show you a context where this ability to spec little bits of code is useful. We'll do that next.

Most Common Lambda Questions


Custom Sort - Power Feature

Python Custom Sort - Food Examples

>>> foods = [('radish', 2, 8), ('donut', 10, 1), ('apple', 7, 9), ('broccoli', 6, 10)]
>>> 
>>> sorted(foods)
[('apple', 7, 9), ('broccoli', 6, 10), ('donut', 10, 1), ('radish', 2, 8)]
>>> 

Python Custom Sort Lambda

[('radish', 2, 8), ('donut', 10, 1), ('apple', 7, 9), ('broccoli', 6, 10)]

#  Project out these values for sort-compare

[2, 10, 7, 6]
alt: project out len-4 list of tasty values from food tuples

Sort By Tasty - Lambda

lambda food: food[1]

Q: What is the parameter to the lambda?

A: One elem from the list (similar to map)

Food Sort Examples

>>> foods = [('radish', 2, 8), ('donut', 10, 1), ('apple', 7, 9), ('broccoli', 6, 10)]
>>> # sort by tasty - project out the tasty int - radish first, donut last
>>> sorted(foods, key=lambda food: food[1])
[('radish', 2, 8), ('broccoli', 6, 10), ('apple', 7, 9), ('donut', 10, 1)]
>>> 
>>> sorted(foods, key=lambda food: food[1], reverse=True)  # by tasty, reverse=True
[('donut', 10, 1), ('apple', 7, 9), ('broccoli', 6, 10), ('radish', 2, 8)]
>>> 
>>> sorted(foods, key=lambda food: food[2], reverse=True)  # by healthy
[('broccoli', 6, 10), ('apple', 7, 9), ('radish', 2, 8), ('donut', 10, 1)]
>>> 
>>> sorted(foods, key=lambda food: food[1] * food[2], reverse=True) # by tasty*healthy
[('apple', 7, 9), ('broccoli', 6, 10), ('radish', 2, 8), ('donut', 10, 1)]
>>>

Sorted vs. Min Max

>>> foods = [('radish', 2, 8), ('donut', 10, 1), ('apple', 7, 9), ('broccoli', 6, 10)]
>>> max(foods)     # uses [0] by default - tragic!
('radish', 2, 8)
>>> 
>>> sorted(foods, key=lambda food: food[1])
[('radish', 2, 8), ('broccoli', 6, 10), ('apple', 7, 9), ('donut', 10, 1)]
>>> 
>>> max(foods, key=lambda food: food[1])
('donut', 10, 1)
>>> min(foods, key=lambda food: food[1])
('radish', 2, 8)
-Pulling out one element is much faster than sorting all n elements

Python Custom Sort String Examples

>>> strs = ['coffee', 'Donut', 'Zebra', 'apple', 'Banana']
>>> sorted(strs)
['Banana', 'Donut', 'Zebra', 'apple', 'coffee']
>>> strs = ['coffee', 'Donut', 'Zebra', 'apple', 'Banana']
>>> 
>>> sorted(strs, key=lambda s: s.lower())    # not case sensitive
['apple', 'Banana', 'coffee', 'Donut', 'Zebra']
>>> 
>>> sorted(strs, key=lambda s: s[len(s)-1])  # by last char
['Zebra', 'Banana', 'coffee', 'apple', 'Donut']
>>> 

All Together - Dict Count + Sorted

A common case is that your load up a dict with your data. Then use sorted() on d.items() to order the output the way you want.

Sorted vs. Dict Count Items

>>> items = [('z', 1), ('a', 3), ('e', 11), ('b', 3), ('c', 2)]
>>> items = [('z', 1), ('a', 3), ('e', 11), ('b', 3), ('c', 2)]
>>> 
>>> sorted(items)
[('a', 3), ('b', 3), ('c', 2), ('e', 11), ('z', 1)]
>>> 
>>> sorted(items, key=lambda pair: pair[1])   # project out pair[1]
[('z', 1), ('c', 2), ('a', 3), ('b', 3), ('e', 11)]
>>> 
>>> sorted(items, key=lambda pair: pair[1], reverse=True)
[('e', 11), ('a', 3), ('b', 3), ('c', 2), ('z', 1)]
>>> 
>>> max(items, key=lambda pair: pair[1])
('e', 11)

Wordcount2 - Lambda - You Try It

> wordcount2.zip

This is a version of our earlier wordcount example, but modified to take a "-top" command line argument like this, which lists the N most common words in a text

$ python3 wordcount.py poem.txt 
are 2
blue 2
red 2
roses 1
violets 1
$
$ python3 wordcount-solution.py -top 10 alice-book.txt 
the 1639
and 866
to 725
a 631
she 541
it 530
of 511
said 462
i 410
alice 386

print_top() Solution Code

def print_top(counts, n):
    """
    Given counts dict and int N, print the N most common words
    in decreasing order of count
    the 1045
    a 672
    ...
    """
    items = counts.items()
    # Could print the items in raw form, just to see what we have
    #print(items)
    pass
    # Your code - my solution is 3 lines long, but it's dense!
    # Sort the items with a lambda so the most common words are first.
    # Then print just the first N word,count pairs with a slice
    items = sorted(items, key=lambda pair: pair[1], reverse=True) # 1. Sort largest count first
    for word, count in items[:n]:                                 # 2. Slice to grab first N
        print(word, count)