List comprehensions

The Python community generally prefers list comprehensions to the functional programming ways of achieving the same goals.

Creating a list of squares:

In [ ]:
# regular way
squares = []
for x in range(10):
  squares.append(x**2)
print(squares)
In [ ]:
# functional tools
squares = list(map(lambda x: x**2, range(10)))
print(squares)
In [ ]:
# list comprehension
squares = [x**2 for x in range(10)]
print(squares)

The structure of a list comprehension is the following:

[ expression for-loop for-loops and if-statements]

For example:

In [ ]:
combinations = [(x, y) for x in [1,2,3] for y in [3,1,4] if x != y]
print(combinations)
# expression: (x, y)
# first for-loop: for x in [1,2,3]
# more for-loops and if-statements: for y in [3,1,4] if x != y
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]
In [ ]:
# equivalent code:
combinations = []
for x in [1,2,3]:
    for y in [3,1,4]:
        if x != y:
            combinations.append((x, y))
print(combinations)
Out[ ]:
[(1, 3), (1, 4), (2, 3), (2, 1), (2, 4), (3, 1), (3, 4)]

This way the if statement in a list comprehension can replace a filter.

In [ ]:
numbers = filter(lambda x: abs(x//10 - x%10) < 3, range(10, 40))
print(*numbers)
10 11 12 13 20 21 22 23 24 31 32 33 34 35
In [ ]:
numbers = [x for x in range(10, 40) if abs(x//10 - x%10) < 3]
print(*numbers)
10 11 12 13 20 21 22 23 24 31 32 33 34 35

Nested List Comprehensions

The expression in a list comprehension may be anything, including another list comprehension. This allows you to make nested list comprehensions to perform more complex actions. However, you should make sure that your code stays readable and that your list comprehensions don't cause too much confusion.

In [ ]:
matrix = [
    [1, 2, 3, 4],
    [5, 6, 7, 8],
    [9, 10, 11, 12],
]
In [ ]:
[[row[i] for row in matrix] for i in range(4)]
Out[ ]:
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
In [ ]:
# the equivalent code (1 list comprehension):
transposed = []
for i in range(4):
    transposed.append([row[i] for row in matrix])
print(transposed)
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]
In [ ]:
# the equivalent (no list comprehensions)
transposed = []
for i in range(4):
    transposed_row = []
    for row in matrix:
        transposed_row.append(row[i])
    transposed.append(transposed_row)
print(transposed)
[[1, 5, 9], [2, 6, 10], [3, 7, 11], [4, 8, 12]]

Task 1

Write a program that flattens a matrix using a list comprehension.

Flattening means taking a nested list (a list of lists in case of a matrix) and turning it into a single list with all the elements of the matrix.

Task 2

Solve Task 3 of Workshop 15 using list comprehensions. don't use any for loops outside of them.

You are given a list of data pairs. The first element of a pair is a year, the second one is a data point.

Split the data into groups by year using the itertools.groupby() function. Then for every year output data points containing numbers over 1000. A list comprehension to do it.

Do not use any lists / tuples / sets / dictionaries as variables. Do not use any for-loops beside the one given below. Solve everything using groupby and list comprehensions.

You can learn how to use groupby here: https://docs.python.org/3.8/library/itertools.html#itertools.groupby

The key part is this:

for k, g in groupby(data, keyfunc):

Your output should be

Data for the year 1988
4636 1808 1108
Data for the year 1989
3517
Data for the year 1990
2276 2407 1798

Recursion and mapping

Here is an example of a function that flattens a dictionary recursively, separating the keys of different levels with dots:

In [ ]:
def flatten_dict(d, prefix=None):
    if prefix is None:
        prefix = ''
    result = {}
    for k, v in d.items():
        if isinstance(v, dict):
            result.update(flatten_dict(v, k+'.'))
        else:
            result.update({prefix+k:v})
    return result

Task 3

Write a function that maps a function over a nested list

In [ ]:
def treemap(???):
  ???

print(treemap(lambda x: x*x, [[[1], [2, 3], 4], [5, 6], 7]))
# [[[1], [4, 9], 16], [25, 36], 49]

Task 4*

Write a function that does the opposite of the function flatten_dict: given a dictionary with dots in keys it should create a nested dictionary where dots separate the levels.