Efficient colon operator for multiple start and end points
Asked Answered
B

4

6

Suppose I have the following two variables:

start_idx = [1 4 7];
end_idx   = [2 6 15];

I want to efficiently (no for loop if possible) generate a single row which consists of the colon operator being applied between corresponding elements of start_idx and end_idx. For this example, this would result in:

result = [1:2 4:6 7:15];

Therefore:

results = [1 2 4 5 6 7 8 9 10 11 12 13 14 15];

The method to do this should be usable inside Simulink's MATLAB Function block. Thank you very much!

Baltazar answered 21/7, 2016 at 10:3 Comment(4)
out=cell2mat(arrayfun(@(x,y)[x:y],start_idx,end_idx,'uniformoutput',false));Postrider
Is arrayfun any faster than a for loop, though?Baltazar
Not really. What are the sizes of your sart_idx and end_idx vectors though? I believe they must be pretty big for this piece of code to have a non-neglictible impact on the speed of your codePostrider
One of your replies to one of the answers says you need this to work for code generation - do you mean with Simulink Coder? That would have been useful information to include in the question. But given that the result of this operation is going to be a variable length signal you are also going to have to set up the MATLAB Fcn block up to handle that correctly. Finally, IMHO doing this in a loop a far cleaner and more understandable way to go than looking for some highly obfuscated one liner.Correspondence
M
5

Here's a vectorized approach based on cumulative summation -

% Get lengths of each group
lens = end_idx - start_idx + 1;

% Determine positions in o/p array where groups would shift
shift_idx = cumsum(lens(1:end-1))+1

% Initialize ID array and at shifting positions place strategically created
% numbers, such that when ID array is cumulatively summed would result in
% desired "ramped" array
id_arr = ones(1,sum(lens));
id_arr([1 shift_idx]) = [start_idx(1) start_idx(2:end) - end_idx(1:end-1)];
out = cumsum(id_arr)

Sample run -

start_idx =
     6     8    13
end_idx =
    11    11    15
out =
     6     7     8     9    10    11     8     9    10    11    13    14    15
Macaco answered 21/7, 2016 at 14:40 Comment(0)
P
2

As I indicated in the comments, a one liner to solve this would be :

out=cell2mat(arrayfun(@(x,y)[x:y],start_idx,end_idx,'uniformoutput',false));

The arrayfun call will create a cell array whose each cell is a part of your output :

ans =

     1     2


ans =

     4     5     6


ans =

  Columns 1 through 8

     7     8     9    10    11    12    13    14

  Column 9

    15

By wrapping it inside a cell2mat call you get the expected output :

out =

  Columns 1 through 8

     1     2     4     5     6     7     8     9

  Columns 9 through 14

    10    11    12    13    14    15
Postrider answered 21/7, 2016 at 11:17 Comment(3)
Anonymous functions are not supported by code generation, though, so I cannot use this in a MATLAB Function block in Simulink - which is the requirement. Thanks for the input, but is there another way you can think of?Baltazar
fr.mathworks.com/matlabcentral/answers/…Postrider
It has to be supported for code generation. coder.extrinsic is not supported for code generation.Baltazar
K
1

This is cumbersome, but perhaps faster:

x = min(start_idx):max(end_idx);
m = sum(bsxfun(@ge,x,start_idx(:)),1)==numel(end_idx)-sum(bsxfun(@le,x,end_idx(:)),1)+1;
result = x(m);

It correctly handles empty ranges, i.e.

start_idx = [1 4 16]
end_idx   = [2 6 15];

gives

result =
     1     2     4     5     6
Kattie answered 21/7, 2016 at 11:39 Comment(0)
K
1

If you measure the elapsed time for this piece of code, after clearing the workspace, you will see that it takes an average of 0.004 sec while Divakar's code also takes roughly the same amount i.e. 0.007 sec..

start_idx=[2 4 6 8 10 12 14 16 18 20 22 24 26 28 30 32 34 36 38 40 42 44];
end_idx=[100 200 300 400 500 600 700 800 900 1000 1100 1200 1300 ...
1400 1500 1600 1700 1800 1900 2000 2100 2200];
tic
final_arr=[];
for i=1:length(start_idx)
final_arr=[final_arr,start_idx(i):end_idx(i)];
end
toc
final_arr

As you can see, I have used start and end idx arrays of longer length and made sure that the end array elements are very far away from their respective start array elements.

The elapsed time which comes after the command 'toc' always changes according to the load on the CPU.. When I measure the time, I had only 1-2 apps open other than MATLAB and cleared the workspace before executing this code. The final_arr has a count of ~24k elements yet the time it took to process the output is not very much.

Hope it helps.

Kellie answered 26/7, 2016 at 7:9 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.