Today: tuples, dict.items() output pattern, lambda 1-2-3, map, one-liners

Each Type has a Char

Notice that each new type has a char that marks it literal form in the code:

Tuples

For more detail see guide Python Tuples

>>> t = ('smith', 'alice', 53635252)
>>> 
>>> len(t)
3
>>> t[0]
'smith'
>>> t[2]
53635252
>>> t[0] = 'xxx'
TypeError: 'tuple' object does not support item assignment
>>>

When To Use Tuple? (vs. List)

# Tuple Examples

# Store sunet and id number together
  ('sally22', 123456)

# Store 3 float x,y,z coordinates all together
  (4, 5.2, 6.1)

# Store an invoice id # and a list of the product
# id # that are in the invoice
  (233533, [22345, 12234, 68466])

When To Use List?

# List examples

# Store many urls - list of strings
  ['http://foo.com', 'http://kitten.org/bleh', ...]

# Store many weights - list of floats
  [34.5, 12.0, 16.8, ...]

# Store many filenames - list of strings
  ['poem.txt', 'jokes.html', ... ]

Tuple Optional Parenthesis

It's possible to omit the parenthesis when writing a tuple. We will not do this in CS106A code, but you can write it if you like and it is allowed under PEP8. We will write our code more spelled-out, showing explicitly when creating a tuple.

>>> t = 1, 4     # This works
>>> t
(1, 4)
>>> t = (4, 5)   # I prefer it spelled out like this
>>> t            # PEP8 says parenthesis optional
(4, 5)

Tuple Assignment = Shortcut

Here is a neat sort of trick you can do with a tuple. This is a shortcut for the use of =, assigning multiple variables in one step. This is just a little trick, not something you need to use.

>>> (x, y) = (3, 4)
>>> x
3
>>> y
4

Tuple == Compare Multiple

Comparing with == works with tuples. This can be used to compare multiple values in one step — the example below checks both x and y with one comparison:

>>> x = 3
>>> y = 4
>>> 
>>> (x, y) == (3, 4)
True

Sorting With Tuples

>>> cities = [('ca', 'zebra'), ('ca', 'san jose'), ('tx', 'austin'), ('tx', 'aardvark'), ('ca', 'palo alto')]
>>> 
>>> sorted(cities)
[('ca', 'palo alto'), ('ca', 'san jose'), ('ca', 'zebra'), ('tx', 'aardvark'), ('tx', 'austin')]
>>> 
>>> sorted(cities, reverse=True)
[('tx', 'austin'), ('tx', 'aardvark'), ('ca', 'zebra'), ('ca', 'san jose'), ('ca', 'palo alto')]

Recall Dict Output: keys() values()

>>> d = {'a': 'alpha', 'g': 'gamma', 'b': 'beta'}
>>>
>>> d.keys()
dict_keys(['a', 'g', 'b'])
>>> sorted(d.keys())
['a', 'b', 'g']
>>>
>>> d.values()
dict_values(['alpha', 'gamma', 'beta'])
>>> 

Dict Output v1 - sorted(d.keys())

Say we have a dict loaded up with data

>>> d = {'a': 'alpha', 'g': 'gamma', 'b': 'beta'}

Here is the dict-print code we used before. This is a fine, standard pattern you can continue to use. But we are going to look at another way.

>>> for key in sorted(d.keys()):
...   print(key, '->', d[key])
... 
a -> alpha
b -> beta
g -> gamma

dict.items() - Another Way To Get Dict Data

>>> d = {'a': 'alpha', 'g': 'gamma', 'b': 'beta'}
>>> 
>>> d.items()          # (key, value) tuples - all the data
dict_items([('a', 'alpha'), ('g', 'gamma'), ('b', 'beta')])
>>> 

sorted(d.items())

>>> d.items()                      # random order
dict_items([('a', 'alpha'), ('g', 'gamma'), ('b', 'beta')])

Since sorting of tuples goes by [0] first, and [0] here is the key, the len-2 tuples are in effect sorted by key:

>>> sorted(d.items())              # sorted by key
[('a', 'alpha'), ('b', 'beta'), ('g', 'gamma')]

So it just sorts by the key. In practice, the sorting uses only key in each tuple, [0], never needing to look at [1], since the key values are all different.


d.items() Output Code, Almost

>>> for item in sorted(d.items()):
...     print(item[0], item[1])
... 
a alpha
b beta
g gamma

Dict Output Code v2 - d.items()

Recall the shortcut

>>> (a, b) = (6, 7)
>>> a
6
>>> b
7

Can use a similar shortcut inside a for loop. Since we are looping over tuples len-2, can specify two variables, and the loop unpacks each tuple into the variables, here key and value:

>>> for key, value in sorted(d.items()):
...     print(key, value)
... 
a alpha
b beta
g gamma

The above phrase is the v2 way to do dict output.

It's handy that the .items() gets all the data, so and we don't need to look up each value in the loop. Also it's nice that the key and value are unpacked into the variables in the loop so we have nice variable name for each.

