4
\$\begingroup\$

I want to perform the following procedure:

I have two vectors x=[1,2,3] and y=[5,7,9]

I want to take first element of x and then add each element from y to it, then I want to take second element from x and add each element from y to it and so on... Finally, I want to save each result from each step of this procedure in a vector of the corresponding size. I know in advance that I will get the following vector r =[6,8,10,7,9,11,8,10,12].

In order to perform the following procedure I have written the following script:

 clear
  a = 1;
  b = 3;
  c = 5;
  d = 2;
  e = 9;
  x = [a:b];
  y = [c:d:e];
  [rx,cx] = size(x);
  [ry,cy] = size(y);
  r = zeros((cx*cy),1);

  for ii = x
  for jj = y
  xi = find(x==ii)    
  yi = find(y==jj)
  row = xi*cy-(cy-yi)
  r(row) = ii + jj
  end
  end

Finally, I got the result but I spent about 2 hours to come up with that. (well, I am just beginner in programming). I would be happy to know if there is any simpler and more efficient way to do that. I am especially concerned about this step, since it took the most of my time. Or should I give up coding since I took for so long for so simple problem?

xi = find(x==ii)    
yi = find(y==jj)
row = xi*cy-(cy-yi)
r(row) = ii + jj
\$\endgroup\$
4
\$\begingroup\$

Or should I give up coding since I took for so long for so simple problem?

It gets easier the more you do it. It's the same with everything else in life: practice, practice, practice!

I'll go through your code and point out issues and best practices. I hope you learn from this, and that it helps you with your next project. I also hope that me exhaustively pointing out issues doesn't discourage you.


x = [a:b];
y = [c:d:e];

The square brackets [] are for concatenating values. [a,b] creates a vector with these two values. The colon operator also creates a vector. Thus, a:b is a vector, and [a:b] concatenates that vector with... nothing. You can simplify this by simply removing the square brackets. Note that the MATLAB editor will give you this hint also (I see a little red squiggly line underneath the opening bracket in these two lines, and an orange marker on the right edge of the editor). Do pay attention to these hints, they'll teach you a lot.

I honestly don't see the reason behind the variables a through e you use here, I would simply create these vectors directly with numeric constants, which makes it easier to see what the result will be:

x = 1:3;
y = 5:2:9;

[rx,cx] = size(x);
[ry,cy] = size(y);

Here you define rx and ry, but never actually use them. MATLAB has a special syntax that you can use to avoid defining values you don't use later:

[~,cx] = size(x);
[~,cy] = size(y);

This reduces clutter (thus making it easier to read the program), and also explicitly states your intention of not wanting to use those values. Showing your intention as a programmer is important when you (or someone else) need to fix a bug later.

Alternatively, you can use the second input argument to size:

cx = size(x,2);
cy = size(y,2);

And because you have vectors, this is equivalent to

cx = length(x);
cy = length(y);

r = zeros((cx*cy),1);

You're pre-allocating the output here. Excellent! This is not strictly necessary, because MATLAB grows arrays dynamically as needed, but this is much more efficient, and considered best practice.


for ii = x
for jj = y
xi = find(x==ii)    
yi = find(y==jj)
row = xi*cy-(cy-yi)
r(row) = ii + jj
end
end

This is the "meat" of the program, and the bit you had difficulty with. There are simpler ways to express the same logic. But first, please indent your code. It makes reading the code much easier (and thus makes it easier spotting and fixing bugs). The MATLAB editor can indent for you, simply select the code and press Ctrl+I.

Kudos for using ii rather than i! i is a constant in MATLAB, and it's considered bad practice to change its meaning.


So first for an analysis of this double loop: You iterate over each of the elements of x, and for each of them you iterate over each element of y. This gives you each of the combinations that you are looking for. Next you find the index within the two arrays for the two current elements.

There are several things to note there. First, xi is not changed in the inner loop, yet you re-compute it for each new value of jj. You can move this out of the inner loop like so:

for ii = x
   xi = find(x==ii)    
   for jj = y
      yi = find(y==jj)
      ...

Second, this assumes that each element in x is unique, as is each element in y. What happens if the input is x = [1,1,2]? When ii=1, x1 will have two elements. I think the computation of row will actually still work, but row will have two elements also. If both x and y have repeated elements, you'll surely get an error thrown here.

