Nested loops are loops within loops. We already saw them in the context of the Missing Number problem. Here is a simpler example.
counter = 0 #the number of times we printed output so far
for i in range(5):
for j in range(3):
print(counter, ': i = ', i, ', j = ', j, sep = "")
counter += 1
(sep="") removes the spaces between the different things that are printed.
Here is one way to understand what's going on here. The outer loop set i to 0, 1, 2, 3, 4. Then, for each of those i's, the inner loop is run:
#i is fixed
for j in range(3):
print(counter, ': i = ', i, ', j = ', j, sep = "")
counter += 1
For example, for i=2, the loop that is running is, in effect,
for j in range(3):
print(counter, ': i = ', 2, ', j = ', j, sep = "")
counter += 1
So that the output (ignoring the couter) would be i = 2, j = 0 i = 2, j = 1 i = 2, j = 2
This process is repeated for every i.
In addition, every time we print, the variable counter is incremented by 1. That means that counter will go from 0, to 1, to 2, ... when we print.
At the end, the value of counter is
counter
That's because we repeated print 3 times inside the inner loop, and we repeated the whole thing 5 times, so we printed output $15 = 5\times 3$ times in total.
Here is another way to understand the loop. Here is some equivalent code:
i = 0
for j in range(3):
print(counter, ": i = ", i, ", j = ", j, sep = "")
counter += 1
i = 1
for j in range(3):
print(counter, ": i = ", i, ", j = ", j, sep = "")
counter += 1
i = 2
for j in range(3):
print(counter, ": i = ", i, ", j = ", j, sep = "")
counter += 1
#....
Like before, we can convert the nested loop to a simple loop that calls a helper function:
def count_i(i, n_times):
global counter
for j in range(n_times):
counter += 1
print(counter,": i = ", i, ", j = ", j)
if __name__ == '__main__':
counter = 0
for i in range(5):
count_i(i, 3)
The code is the same. The difference is that we made a helper function instead of having an inner loop. We used a global counter variable here. The alternative would be to pass counter as a parameter, and then return what the counter is at the end from count_i. Neither solution is ideal (global variables are to be avoided generally if possible, and passing counter back and forth is a little bit awkward). Really, a nested loop without a helper function is probably appropriate here.
Let's now print all words of length 2 that contain letters from alphabet = ["a", "b", "c", "d", "e", "f"] This is a somewhat similar task to what we did before: above, we are printing all two-digit combinations of the form
[digit between 0 and 4][digits between 0 and 2]
Here is how we can print all passwords of length 2
alphabet = ["a", "b", "c", "d", "e", "f"]
for letter1 in alphabet:
for letter2 in alphabet:
print(letter1 + letter2)
The story is the same: we fix letter1 to be, in turn, "a", "b", "c", ..., and then go over all the possibility for letter2.
So for example when letter1 is "c", the inner loop prints
ca
cb
cc
cd
ce
cf
How about all possible words of length 3?
alphabet = ["a", "b", "c", "d"]
for letter1 in alphabet:
for letter2 in alphabet:
for letter3 in alphabet:
print(letter1 + letter2 + letter3)
To understand how this works, again think from the bottom up. In the innermost loop, letter1+letter2 is fixed, and we go through all the possible letter3's. For example, if letter1+letter2 is "ca", we print out
caa
cab
cac
In the second-most inner loop, letter1 is fixed, and we go through all the possible letter2's, and for each letter2 we go through all the possible letter3's.