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

## TK GUI Drawing

• "TK" is the Python built-in way to draw (vs our CS106 DrawCanvas "Quilt" Way)
• We'll use TK drawing on HW7 a little
• In this way, you are writing a "real" Python program, not depending on our CS106A code
• Fortunately, the TK functions are nearly identical to the DrawCanvas functions
• (Details on HW7 handout)
• TK draw line:
`canvas.create_line(x1, y1, x2, y2)`
• TK draw String
`canvas.create_text(x, y, text='some text', anchor=tkinter.NW)`
Here `tkinter.NW` means the text is drawn with the x,y at its upper left (north-west)

## 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

• What we see: the foreach/result building is the same every time
• It's boilerplate
• What's different: how to compute the element we want

## lambda To The Rescue

• lambda solves this
• map does the boilerplate part
• lambda lets us specify the new elements we want, and none of the boilerplate

## 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

• (from prev lecture)
• lambda = short way to specify some code
• def - another way to specify some code
• lambda has: no name, one line, no "return"
• So lambda is convenient for very short code
• Use lambda for short code, def for longer code
• We'll see spots where that's just what we need shortly

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

• What is the parameter to this?
• For map/sorted
• One element from the list
• List of strings - one str
• List of ints - one int
• List of tuples - one tuple

## Custom Sort - Power Feature

• Python sorting has a lot of power in it
• Use lambda to guide the sorting
• This code feels powerful and dense
• More examples in section!

## Python Custom Sort - Food Examples

• Suppose I have len-3 food tuples, each with 3 parts:
• food = (name, tasty 1-10, healthy 1-10)
• e.g. food is how tasty it is
• Default sorted of tuples, first uses , then , ..
```>>> 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

• Goal: sort by tastiness
• Like drawing a circle around tasty values - sort by these!
• How to code it:
• For each element in list
• "Project out" a value to use for comparisons, here project out the tasty int
```[('radish', 2, 8), ('donut', 10, 1), ('apple', 7, 9), ('broccoli', 6, 10)]

#  Project out these values for sort-compare

[2, 10, 7, 6]
```
• Do comparisons with the projected list, but sort the upper list
• aka "proxy" sort-by strategy - specify sort-by values for comparison
• Q: how to project out these sort-by values?
• A: lambda ## Sort By Tasty - Lambda

• Say we want to sort by how tasty the foods are, how?
• 1. Call sorted() as usual
• 2. provide key=lambda to control sorting
• Lambda here takes one parameter - an elem from the list
• The lambda projects out the sort-by value to use for comparisons
• Project out the tasty int from the food tuple
• e.g. project tasty out of donut tuple:
`('donut', 10, 1) -> 10`
Sorting uses the projected values
• i.e. "Sort By This"
• What is the lambda for that?
```lambda food: food
```

Q: What is the parameter to the lambda?

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

## Food Sort Examples

• `sorted(lst)` - default sort, increasing order
• `sorted(lst, reverse=True)` - reverse option, decreasing order
• examples below
sort by tasty (reverse .. highest value first)
sort by healthy
sort by tasty * healthy
```>>> 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)
[('radish', 2, 8), ('broccoli', 6, 10), ('apple', 7, 9), ('donut', 10, 1)]
>>>
>>> sorted(foods, key=lambda food: food, reverse=True)  # by tasty, reverse=True
[('donut', 10, 1), ('apple', 7, 9), ('broccoli', 6, 10), ('radish', 2, 8)]
>>>
>>> sorted(foods, key=lambda food: food, reverse=True)  # by healthy
[('broccoli', 6, 10), ('apple', 7, 9), ('radish', 2, 8), ('donut', 10, 1)]
>>>
>>> sorted(foods, key=lambda food: food * food, reverse=True) # by tasty*healthy
[('apple', 7, 9), ('broccoli', 6, 10), ('radish', 2, 8), ('donut', 10, 1)]
>>>
```

## Sorted vs. Min Max

• What if we just want the most tasty food?
• Sorting n things is kind of expensive
• Could sort, take the last item - expensive
• Use max(), max takes a key=lambda just like sorted()
• e.g. pull out most or least tasty food - change "sorted" to "max"
```>>> foods = [('radish', 2, 8), ('donut', 10, 1), ('apple', 7, 9), ('broccoli', 6, 10)]
>>> max(foods)     # uses  by default - tragic!
>>>
>>> sorted(foods, key=lambda food: food)
[('radish', 2, 8), ('broccoli', 6, 10), ('apple', 7, 9), ('donut', 10, 1)]
>>>
>>> max(foods, key=lambda food: food)
('donut', 10, 1)
>>> min(foods, key=lambda food: food)
```
-Pulling out one element is much faster than sorting all n elements

## Python Custom Sort String Examples

• Default sorted() uses "<"
• With strings, < places uppercase before lowercase
```>>> strs = ['coffee', 'Donut', 'Zebra', 'apple', 'Banana']
>>> sorted(strs)
['Banana', 'Donut', 'Zebra', 'apple', 'coffee']
```
• Strategy: for each elem, project out a value for comparisons
• For this problem, project out lowercase version of each str to ignore case
• Sorted lambda takes in one elem from list - in this case 1 string
• e.g. `lambda s: s.lower()`
```>>> 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

• Say we have a "counts" style dict, counting how many times various chars appear in a text
• Below is counts.items(), list of (key, count) pairs
```>>> items = [('z', 1), ('a', 3), ('e', 11), ('b', 3), ('c', 2)]
```
• Questions we could ask of the items - could do some of these as You Try It
• 1. How to sort items in increasing order by char (easy!)
• 2: How to sort items in increasing order by count?
• 3. How to sort items in decreasing order by count?
• 4: How to access the pair with the largest count?
```>>> 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)   # project out pair
[('z', 1), ('c', 2), ('a', 3), ('b', 3), ('e', 11)]
>>>
>>> sorted(items, key=lambda pair: pair, reverse=True)
[('e', 11), ('a', 3), ('b', 3), ('c', 2), ('z', 1)]
>>>
>>> max(items, key=lambda pair: pair)
('e', 11)
```

## Wordcount2 - Lambda - You Try It

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
```
• Look at print_top() function
• Recall: dict.items() - random order of word/count pairs
`[('sister', 12), ('rabbit', 5), ...]`
• Need to order the pairs
• Use sorted/lambda
• This code is incredibly short but powerful

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, reverse=True) # 1. Sort largest count first
for word, count in items[:n]:                                 # 2. Slice to grab first N
print(word, count)
```