Today: lambda extras, floating point, the image logic, bluescreen, Ghost demo

1. Lambda Extra Material

Here is some answers about lambda from last lecture. This just extra material, not required for the course.

How to Manually Call A Lambda?

A "def" introduces a name for a function - basically the function name points to the code. We can do the same thing manually, = assigning a lambda into a variable like this:

>>> f = lambda n: n * 2
>>>
>>> f   # what is f?
<function <lambda> at 0x7fabfd4b6ee0>

The above is essentially what def does - set up some code and set a variable name to point to it. You can then call the lambda using parenthesis to hold the params, just like a regular function:

>>> f(2)
4
>>> f(100)
200

This is not normally how you call your function code, so we don't want to dwell on this example. However, the example does show how Python has a uniform idea of some code in memory, and a name pointing to that code.

How To Call a Lambda of 2 Params?

By far the most common map/lambda form is a lambda of 1 parameter mapped over a linear collection of elements. That said, here is how you do it for multiple parameters:

Given a lambda of 2 params, map() will take 2 lists of the same length, drawing the first param from the first list, the second param from the second list:

>>> list(map(lambda a, b: a + b, [1, 2, 3], [4, 5, 6]))
[5, 7, 9]

Map() calls the lambda until it reaches the end of the shortest list, then stops. This could be a source of subtle bugs if the input lists are not the same length - e.g. the 6 is silently dropped in this example:

>>> list(map(lambda a, b: a + b, [1, 2], [4, 5, 6]))
[5, 7]

Do I need to list() the map() Output?

The output of map() is not a list. It is an "iterable" which is list-like, but does not have all the features of a list. The iterable is cheaper to construct than a list, which is why Python does this. The iterable can be fed into another map(), or used in a foreach, or fed into a function like sorted() or min() or max(). So if you are going to use the map() output immediately like that, you do not need to do anything extra, and it will run the quickest:

>>> iter = map(lambda n: n * 2, [1, 2, 3, 4, 5])
>>> for num in iter:
...   print(num)
... 
2
4
6
8
10

However, if you need to access an element with [] or do a slice, or evaluate len(), then the iterable will not work. In that case, use list() to convert the iterable to a list and then everything should work. Do not worry about creating a list() out of an iterable; it's not that costly.

>>> iter = map(lambda n: n * 2, [1, 2, 3, 4, 5])
>>> iter[0]           # [0] does not work, it's not a list
TypeError: 'map' object is not subscriptable
>>> 
>>> nums = list(iter)  # fix: make a list out of it
>>> nums
[2, 4, 6, 8, 10]
>>> nums[0]
2

Two Math Systems, "int" and "float" (Recall)

# int
3  100  -2

# float, has a "."
3.14  -26.2  6.022e23

Math Works

Mixed Case: int + float = float "promotion"

>>> 1 + 1 + 1
3
>>> 1 + 1 + 1.0  # float promotion
3.0
>>> 3.14 * 2
6.28
>>> 3.0 * 3
9.0
>>> 3.14 * 2 + 1
7.28

float() int() Conversions

-Use float() to convert str to float value, similar to int()
>>> float('3.14')   # str -> float
3.14
>>> int(3.14)       # float -> int, truncation
3
>>> int('16')
16
>>> int('3.14')
ValueError: invalid literal for int() with base 10: '3.14'

Float - One Crazy Flaw - 1/10

Crazy Flaw Demo - Adding 1/10th

>>> 0.1
0.1
>>> 0.1 + 0.1
0.2
>>> 0.1 + 0.1 + 0.1    # this is why we can't have nice things
0.30000000000000004
>>> 
>>> 0.1 + 0.1 + 0.1 + 0.1
0.4
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.5
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.6
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.7
>>> 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1 + 0.1
0.7999999999999999     # here the garbage is negative

Another example with 3.14

>>> 3.14 * 3
9.42
>>> 3.14 * 4
12.56
>>> 3.14 * 5
15.700000000000001   # d'oh

Summary: float math is slightly wrong

Why Must We Have This Garbage?

The short answer, is that with a fixed number of bytes to store a floating point number in memory, there are some unavoidable problems where numbers have these garbage digits on the far right. It is similar to the way the number 1/3 is not possible to write it out precisely as a decimal number.

Crazy, But Not Actually A Problem

Must Avoid One Thing: no ==

>>> a = 3.14 * 5
>>> b = 3.14 * 6 - 3.14
>>> a == b   # Observe == not working right
False
>>> b
15.7
>>> a
15.700000000000001
>>> abs(a-b) < 0.00001
True

Float Conclusions

Demo HW7 Ghost