4

How do nested for loops (in this case double for loops) work in creating a 2D list.

For example I would like to have a 2x2 matrix that is initialized with 0 as every element.

I got this:

x = [[0 for i in range(row)] for j in range(col)]

where row is defined to be the number of rows in the matrix and col is defined to be the number of columns in the matrix. In this case row = 2 and col = 2.

When we print x:

print(x)

we will get:

[[0, 0], [0, 0]]

which is what we want.

What is the logic behind this? Is [0 for i in range(row)] saying that, for every element in the range of the specified row number, we are going to assign a 0 to effectively create our first row in the matrix?

Then for j in range(col) is saying we repeat the creation of this list according to the specified column number to effectively create more rows, which end up being columns?

How should I be reading this code snippet from left to right?

Community
  • 1
  • 1
nvars
  • 49
  • 1
  • 1
  • 2
  • Good answers below, but the docs already cover it pretty well: https://docs.python.org/2/tutorial/datastructures.html#nested-list-comprehensions – SiHa Feb 16 '16 at 16:10
  • Possible duplicate of [Understanding nested list comprehension](http://stackoverflow.com/questions/8049798/understanding-nested-list-comprehension) – SiHa Feb 16 '16 at 16:13

3 Answers3

5

It is just a shortcut for this:

x = []
for j in range(column):
    _ = []
    for i in range(row):
        _.append(i)
    x.append(_)

you can put the inner for loop into one by saying that the list is composed of a whole bunch of 0's. We do that for each i in range(row), so we can say _ = [0 for i in range(row)]. This is how that looks:

x = []
for j in range(column):
    x.append([0 for i in range(row)])

We can now see that x is composed of a bunch of [0 for i in range(row)]'s and we do that once for each of j in range(column), so we can simplify it to this:

x = [[0 for i in range(row)] for j in range(column)]
zondo
  • 19,901
  • 8
  • 44
  • 83
1

Sometime to expand all the one-liner shorthand code can help you understand better:

Basic

x = [i for i in range(somelist)]

Can be expanded into:

x = []
for i in range(somelist):
    x.append(i)

More Nested

x = [[0 for i in range(row)] for j in range(col)]

Can be expanded into:

x = []
for j in range(col):
    _ = []
    for i in range(row):
        _.append(0)
    x.append(_)
Yeo
  • 11,416
  • 6
  • 63
  • 90
  • 1
    LOL, my example variable naming are exactly the same as @zondo. What a standard convention we use. :) `_` is just a temporary variable name, in case anyone confused. – Yeo Feb 16 '16 at 16:03
  • Hello thanks for the reply. So just to make sure from reading the expanded version of this nested for loop, am I supposed to read the list comprehension version from right to left instead of left to right? Because In the expanded version we started with the outer for loop that involves j, then the for loop that involves i which is nested within it. – nvars Feb 16 '16 at 16:16
  • yes, start with outer loop. but you have to be careful. especially on the following codes are not the same: `[i for i in range(row) for j in range(col)]` vs `[[for i in range(row)] for j in range(col)])`. Notice the difference of those 2, ask on another thread if you are curious about those 2. ;) – Yeo Feb 16 '16 at 16:20
0

Your assumption about how this piece of code works is correct: The inner list comprehension creates a list of zeroes, and the outer list comprehension creates a list of such lists. You then simply interpret the lists as columns and the zeroes at a certain position inside each list as rows. (There's no mathematical concept of a matrix involved, so there's no turning rows into columns; it's just a list of lists.)

The code for creating the zeroes and the lists is actually executed multiple times, resulting in distinct lists (i.e. you could modify one of them without modifying the others). I'm emphasising this because since you're new to Python, it's important to pay attention to things like object identity vs. object equality. For example, the lists of zeroes could be done like this:

[0] * row

This would be OK since the list holds references to the integer 0, and modifying a list would replace some references and leave others intact. On the other hand, the following code would do something different than yours:

[[0] * row] * col

The result would look the same as yours at first glance, but the outer list would now hold multiple references to the same list of zeroes, so modifying one column would also modify the others (because all columns are now the very same list, not distinct lists of equal content).

Also, it's interesting to consider the functional nature of list comprehensions. You're asking how to read the code: just read it as you'd read it out aloud. What I'm getting at is that this code is not procedural, it doesn't prescribe how the machine should create empty lists and add elements to them. It instead describes what the result should be: a list of zeroes at each position in a list, their number being row, and a list of such lists at each position in another list, their number in turn being col.

Thomas Lotze
  • 5,153
  • 1
  • 16
  • 16
  • Thanks! This helped my understanding a bit more on how these lists can be interpreted especially if I wanted to make lists within a list (so a 2D list in my example). Also thanks for the reference tips as altering one thing could alter something else unintentionally. – nvars Feb 16 '16 at 16:28