2

What is the most efficient way of generating

>> A

A =

     0     1     1
     1     1     0
     1     0     1
     0     0     0

with

>> B = [2 3; 1 2; 1 3]

B =

     2     3
     1     2
     1     3

in MATLAB?

E.g., B(1, :), which is [2 3], means that A(2, 1) and A(3, 1) are true.

My attempt still requires one for loop, iterating through B's row. Is there a loop-free or more efficient way of doing this?

Robert Seifert
  • 25,078
  • 11
  • 68
  • 113
Sibbs Gambling
  • 19,274
  • 42
  • 103
  • 174

2 Answers2

2

This is one way of many, though sub2ind is the dedicated function for that:

%// given row indices
B = [2 3; 1 2; 1 3]
%// size of row index matrix
[n,m] = size(B)
%// size of output matrix
[N,M] = deal( max(B(:)), n)
%// preallocation of output matrix
A = zeros(N,M)
%// get col indices to given row indices
cols = bsxfun(@times, ones(n,m),(1:n).')
%// set values
A( sub2ind([N,M],B,cols) ) = 1

A =

     0     1     1
     1     1     0
     1     0     1

If you want a logical matrix, change the following to lines

A = false(N,M)
A( sub2ind([N,M],B,cols) ) = true

Alternative solution

%// given row indices
B = [2 3; 1 2; 1 3];
%// number if rows
r = 4;  %// e.g. = max(B(:))
%// number if cols
c = 3;  %// size(B,1)

%// preallocation of output matrix
A = zeros(r,c);
%// set values
A( bsxfun(@plus, B.', 0:r:(r*(c-1))) ) = 1;
Robert Seifert
  • 25,078
  • 11
  • 68
  • 113
  • +1 for the use of sub2ind. Surprisingly, this is slower (measured by `tic toc`) than looping through `B`'s elements (my real `B` is `100x5`). Probably because `B` only has 500 elements, so it's actually more efficient to just loop through all of them? – Sibbs Gambling Jul 16 '16 at 16:46
  • 1
    @SibbsGambling 500 elements is small and its possible. But the use of `tic/toc` is inappropriate for benchmarks, [have a look here](http://stackoverflow.com/a/21121323/2605073) for a more appropriate benchmark using `timeit`. – Robert Seifert Jul 16 '16 at 17:33
  • @SibbsGambling I added an alternative solution which should be faster. Also for benchmark: make sure you didn't forgot any `;` :) – Robert Seifert Jul 16 '16 at 17:45
2

Here's a way, using the sparse function:

A = full(sparse(cumsum(ones(size(B))), B, 1));

This gives

A =
     0     1     1
     1     1     0
     1     0     1

If you need a predefined number of rows in the output, say r (in your example r = 4):

A = full(sparse(cumsum(ones(size(B))), B, 1, 4, size(B,1)));

which gives

A =
     0     1     1
     1     1     0
     1     0     1
     0     0     0

You can equivalently use the accumarrray function:

A = accumarray([repmat((1:size(B,1)).',size(B,2),1), B(:)], 1);

gives

A =
     0     1     1
     1     1     0
     1     0     1

Or with a predefined number of rows, r = 4,

A = accumarray([repmat((1:size(B,1)).',size(B,2),1), B(:)], 1, [r size(B,1)]);

gives

A =
     0     1     1
     1     1     0
     1     0     1
     0     0     0
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
  • +1; thanks for providing an alternative! I realize that in my real problem, just looping through `B` is faster than these methods... My real `B` is `100x5`. Probably because `B` only has 500 elements, so it's actually more efficient to just loop through all of them? – Sibbs Gambling Jul 16 '16 at 16:47
  • 1
    I don't know... Timing is always difficult. Whichever works best for you :-) – Luis Mendo Jul 16 '16 at 17:03