That said, v2 is just an optional, compact way to write it. It's totally fine to write it the old v1 way using sorted(d.keys()):

>>> for key in sorted(d.keys()):
...     print(key, d[key])
... 
a alpha
b beta
g gamma

Wordcount Output

Recall the wordcount.zip example. The print_counts() function prints out an alphabetical list of all the words in a text, each with its count.

$ python3 wordcount.py somefile.txt
aardvark 1
anvil 3
boat 4
...

The function can be written either with .keys() or with .items(). Both approaches are fine and are commonly used in Python code. The .items() approach is a little shorter, but using slightly more esoteric Python features.

1. Dict Output - sorted(dict.keys())

Standard output code using d.keys() (v1):

def print_counts(counts):
    for word in sorted(counts.keys()):
        print(word, counts[word])

2. Dict Output - sorted(dict.items())

Or using d.items() (v2):

def print_counts(counts):
    for key, value in sorted(counts.items()):
        print(key, value)

What is a Function? What is Code?

We'll re-visit some of the most basic steps from week 1, enabling some neat techniques.

Map/Lambda - Advanced Hacker Features

Map - a short way to transform a list - handy, but not super important

Lambda - an important way to package some code. Today we'll use map() to explore how lambda works

Lambda - Dense and Powerful

Lambda code is dense. Another way of saying that it is powerful. Sometimes you feel powerful with computer code because the code you write is long. Sometimes you feel even a little more powerful, because the code you write is short!

alt: short code can be the most powerful

One-Liner Code Solutions

There is something satisfying about solving a real problem with 1 line of code. The 1-liner code is so dense, we'll will write it a little more deliberately. See how this works below!


1. What does def do?

Consider the following "double" def. What does this provide to the rest of the program? Does it run the code? No.

def double(n):
    return 2 * n

The def defines that name within the program, and setting it to point to that body of code. Later line can refer to the code by that name. The drawing below shows a form of this - the name "double" now points to this black-box of code that anybody can call.

alt: name double points to black box of code

def: name + code

2. What is the double() code?

The code in this story is essentially a black box. When run, it takes in one input, does its computation, and returns one output.

alt: code takes int in, int out

This does not depend on the name. The computation is the nature ofthe black box itself.

3. double() In The Interpreter

Normally we don't define a function in the interpreter, but here we'll do it to see the parts at work.

1. Define function

2. Call the function using its name

3. Ask interpreter what the value of "double" is

>>> def double(n):
...   return 2 * n
... 
>>>
>>> double(10)
20
>>> double(144)
288
>>>
>>> double
<function double at 0x7fe2caad0430>
>>>

The function name "double" points to the double code, printed as "0x7fe2caad0430" which is a bit obscure. It's the location in memory of the bytes of code that implement the function. When the function is called, the code at that location is run.

Aside: Memory locations are usually written in hexadecimal base-16, using the digits 0-9 and the letters a-f. The prefix "0x" at the start marks it as a hexadecimal number. This behind-the-scenes material comes up more in CS106B and CS107.

4. What map() Does: map(fn, list)

The map function takes in a function and a list of elements. For each element in the original list, map() calls the function, passing in one element from the original list.

Each function call to the function returns one result. Map() gathers all the results together into a new list. So if the original list is length 5, the function will be called 5 times, each call getting as input one element from the original list.

A visual of what map() does

map(double, [1, 2, 3, 4, 5]) -> [2, 4, 6, 8, 10]

alt: map double across list

We might say this example: "maps the double function over the list"

map() Example - double()

>>> # We have a "double" def
>>> def double(n):
...   return 2 * n
... 
>>>
>>> map(double, [1, 2, 3, 4, 5])
<map object at 0x7f9a25969910>     # why we need list()
>>> 
>>> list(map(double, [1, 2, 3, 4, 5]))
[2, 4, 6, 8, 10]
>>> 
>>> list(map(double, [3, -1, 10]))
[6, -2, 20]
>>> 
>>> list(map(double, range(20)))
[0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38]
>>> 

Note: map() Result + list()

map() Example - minus()

Create a minus() function that takes in n, returns -n.

>>> def minus(n):
...   return -1 * n
... 
>>> list(map(minus, [1, 2, 3, 4]))
[-1, -2, -3, -4]
>>> 
>>> list(map(minus, range(20)))
[0, -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, -11, -12, -13, -14, -15, -16, -17, -18, -19]
>>>

map() Example - exclaim()

Say we have an exclaim(s) function that takes in a string and returns it uppercase with an exclamation mark at the end. Use map to run exclaim() over a list of strings.

>>> def exclaim(s):
...   return s.upper() + '!'
... 
>>>
>>> list(map(exclaim, ['hi', 'woot', 'donut']))
['HI!', 'WOOT!', 'DONUT!']
>>> 
>>> list(map(exclaim, ['meh']))
['MEH!']

