Today: de-facto If, float, comprehensions
Aside: HP 35 Calculator
- Pre computer, had the slide rule
- Floating point calculation was just possible in 1970
- A large, expensive desktop machine
- 1970: Bill Hewlett said .. it would be great if you could switch this huge desktop calculator down so it fits in my pocket
- That was a huge jump in capability to ask for
- Silicon valley: genius or hubris! "audacity"
- They did it HP 35 calculator HP35 1972
- Moore's law - chips technology just getting started, enabled HP35
- The iPhone of its day
- You can buy one on eBay - red glowing numbers, LCD not invented yet
Shorthand: De-Facto If Test
- Optional shorthand
- Just a shorter way to write if/while tests
We have seen this a million times
if test:
print('test is true!')
Surprisingly, the test does not need to be a boolean True/False value. The test is flexible, so any string, int etc. can be the test.
Python has a de-facto True/False system, where all the "empty" values count as de-facto False:
# de-facto False:
None, '', 0, 0.0, [], {}
Any other value de-facto counts as True: a non-empty string, a non-zero int, a non-empty list. Many languages use this
anything-empty-is-false system.
The bool() function takes
any value and returns a formal bool False/True value, so that's a way to see the empty=False interpretation:
>>> bool(0)
False
>>> bool(0.0)
False
>>> bool('') # empty string - False
False
>>> bool([]) # empty list - False
False
>>> bool(None)
False
>>> bool(6) # non-zero int - True
True
>>> bool('hi') # non-empty string - True
True
>>> bool([1, 2]) # list of something - True
True
De-Facto Shorthand
Why does the de-facto system exist? It makes it easy to test, for example, for an empty string like the following. Testing for "empty" data is such a common case, it's nice to have a shorthand for it.
The wordcount program has a fragment like this:
s = clean(s)
if len(s) > 0:
print(s)
Re-write it shorter, since empty string is de-facto False:
s = clean(s)
if s:
print(s)
De-Facto Exercises
Re-write each if-test:
1. if int id
# id is an int id number, or 0 if not set
id = get_id()
if id != 0:
print(s)
1. Re-Written
This works because int 0 is de-facto False
id = get_id()
if id:
print(id)
2. if list
# Suppose lst is a list of strings, but it may be None.
# Want to print the list if it exists and is not empty.
if lst != None and len(lst) > 0:
print(lst)
2. Re-Written
This works, because None and empty list are both de-facto False
if lst:
print(lst)
Conclusions: (1) this is an optional but handy shorthand for writing tests. (2) Other languages use this empty=false scheme too.
Two Math Systems, "int" and "float" (Recall)
- Two Systems
- int and float are two different worlds
- "float" .. floating decimal point, moves around
- Float and int - each have their own area on the chip
- Look similar, but distinct
6 - the int six
6.0 - the float six
# int
3 100 -2
# float, has a "."
3.14 -26.2 6.022e23
Math Works
- Math works: + - * / min() max() for both int and float fine:
- i.e. mostly don't have to think about it
- Foreshadow:
Float mostly works easily
BUT Float has one crazy flaw .. revealed below
- Clickbait: you will not believe what float does
- Clear up some cases about int and float
1. int modulo %
The "modulo" or "mod" operator % is essentially the remainder after division. So (23 % 10)yields 3 — divide 23 by 10 and 3 is the leftover remainder. Modulo makes the most sense with positive numbers, so avoid negative numbers in modulo arithmetic.
- Use non-negative integers with modulo
- Mod by n: result is always in range 0..n-1
- Mod by 0 is an error, just like divide by 0
- When mod by n == 0: the division came out evenly
>>> 23 % 10
3
>>> 36 % 10
6
>>> 43 % 10
3
>>> 15 % 0
ZeroDivisionError: integer division or modulo by zero
>>>
>>> 40 % 10 # mod result 0 = divides evenly
0
>>> 17 % 5
2
>>> 15 % 5
0
Mod - Round Robin "Wrap Around"
Suppose we have a list of states. We want to associate each state with either 'red', 'green', or 'blue' for our graphics output.
>>> states = ['ca', 'tx', 'nj', 'mn', 'wa', 'or', 'mi', 'fl', 'nm', 'ny']
>>>
>>> colors = ['red', 'green', 'blue']
Say we decide to assign the colors round-robin, like this
i state color
0 ca 0 red
1 tx 1 green
2 nj 2 blue
3 mn 0 red # wrap around
4 wa 1 green
5 or 2 blue
6 mi 0 red
...
We need a number that is 0, 1, 2 to index into the colors list (aka 0..len(colors)-1). The raw index into the states list is too big.
Use Mod To Shrink N
Key insight: use mod to cut the value down to fit into 0, 1, 2, like this: i % 3, which is always 0, 1, 2.:
i i % 3
0 0
1 1
2 2
3 0
4 1
5 2
6 0
7 1
...
Here is a loop which shows all the states, indexes, and colors. We use i % len(colors) to reduce i to fit into the colors list, whatever its length.
>>> for i in range(len(states)):
... print(i, states[i], i % len(colors), colors[i % len(colors)])
...
0 ca 0 red
1 tx 1 green
2 nj 2 blue
3 mn 0 red
4 wa 1 green
5 or 2 blue
6 mi 0 red
7 fl 1 green
8 nm 2 blue
9 ny 0 red
int + int = int
- e.g. 3 1001 -26
- int is very common because indexing into a structure is fundamentally int
- + - * % // ** work as we've seen
- Math with int inputs, yields int result
- int in, int out
- Division / is an exception
>>> 1 + 2
3
>>> type(3)
>>> 10001 - 24
9977
>>> 10001 // 10
1000
>>> 10001 % 10
1
>>> 10 ** 3
1000
One exception: / yields a float - crosses the two worlds
>>> 5 // 2 # // int division, no fraction
2
>>> 5 / 2 # / division, yields float
2.5
float Arithmetic: float + float = float
- float math largely follows existing math patterns
+ - * / ** .. all work with floats
- float in, float out
- Math with float inputs, yields float result
- Note the "." in the output in every case
>>> 3.14
3.14
>>> type(3.14)
>>>
>>> 3.14 * 2.0
6.28
>>> 3.14 * 2.0 + 1.0
7.28
>>> 10.0 ** 6.0
1000000.0
float Scientific Notation "3.14e26"
- Float numbers can span an enormous range
- Like your TI84 calculator (or HP 12c!)
- Scientific format:
3.14e26
above 3.14 * 1026
- Float = fraction * exponent
- The 3.14 is the "fraction" with about 15 digits of accuracy
- The e26 "exponent" can be in the range 10 to the -308..+308
- In Python, using "e26" format yields a float type number
- Lots of physics, chemistry, 3D graphics .. works well with this sort of number
Mixed Case: int + float = float "promotion"
- Mixed case: int + float
- Combine int and float .. yields float
- Any float value "promotes" the computation to float
- Note how output below is all float, some int inputs
>>> 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 Applications
- Float is useful where need digits to the right of the "."
- A smoothly varying function
- e.g. on babygraphics computed x coord for line, based on 0.0..1.0 fraction
babygraphics window width 400, 800, 1200 pixels divided evenly
- e.g. Need a big range
- e.g. float used for angles, velocities, etc. for 3D graphics (games!)
- Float can be slower than int in the CPU
- In Python the speeds are roughly similar
Float - One Crazy Flaw - 1/10
- Note: do not panic! We can work with this. But it is shocking.
- Float arithmetic is a little imprecise
- Off at the 15th digit .. there are erroneous "garbage" digits
- 1. Idea of 1/10th, mathematically pure
- 2. In Python code: looks like this
0.1
- 3. In the computer memory, Python stores the number like this:
0.100000000000076
- There are some garbage digits way off to the right
- The Math Will Not Come Out Exactly Right
- This is a deep feature of computer floats, applies to all languages
- The print routine hides a few digits, so often the garbage is hidden
But in the computation, the garbage is there
Crazy Flaw Demo - Adding 1/10th
- Garbage digits are very often part of a float value
- Printing omits a few stored digits at right
- So often do not see the garbage
- But eventually the garbage gets big enough to print...
>>> 0.1
0.1
>>> 0.1 + 0.1
0.2
>>> 0.1 + 0.1 + 0.1 # everyone contemplate this
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 # this is why we can't have nice things
Summary: float math is slightly wrong
Why? Why Must There Be This Garbage?
- We think in base 10
- So 0.1 and 2.5 come out "even"
- But 1/3 does not come out even 0.333333333...
- Some numbers come out even in base 10, and some don't
- The computer uses base 2 internally - 0's and 1's
- In base 2, many numbers don't come out even
- There's this little garbage error off to the right
- Base 10 has the 1/3 case, but you're just used to that one
Crazy, But Not Actually A Problem
- Everyone needs to remember:
- float arithmetic always comes out a tiny bit wrong
- (int arithmetic, comes out perfect)
- The error is typically far less than 1-trillionth part
- But the error is not zero
- Most computations can handle an error of 1-trillionth part
- Actually not a problem
- How many digits of accuracy in the inputs, 6 digits?
Must Avoid One Thing: no ==
- There is one concrete coding rule
- Do not use == with float
>>> a = 3.14 * 5
>>> b = 3.14 * 6 - 3.14
>>> a == b # Observe == not working right
False
>>> b
15.7
>>> a
15.700000000000001
- abs(x) - the absolute value function
- Instead of ==, look at abs(a-b)
>>> abs(a-b) < 0.00001
True
- Exception: 0.0 is reliable for ==
- Any float value * 0.0 will be exactly 0.0
Float Conclusions
- 1. Two systems, int 67 and float 3.14
- 2. Math works for both int/float seamlessly
- 3. Float has tiny error many digits to the right, don't use ==
List Comprehensions - Magic
- Comprehensions are like map(), but pretty
- Comprehensions, when they click, feel a bit magical
- We did map() because you must know lambda
- It's fine to use comprehensions instead of map()
- If you work as a Python intern .. they will be pleased you know comprehensions
- Have list of XXX want list of YYY
- Pattern that appears in code 15% of the time
- Python's solution is beautiful for that 15%
Comprehension Syntax 1
- Given a list of elements
- Compute a new list, populated:
- expression: old elem → new elem
- e.g. compute square of each num element
- Syntax:
>>> nums = [1, 2, 3, 4, 5, 6]
>>> [n * n for n in nums]
[1, 4, 9, 16, 25, 36]
>>> [n * -1 for n in nums]
[-1, -2, -3, -4, -5, -6]
- Computes a new list, original is unchanged
- Nick's mnemonic: re-use syntax of other features
- 1-2-3 steps to write a comprehension:
1. type in a pair of outer brackets [ ]
2. inside write a foreach "for n in nums"
3. then the result expression "n * n" goes on the left
Comprehension If 2
- Can add "if" filter on the right hand side
Mnemonic: re-use syntax again
- Left hand side can be just "n" to pass value through unchanged
>>> [n for n in nums if n > 3]
[4, 5, 6]
>>> [n * n for n in nums if n > 3]
[16, 25, 36]
Comprehension Type Change 3
- Suppose you want list of strs ['1!', '2!', ...]
- Expression on left hand side (LHS) can build any type
>>> [str(n) + '!' for n in nums]
['1!', '2!', '3!', '4!', '5!', '6!']
>>> [(n, n) for n in nums]
[(1, 1), (2, 2), (3, 3), (4, 4), (5, 5), (6, 6)]
(x, y) Comprehension Example
- Suppose we have a list of (x, y) tuples representing coordinates
- Write a comprehension that produces a list of (x, y) where x/y have been swapped
>>> points = [(3, 4), (5, 2), (6, 1)]
>>>
>>> points
[(3, 4), (5, 2), (6, 1)]
>>>
>>> [(pt[1], pt[0]) for pt in points]
[(4, 3), (2, 5), (1, 6)]
Comprehension Style Notes
- 1. Name var for what is in the list
e.g. n, s, pair, lst
Keep your mind straight what type you have
- 2. Comprehension Fever
Sometimes people go crazy, trying to write everything as a comprehension
e.g. whole program is 4 nested comprehensions
Not readable!
Is this like Gold Fever?
1 line comprehensions are the sweet spot
You Try It - Comprehensions
These are all 1-liner solutions with comprehensions. We'll just do 1 or 2 of these.
Syntax reminder, double each n in list:
[n * 2 for n in nums]
> Comprehensions