Today: de-facto If, float, comprehensions

Aside: HP 35 Calculator

Shorthand: De-Facto If Test

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)

# int
3  100  -2

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

Math Works

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.

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

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

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

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 Applications

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

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


List Comprehensions - Magic

Comprehension Syntax 1

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

Comprehension If 2

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

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

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

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