# | include: false
import numpy as np
import pandas as pd
256852) np.random.seed(
Loops (Nice)
The material in this ‘Nice’ chapter is optional. The discussion typically deals with content beyond what a novice should know. So, please finish all the ‘Need’ and ‘Good’ portions before you go through this chapter.
1 There is more to list comprehension
-
You can have more than one loop in a list comprehension.
for a in range(5) for b in ['A', 'B', 'C']] [[a,b]
[[0, 'A'] [0, 'B'] [0, 'C'] [1, 'A'] [1, 'B'] [1, 'C'] [2, 'A'] [2, 'B'] [2, 'C'] [3, 'A'] [3, 'B'] [3, 'C'] [4, 'A'] [4, 'B'] [4, 'C']]
-
You can even incorporate a condition!
for a in range(5) for b in ['A', 'B', 'C'] if a % 2 != 0] [[a,b]
[[1, 'A'] [1, 'B'] [1, 'C'] [3, 'A'] [3, 'B'] [3, 'C']]
-
=[[1, 2, 3], [4, 5, 6, 7]] nested_listfor x in nested_list for y in x] [y =[[1, 2, 3], [4, 5, 6, 7]] nested_list =[] output for x in nested_list: for y in x: output.append(y)
Here is a slightly more complicated use of list comprehension to flatten a list.
[1, 2, 3, 4, 5, 6, 7]
This does the same as:
2 Zipping a dictionary
zip()
offers one of the easiest ways to combine two lists into a dictionary:
=["Black Widow", "Iron Man", "Doctor Strange"]
super_names=["Natasha Romanoff", "Tony Stark", "Stephen Strange"]
real_names
dict(zip(real_names, super_names))
{'Natasha Romanoff': 'Black Widow', 'Tony Stark': 'Iron Man', 'Stephen Strange': 'Doctor Strange'}
In the above dict()
is used to recast zip()
’s output into a dictionary.
3 for
and while
has an else
=[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers
for i in numbers:
if i < 0: break
else:
print('No negative numbers in the list')
The for
and while
loops in Python come with an optional else
statement! The code in the else
block is executed only if the loops are completed. The else
code-block will not run if you exit the loop prematurely (e.g. by using break
).
Shown alongside is a trivial example. You should add a negative number to the list, and re-run the snippet. There will be no message this time.
So, the else
statement can be used to distinguish between the loops completing as planned or if there was a break
(or return
or an exception).
No negative numbers in the list
Exercises
Exercise 1 (Changing a list)
We want a snippet of code based on a for
loop to remove fruits starting with the letter ‘p’ from the following list.
= ["apple", "banana", "jackfruit",
fruits "pineapple", "papaya", "watermelons",
"peaches", "durian", "mangoes",
"strawberries", "passionfruit"
]
The following has been suggested as a solution. However, it does not work!
for fruit in fruits:
if fruit[0] == "p":
fruits.remove(fruit)
Identify, understand and fix the error.
for fruit in fruits:
if fruit[0] == "p":
fruits.remove(fruit)
Although the above code is elegant it has a serious flaw which you can see by using pythontutor.com to visualise the flow of the script.
A safer solution is the following:
= ["apple", "banana", "jackfruit",
fruits "pineapple", "papaya", "watermelons",
"peaches", "durian", "mangoes",
"strawberries", "passionfruit"
]
= fruits.copy()
copy_of_fruits
# Not that we are looping(iterating) over the copy
for fruit in copy_of_fruits:
if fruit[0] == "p":
fruits.remove(fruit)
An even better, clean and elegant solutions is:
= ["apple", "banana", "jackfruit",
fruits "pineapple", "papaya", "watermelons",
"peaches", "durian", "mangoes",
"strawberries", "passionfruit"
]
for fruit in fruits if fruit[0] != "p"] [fruit
# | include: false
import numpy as np
import pandas as pd
256852) np.random.seed(
Exercise 2 (A list of powers)
The following code is an attempt to create a list \([n, n^2,n^3]\) for several values of \(n\). We can specify the maximum value of \(n\) by changing maximum_n
.
= 5
maximum_n = [[]] * maximum_n
result
for n in range(1, maximum_n + 1):
- 1].append(n)
result[n - 1].append(n**2)
result[n - 1].append(n**3) result[n
For maximum_n = 5
the content of result
should be as shown below.
1, 1, 1],
[[2, 4, 8],
[3, 9, 27],
[4, 16, 64],
[5, 25, 125]] [
However, the code does not produce this expected result!
Identify, understand, explain and fix the bug.
An explantion
The problem arises because [[]] * 5
creates 5 references to the same empty list []
. Running a list comprehension will create 5 different empty lists []
.
Let’s check if what I have said is true.
= 5
maximum_n
= [[]] * maximum_n
result
for count, element in enumerate(result):
print(count, id(element))
= 5
maximum_n
= [[] for _ in range(maximum_n)]
result
for count, element in enumerate(result):
print(count, id(element))
0 140013001824896
1 140013001824896
2 140013001824896
3 140013001824896
4 140013001824896
0 140013001865856
1 140013001866880
2 140013001902400
3 140013001900160
4 140013001867072
A solution
= 5
maximum_n # result = [[]] * maximum_n
= [[] for _ in range(maximum_n)]
result
for n in range(1, maximum_n + 1):
- 1].append(n)
result[n - 1].append(n**2)
result[n - 1].append(n**3) result[n
[[1, 1, 1]
[2, 4, 8]
[3, 9, 27]
[4, 16, 64]
[5, 25, 125]]
Exercise 3 (Time profiling)
Use %%timeit
to compare the execution speeds of the following:
# | Option 1 | Option 2 | Result |
---|---|---|---|
1 | Creating a list of squares with for loop |
Creating a list of squares with while loop |
|
2 | Creating a list of squares with a for loop. |
Creating a list of squares with a list comprehension loop. | |
3 | Creating a list of squares using list append() |
Creating a list of squares using list += |
|
4 | Creating a list of squares using list append() |
Creating a list of squares using append() of Numpy |
|
5 | Creating a list of squares using Numpy | Creating a list of squares using List comprehension loop. |