3.3 Generative Loops

Working with Loops

Generative loops also known as “list comprehensions” in Python, are a concise way to create lists (or other collections) by applying an expression to each item in an iterable. They can also include conditional statements to filter items.

# Example of a generative loop (list comprehension)
squared_numbers = [x**2 for x in range(10)]
print(squared_numbers)
[0, 1, 4, 9, 16, 25, 36, 49, 64, 81]

In this example, we create a list of squared numbers from 0 to 9 using a single line of code. The expression x**2 is applied to each item in the iterable range(10).

We can also add a conditional statement to filter items. For example, if we only want to include even numbers in our list of squared numbers, we can do the following:

# Example of a generative loop with a condition
even_squared_numbers = [x**2 for x in range(10) if x % 2 == 0]
print(even_squared_numbers)
[0, 4, 16, 36, 64]

In this case, the condition if x % 2 == 0 filters the items, so only even numbers are included in the final list.

Generative loops are not limited to lists; they can also be used to create sets and dictionaries. For example:

# Example of a generative loop for a set
unique_squared_numbers = {x**2 for x in range(10)}
print(unique_squared_numbers)

# Example of a generative loop for a dictionary
squared_dict = {x: x**2 for x in range(10)}
print(squared_dict)
{0, 1, 64, 4, 36, 9, 16, 49, 81, 25}
{0: 0, 1: 1, 2: 4, 3: 9, 4: 16, 5: 25, 6: 36, 7: 49, 8: 64, 9: 81}

In summary, generative loops (list comprehensions) provide a powerful and concise way to create and manipulate collections in Python.

Nested Generative Loops

Generative loops can also be nested, allowing you to create more complex structures. For example, you can create a list of tuples representing the Cartesian product (a matrix of all available pairs between list x and list y) of two ranges:

# Example of a nested generative loop
cartesian_product = [(x, y) for x in range(3) for y in range(3)]
print(cartesian_product)
[(0, 0), (0, 1), (0, 2), (1, 0), (1, 1), (1, 2), (2, 0), (2, 1), (2, 2)]

In this example, we use two for clauses in the list comprehension to generate all combinations of x and y from the two ranges. The result is a list of tuples representing the Cartesian product.

Or we could generate the same player-game-shot dataset that exists above with generative loops and we can also use it to generate other collections as well:

import random

players = ["Mbappe", "Ronaldo", "Messi", "Ronaldinho", "Neymar"]
games = {
    player: [
        [random.choice([0, 1]) for _ in range(random.randint(1, 5))]
        for _ in range(random.randint(1, 10))
    ]
    for player in players
}
games
{'Mbappe': [[0, 0], [0], [1, 1, 0, 1, 1]],
 'Ronaldo': [[0, 1], [1], [1, 0, 0, 0], [1, 1, 0, 1], [1, 1]],
 'Messi': [[0], [1, 1, 0, 0], [1, 0, 0], [1], [1, 1, 0], [1, 1], [1, 0]],
 'Ronaldinho': [[1, 0, 1, 0, 1], [1], [0, 0, 0, 1], [0, 1, 0, 1, 0], [1]],
 'Neymar': [[1, 0, 1], [0], [0, 1, 0], [1, 1, 0], [0, 1, 1, 1], [0, 1]]}

Here we are generating a dictionary by looping over each player and for each player generating a list of games, and for each game generating a list of shots. The use of generative loops makes the code more concise and easier to read.