Thus Far - Function into Function

The map() function saves us some bookkeeping - it takes care of calling a function a bunch of times, once for each element in the input list, and giving us a list of the results. This is a first example of passing a function in as a parameter, which is an important advanced technique.


Pre Lambda

Structure of above examples without lambda:

1. def - Do the def first, define the name + code we want to use - e.g. double

2. map - Later, map() refers to function by name - double to get the code

The two steps here seem a little needless? Like could we just do this in one step?

Lambda - Function In One Step

Write an expression that represents the code of a function in one step - no def needed. The lambda lets us write the function object part of the def, but without any of the other stuff. The lambda is stripped down - take away everything not needed, leaving only the essential to type in.

Quote about "Perfection" - Stripped Down

"Perfection is achieved, not when there is nothing more to add, but when there is nothing left to take away." ― Antoine de Saint-Exupéry, Airman's Odyssey

Lambda History

Python Lambda

Lambda Syntax

Here is a lambda that takes in a number, returns double that number

lambda n: 2 * n

Lambda Black Box

It's like the lambda just defines the black box code, not bothering with giving it a name.

alt: lambda defines black box

Lambda works with map()

Want to double a bunch of numbers? Instead of a separate def, write the lambda inside the map() like this:

>>> list(map(lambda n: 2 * n, [1, 2, 3, 4, 5]))
[2, 4, 6, 8, 10]

alt: map lambda over numbers

How To Write Lambda - 1, 2, 3

Here is a lambda that takes in a number, and returns it doubled:

lambda n: 2 * n

1. "lambda"

Write the word "lambda"

2. Param:

The Importance Of The Parameter Name — n: vs. s:

Python itself does not have a rule that the parameter must be named a particular word like "n" or "s" for the code to work. In reality, the parameter can be named any word the programmer prefers. The parameter name is just the label Python uses to identify the function input, and then that name is used in the expression.

In the example above, the parameter is named "n", so then the word "n" is used in the expression: 2 * n

As a matter of keeping your own ideas straight though, it's important to give the parameter a sensible name, e.g. "n" in the input is a number, or "s" if the input is a string. This helps you write the expression correctly, as the name is a reminder of the type of data that is coming in when the code runs.

3. Expression

Lambda Examples in Interpreter

Do these in interpreter >>>. Just hit the up-arrow to change the output expression of the lambda.

>>> nums = [1, 2, 3, 4, 5]
>>> 
>>> # n * 10
>>> list(map(lambda n: n * 10, nums))
[10, 20, 30, 40, 50]
>>>
>>> # 100 - n
>>> list(map(lambda n: 100 - n, nums))
[99, 98, 97, 96, 95]
>>>
>>>
>>> # n % 2 -> 0 for even, 1 for odd
>>> list(map(lambda n: n % 2, nums))
[1, 0, 1, 0, 1]
>>> 

Lambda String Examples

Have a list of strings. Map a lambda over this list. What is the parameter to the lambda? One string. Whatever the lambda returns, that's what makes up the list of results.

1. Compute list of lowercase string forms

2. Compute list of first char of each string

3. Compute list of the lengths of each string. This shows that the output type can be anything — whatever type the lambda returns, that's what the result list is built out of.

>>> strs = ['Banana', 'apple', 'Zebra', 'coffee', 'Donut']
>>> 
>>> # 1. Lowercase form
>>> list(map(lambda s: s.lower(), strs))
['banana', 'apple', 'zebra', 'coffee', 'donut']
>>> 
>>> # 2. First char
>>> list(map(lambda s: s[0], strs))
['B', 'a', 'Z', 'c', 'D']
>>> 
>>> # 3. List len of each str
>>> list(map(lambda s: len(s), strs))
[6, 5, 5, 6, 5]
>>>

Cross-Type Lambda Example

This is a more complex example. The output elements do not need to be the same type as the input elements. Write a map/lambda that takes in a list of ints, and returns a list of strings, each string showing the original number * 1000 with '!' added at its end. Tricky: do the multiplication on the input int, then convert that to string and add on the '!'.

>>> nums = [1, 2, 3, 4, 5]
>>> list(map(lambda n: str(n * 1000) + '!', nums))
['1000!', '2000!', '3000!', '4000!', '5000!']

Remember: Density

Map/lambda is powerful, but the line is quite dense too. It's fine to slow down a little, write each bit of the line carefully.

Map Lambda - Examples

One liners!

> squared()

> shout()

Map Lambda - Exercises

These are true one-liner exercises. We'll do a few of them in class, and you can look at the others in the lambda1 section on the server.

Solve each of these with a 1 line map/lambda .. though it's a dense line!

You do not need to call list() for these. That was needed in the interpreter, but here just plain map() works, returning a list-like that works for most purposes.

For reference, here is the syntax for our "double" example:

map(lambda n: 2 * n, [1, 2, 3, 4, 5])

> negate

> power10

> first2x

> first_up