How to avoid for loops when generating index arrays?

Asked by Oliver on 30 Apr 2013 at 17:42
Latest activity Commented on by Matt J about 10 hours ago

I often find myself coding nested for loops to generate vectors of integer indices. For example:

n = 4;
i = 1;
for L = 0:n
    for M = -L:L
        l(i) = L;
        m(i) = M;
        i = i+1;
    end
end

All I need are the vectors "l" and "m". I can preallocate to save some speed, but my real problem is having to use the for loops as sometimes the index vectors I need to create have many more nested for loops whose (note that the inner loop index depends on the outer loop index).

Is there a simple way to avoid using loops to generate index vectors like these?

0 Comments

Oliver

Products

6 Answers

Answer by Roger Stafford on 30 Apr 2013 at 21:09
Edited by Matt J on 30 Apr 2013 at 21:38

For your particular problem you can do this:

 M = (0:n*(n+2))';
 L = floor(sqrt(M));
 M = M-L.*(L+1);

(I've used uppercase letters, 'L' and 'M', in place of your lowercase 'l' and 'm'.)

As with Matt Kindig, I am not sure this will be any faster than your for-loops. Time it with a large value for n and see.

0 Comments

Roger Stafford
Answer by Matt J on 30 Apr 2013 at 21:37
Edited by Matt J on 30 Apr 2013 at 21:40

Here's a way to do it using NDGRID. It's not apriori obvious whether for loops would or would not be faster. It depends what you plan to reuse.

[mg,lg]=ndgrid(-n:n,0:n);
idx=abs(mg)<=lg;
 l=lg(idx).',
 m=mg(idx).',

6 Comments

Matt J on 30 Apr 2013 at 22:20

In this case the intermediate array lg uses nearly twice as much memory as the final array l.

Again, why do we care? Even for n=300, lg and mg consume a measly 2MB. And as I've said (3 times now), you might be able to reuse lg and mg to generate other stuff.

Oliver about 11 hours ago

The reason that I care is that I have only given you a simplified example. In practice I have things like:

for N = 0:2:30
    for L = 0:N
        for M = -L:L
            for P = 0:M
                for Q = -P:P
                    ...
                end
            end
        end
    end
end

So making the intermediate arrays in this case would require a 5-dimensional array that takes up a lot of space.

Matt J about 10 hours ago

I'm starting to think Sean's advice about sticking with for-loops is the best one. There can definitely be ways to cut down on the loop nesting (see my newest Answer based on cell arrays), but the required form would depend on the body of the original set of for-loops.

Matt J
Answer by Sean de Wolski on 30 Apr 2013 at 17:43
Edited by Sean de Wolski on 30 Apr 2013 at 17:44
doc meshgrid
doc ndgrid %?

:)

And of course, depending on your application, two nested for-loops or bsxfun() might be better.

2 Comments

Oliver on 30 Apr 2013 at 17:46

I can't use ndgrid or meshgrid because the inner loop depends on the outer loop. If i had something like

i = 1;
for L = 3:6
    for M = 1:5
        l(i) = L;
        m(i) = M;
        i = i+1;
    end
end

then I could use:

[l,m] = meshgrid(3:6,1:5);

But, this won't work with the dependency.

Sean de Wolski on 30 Apr 2013 at 20:11

Just use the for-loops, they'll be the fastest by far. If you want to disguise it, write a function that takes L and M and returns l and m.

cellfun and arrayfun are slow and converting between cells and numeric types is slow. The above with preallocation will be pretty quick.

Sean de Wolski
Answer by Matt Kindig on 30 Apr 2013 at 19:12

It's kind of hack-y, but it gives the same output as your original posting:

    n=4;
    l = cell2mat(arrayfun(@(x) x*ones(1,2*x+1), 0:n, 'uni', false));
    m= cell2mat( arrayfun(@(x) (-x:1:x), 0:n, 'uni', false));

Keep in mind that this may very well be slower than for-loops--I haven't done any timing comparisons.

0 Comments

Matt Kindig
Answer by Matt J on 30 Apr 2013 at 22:01

Here's another method, less memory consuming than NDGRID

    mm=sparse(-n:n);
    ll=sparse(0:n);
    map=bsxfun(@le,abs(mm.'),ll);
    idx=nonzeros(bsxfun(@times,map,1:length(ll)  ));
       l=full(ll(idx));
    idx=nonzeros(bsxfun(@times,map,(1:length(mm)).'))  ;
       m=full(mm(idx));

0 Comments

Matt J
Answer by Matt J about 10 hours ago
Edited by Matt J about 10 hours ago

Here's a way to eliminate one nested loop

    l=cell(1,n+1);
    m=l;
    for L=0:n
        i=L+1;
        m{i}=-L:L;
        l{i}=m{i};
         l{i}(:)=L;
    end
    l=[l{:}],
    m=[m{:}],

2 Comments

Sean de Wolski about 10 hours ago

I'd be surprised if this is faster due to the cell array conversions. I guess one of us will have to run a timing test.

Matt J about 10 hours ago

For n=1000 I get this,

Original Approach:

Elapsed time is 0.093130 seconds.

Cell-Based Approach

Elapsed time is 0.027393 seconds.

I think the vectorization inherent in

        m{i}=-L:L;
        l{i}=m{i};
         l{i}(:)=L;

trumps the overhead from the cell conversion.

Matt J

Contact us