For Loop Style Question

Given that range1, range2, f(), and g() are set beforehand, what is the preferred way to populate a matrix if we need both indices and values?

A=zeros(length(range1),length(range2))
for i in 1:length(range1)-1, j in 1:length(range2)
    A[i+1,j]=A[i,j]+f(range1[i])+g(range2[j])
end
A=zeros(length(range1),length(range2))
for (i,value1) in enumerate(range1[1:end-1]), (j,value2) in enumerate(range2)
    A[i+1,j]=A[i,j]+f(value1)+g(value2)
end

The first seems simpler to me, assuming both of your range arrays use 1-based indexing. Note that range1[1:end-1] makes a copy (although if this is a Range object then it is still efficient because the copy is another Range).

Note that I would swap the loop order in order to get better locality (since A is stored column-major) for cache-line efficiency.

2 Likes

Isn’t 1-based indexing required by Julia? Thanks for you comments. I wasn’t sure how the loops were parsed when on a single line.

Not at all! Julia Arrays have 1 based indexing, but AbstractArrays can have any indexing behavior. For a particularly funny example, https://giordano.github.io/blog/2020-05-23-random-array-indexing/

2 Likes

Here you are calculating f(range1[i]) over and over, once for each iteration over range2. You should probably pull this out of the inner loop.

And the first loop example is definitely cleaner.

1 Like

I liked the first way better too. It’s pretty much exactly how I would write the code in Matlab. I wanted to make sure I wasn’t missing some Julian efficiency or style.

Why would I want to use enumerate and create a new variable instead of just calling the original range with an index?

If you want to handle more general objects, e.g. user-defined array types with non 1-based indexing, or even iterable objects that are not indexable at all.

Basically, a little more thought is required to write code that is more generic. But it is perfectly fine to write less-generic, obvious code to start with, and genericize it later as needed (e.g. if you put it into a library that you start re-using in lots of new contexts).

Also, I second @DNF’s suggestion that you may want to precompute frange1, grange2 = f.(range1), g.(range2) so that you only evaluate these functions once per range element (if performance matters and these functions are expensive).

1 Like