Instead, you can turn this logic around. Loop over the indices into the vectors, and get the value at those locations using indexing, like so:

for xi = 1:cx
   ii = x(xi);   
   for yi = 1:cy
      jj = y(yi);
      ...

This code does exactly the same, but it won't trip if there are repeated values. Also note that find(x==ii) is quite expensive. Think about x having a million elements. x==ii will compare each of the million elements to ii, then find will look through this million-element array and find the index of the non-zero elements. And you repeat this for each of the million elements in x. That is a billion (or a trillion if you're American) comparisons. With the loop over indices, you're only doing a million indexing operations. This is the difference between an O(n) algorithm and an O(n²) algorithm. (Read here if you don't know about the Big O notation and would like to understand this statement.)


Next, you compute the index into the output vector:

row = xi*cy-(cy-yi)

This is an heroic find, you can be proud of this! Because MATLAB uses 1-based indexing, this computation is more difficult than necessary. In 0-based indexing it would simply be xi*cy+yi. I have always written it as (xi-1)*cy+(yi-1)+1 (i.e. turning it into a 0-based problem).

But note that row here increases by one each iteration. You could also have done this:

row = 1;
for ...
   for ...
      r(row) = ii + jj;
      row = row + 1;
   end
end

Or, you could have chosen to be "naughty" and not pre-allocate. Then you just grow r by one element each loop iteration, and you don't need to keep track of the index:

r = [];
for ...
   for ...
      r(end+1) = ii + jj;
   end
end

(Note I'm not suggesting you do this, I'm just showing the pattern.)


As an alternative, you can compute the same thing in a vectorized manner, without any loops at all. The oldest technique for this is using repmat, which I'm showing here so you understand what is happening:

r = repmat(x,cy,1) + repmat(y.',1,cx);
r = r(:);

What we're doing here is creating two matrices by replicating the two vectors. x is repeated cy times vertically, and y is first transposed (.') to make a column vector, then repeated cx times horizontally. This leads to:

1   2   3       5   5   5       6   7   8
1   2   3   +   7   7   7   =   8   9  10
1   2   3       9   9   9      10  11  12

The result is then reshaped into a column matrix (r(:) does this, you can use reshape also).

In MATLAB R2007a, they introduced the function bsxfun (a terrible name), which is meant to do this exact same operation without creating the intermediate large matrices:

r = bsxfun(@plus,x,y.');
r = r(:);

You need to know here that plus is the function that implements the + operator, you can learn this by typing help + at the command prompt. The @ creates a function handle, which is a reference to a function that you can use as a parameter to bsxfun and similar functions.

In MATLAB R2016b (it took them 10 years to make this decision) they finally caved and made it so that a+b automatically does what bsxfun(@plus,a,b) does. Some people don't like this, but to me it was long overdue. So now you can simply write:

r = x + y.';
r = r(:);

This is a lot clearer than bsxfun ever could be. I love it! And given the explanation with repmat, I hope you understand what it does and why.


Please don't be discouraged that your complicated loop could be replaced with such a short line of code. This is one of the features of MATLAB: it's easy to write code, but it takes a lot of experience to be able to simplify it like this. The more you read other people's code, and the more you write your own, the better you'll get at it.

You can, for example, read some of the functions that come with MATLAB. For example you can type edit median at the command prompt, and you'll see how the creators of MATLAB wrote the function that computes the median. Not everything is implemented as M-files, much of it is built-in, but there's still a lot of professional code that you can learn from. You can also follow the Matlab tag on Stack Overflow, answers posted there can be very instructive!

Also, sorry for making this such a long post. I got carried away... :)

\$\endgroup\$
  • \$\begingroup\$ Thank you so much for such a detailed explanation. This was excellent help. I will go through this again tomorrow so I remember every point you mentioned. Once again thanks! Was awesome job and a huge help! \$\endgroup\$ – G.T. Apr 29 '18 at 17:55
  • 1
    \$\begingroup\$ One reason for the a,b,c... constants could be something like: lower_bound = 5; upper_bound = 9; step = 2; y = lower_bound:step:upper_bound. That would make it easier to read than just y = 5:2:9. a,b,c... aren't suitable variable names if this is the case though... Very nice answer by the way! :) \$\endgroup\$ – Stewie Griffin May 19 '18 at 19:33

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.