Nested loop confusion

Hi all, I don’t work much with comprehensions of nested loops, but I came across something which is probably obvious but unexpected to me. Why is there a difference between these two approaches to the nested loop?

Without comprehension, I iterate in a nested loop over the rows in the inner loop, as is efficient for column major ordering as in Julia.

a = zeros(4,3)
c = 0
for j in 1:3, i in 1:4
    global c+= 1
    a[i,j] = c
end

the output is as I expect

Julia> a
4x3 Matrix{Float64}:
 1.0  5.0   9.0
 2.0  6.0  10.0
 3.0  7.0  11.0
 4.0  8.0  12.0

But when sticking the above loops in a comprehension, the matrix is transposed, even though it is filled in column major order!

c = 0
a = [(global c+=1) for j in 1:3, i in 1:4]
3x4 Matrix{Int64}:
 1  4  7  10
 2  5  8  11
 3  6  9  12

Why is the matrix transposed in the comprehension?

1 Like

The two cases have similar syntax, but different semantics. Consider the following versions of your examples:

julia> [(@show i,j) for i in 1:2, j in 1:3]
(i, j) = (1, 1)
(i, j) = (2, 1)
(i, j) = (1, 2)
(i, j) = (2, 2)
(i, j) = (1, 3)
(i, j) = (2, 3)
2×3 Matrix{Tuple{Int64, Int64}}:
 (1, 1)  (1, 2)  (1, 3)
 (2, 1)  (2, 2)  (2, 3)

and

julia> for i=1:2, j=i:4
       @show i,j
       end
(i, j) = (1, 1)
(i, j) = (1, 2)
(i, j) = (1, 3)
(i, j) = (1, 4)
(i, j) = (2, 2)
(i, j) = (2, 3)
(i, j) = (2, 4)

In the for case, the faster moving index is the second, and in the comprehension it is the first. The example of the for loop also shows why this is so: to enable the second index to depend on the value of the first index. In essence, the for loop is syntactic sugar for a double nested for, and as such maintains the order of the index ranges:

for i=1:2
  for j=i:4
    ||
    \/
for i=1:2, j=i:4

In the comprehension, the ranges are in the array indexing order. With the fastest index being the first. Indeed, the comprehension returns an array.
This logic may also help remember this confusing bit.
Hope this helps (this answer not written by GPT-3, maybe GPT-5 :wink: )

4 Likes

Exactly like what GPT-3 would say :face_with_raised_eyebrow:

1 Like

Ah, I see. So if the fastest index is the first in the comprehension, I’d have to reverse the order of the loops in comprehension then to get the same behaviour as the nested for or syntactic sugar for j, i like so

a = zeros(4,3)
c = 0
for j in 1:3, i in 1:4
    global c+= 1
    a[i,j] = c
end

julia> a
4x3 Matrix{Float64}:
 1.0  5.0   9.0
 2.0  6.0  10.0
 3.0  7.0  11.0
 4.0  8.0  12.0

c = 0.
b = [(global c+=1) for i in 1:4, j in 1:3]

julia> b
4x3 Matrix{Float64}:
 1.0  5.0   9.0
 2.0  6.0  10.0
 3.0  7.0  11.0
 4.0  8.0  12.0