Saaru Lindestøkke
Saaru Lindestøkke

Reputation: 2544

How can I index multiple array segments at once without a loop?

I want to change the value in column 2 based on the values in column 1 in one array (main), using a start and end index from another array (conditions).
In conditions column 1 holds the start index, column 2 the end index.

main = zeros(8, 2);
main(:, 1) = 1:8;
conditions = [2, 3; 6, 8]

main =

     1     0
     2     0
     3     0
     4     0
     5     0
     6     0
     7     0
     8     0

conditions =

     2     3
     6     8

I know how to do it using a loop (shown below), but am looking for a faster method.

for ii = 1:size(conditions, 1)
    main(main(:, 1) >= conditions(ii, 1) & main(:, 1) <= conditions(ii, 2), 2) = 1;
end

main =

     1     0
     2     1
     3     1
     4     0
     5     0
     6     1
     7     1
     8     1

Doing main(main(:, 1) >= conditions(:, 1) & main(:, 1) <= conditions(:, 2), 2) = 1 results in the error Matrix dimensions must agree.

Is there a non-loop method?

Upvotes: 1

Views: 156

Answers (2)

Nicky Mattsson
Nicky Mattsson

Reputation: 3052

NOTE: This is a solution for integers only as the original question only presented the integer case.

First, figure out how many elements there are included in the interval

dCon = diff(conditions,[],2)+1;

Then construct an increasing sequence of indexes to the maximum number of elements (this list would be enormous for the float case, and thus this solution does not, feasibly/efficiently, extend to floats)

idx0 = repmat(1:max(dCon),length(dCon),1);

Cap off the indexes that are too large

idx0(idx0>dCon)=1;

Now add the starting point

idx = idx0 + conditions(:,1)-1;

now idx contains all the numbers you want to change. Use ismember to find all elements in main and change them to 1.

main(ismember(main(:,1),idx(:)),2)=1;

EDIT: This is the full example with the vector from Gnovice in the comments

main = zeros(10, 2);
main(:, 1) = [1; 2; 2; 2; 3; 3; 4; 6; 6; 8];
conditions = [2, 3; 6, 8]
dCon = diff(conditions,[],2)+1;
idx0 = repmat(1:max(dCon),length(dCon),1);
idx0(idx0>dCon)=1;
idx = idx0 + conditions(:,1)-1;
main(ismember(main(:,1),idx(:)),2)=1;

Upvotes: 3

Cris Luengo
Cris Luengo

Reputation: 60474

Your attempt is almost correct. If you transpose the conditions, then you'll be comparing a column of main with a row of conditions, leading to MATLAB doing implicit singleton expansion, giving a matrix output. This matrix can then be collapsed using any.

main = zeros(8, 2);
main(:, 1) = 1:8;
conditions = [2, 3; 6, 8];

index = (main(:,1) >= conditions(:, 1).') & (main(:, 1) <= conditions(:, 2).');
index = any(index,2);
main(index,2) = 1;

(I've separated out the code into 3 lines for clarity, but of course they can all be a single line.)


Note that for versions of MATLAB prior to R2016b, this code won't work, you'll need to use bsxfun instead:

index = bsxfun(@ge,main(:,1),conditions(:, 1).') & bsxfun(@le,main(:, 1),conditions(:, 2).');

Upvotes: 3

Related Questions