How to fill a zeros 3D array with a random number of ones specified in a 2D matrix?

Viewed 81

The 3D array A is of size MxNxP. The 2d matrix B is of size MxN. For each row of B, there is at maximum one element greater than zero. Example:

B = [0 3 0; 2 0 0];
A = zeros(2,3,4);

I want to randomly fill A such that it has exactly three ones in the first row and second column and exactly two ones in the second row and first column. For instance:

A(:,:,1) = [0 1 0; 0 0 0]
A(:,:,2) = [0 1 0; 1 0 0]
A(:,:,3) = [0 0 0; 1 0 0]
A(:,:,4) = [0 1 0; 0 0 0]

In other words, the B elements indicate how many ones should be in the third dimension of A with the same row and column indices. I am currently able to do it with a loop but I'm looking forward to doing it in a more efficient way, hence without a loop since M and N are really large in practice. This is my code:

B = [0 3 0; 2 0 0];
A = zeros(2,3,4);
for i = 1:size(B,1)
    [r,c] = max(B(i,:));
    idx = randperm(size(A,3),r);
    A(i,c,idx) = 1;
end
1 Answers

Please see the benchmark at the bottom of this answer to address the "efficiency" in terms of computation time.

You can make your indexing loop faster

By using find, you can get the actual indices where B is non-zero which makes the loop more "targeted", then you can skip the call to max because we don't care about the whole row of B either.

This is arguably more flexible too, since it doesn't assume any constraint on the number of non-zero values in each row of B.

[M, N] = size(B);
% Set up the output
A = zeros(M,N,P);
% Get row and column indices of non-zero B
[r,c] = find(B);
for ii = 1:numel(r)
    % Index into A, given row and column, random columns according to B
    A( r(ii), c(ii), randperm( P, B(r(ii), c(ii)) ) ) = 1;
end

Full matrix operations are not necessarily fast. The below operation uses no looping, but is really memory inefficient which makes it slow for large inputs!

  1. Create a random matrix

    r = rand( M, N, P );
    
  2. Sort the random random matrix in the 3rd dimension, we don't care about the sorted matrix, just the indices; i.e. the ordering. This is a method for obtaining an entire matrix where each vector into the 3rd dimension contains 1:P shuffled (akin to using randperm in a loop).

    [~,shuff] = sort(r, 3);
    
  3. Use B as a threshold to convert the shuffled indices to 1/0. Note that this uses implicit expansion, which requires MATLAB 2016b or newer.

    A = (shuff <= B)
    

You can combine 1. and 2. into a single step, so the full code becomes

% Inputs
B = [0 3 0; 2 0 0];
P = 4;
% Get shuffled matrix
[M, N] = size(B);
[~,shuff] = sort( rand( M, N, P ), 3 );
% Get ouput
A = (shuff <= B);

Benchmark

We can run a quick benchmark to compare these methods. At the very small end, the matrix operations are faster. For large B, the efficient loop proposed at the top of this answer is significantly quicker.

This benchmark uses N = 7, P = 10, M = variable since you said in reality your M is large.

benchie

Related