[0, 3]: List Comprehensions

Python supports a concept called “list comprehensions”. It is a way to define lists that is quite similar to how mathematicians define arrays. Remember this kind of writing:
$S = \{x^2 | x \in \mathbb{N}, x \in [0,9]\}$
or
$Z = \{ \frac{x-E[x]}{std(x)} | x \in [1,4,7,9] \}$
S contains all natural numbers between 0 and 9 (included), squared; Z is a normalization procedure of the list [1, 4, 7, 9]. Using standard flow controls these lists would have been coded this way:

 S=[]
 for x in range(0,10):
 S.append(x**2)
 Z=[]
 orig_list= [1,4,7,9]
 #assume we have a fast way to compute mean and standard deviation,
 #like a custom function
 for x in orig_list:
 Z.append((x-my_mean(orig_list))/my_std(orig_list))
 

The pythonic way of defining the lists instead is the following:

 S = [x**2 for x in range(0,10)]
 Z = [(x-my_mean(orig_list))/my_std(orig_list) for x in orig_list]


What if more complicated for loops are involved?

rng=range(1,100)
rng2=range(100,200)
l=[]
    for i in rng:
        for j in rng2:
            if i < 3:
                if j-i > 197:

                    l.append((i,j))
 #or with a list comprehension
 l=[(i,j) for i in rng for j in rng2 if i < 3 if j-i > 197]

Note how the list comprehension is way more similar to the mathematical correct way to define that list, i.e.:
$l=\{(i,j) | i \in rng, j \in rng2, i < 3, j-i > 197\} $
This concept offers one more degree of manipulability for loops with respect to the standard flow controls.
The last list could be written as follows without changing anything of its inner structure:

l=[(i,j)
 for i in rng
 for j in rng2
 if i < 3
 if j-i > 197]
 

The indentation in Python is the unkind friend that you will thank at the end of your days. A mixed blessing that will help you in debugging and organizing your code.
Here is an example of small code optimization. The code runs over all values of $rng$ and all values of $rng2$, but the first if is passed only by those value of $rng$ that are less than $3$. Why bother checking every value of $rng2$ when we can know upstream if the first filter would be passed or not?

 l=[(i,j)
 for i in rng
 if i < 3
 for j in rng2
 if j-i > 197]
 

Rewriting the code without the list comprehension would have needed us to switch the indentation of the two highlighted rows, thus requiring some more work.
This is obviously a cheap example, the real impact comes when working with more complicated conditions and if chains.
Jump on the Zebra Puzzle section to see it.

The notebook linked in the first lesson is re-linked here for list comprehensions: