matlabpermutationcell-arraycartesian-product

How to create all permutations of a 2-column cell-array?


I created a cell array of shape m x 2, each element of which is a matrix of shape d x d.

For example like this:

A = cell(8, 2);
for row = 1:8
    for col = 1:2
        A{row, col} = rand(3, 3);
    end
end

More generally, I can represent A as follows:

enter image description here

where each A_{ij} is a matrix.

Now, I need to randomly pick a matrix from each row of A, because A has m rows in total, so eventually I will pick out m matrices, which we call a combination.

Obviously, since there are only two picks for each row, there are a total of 2^m possible combinations.

My question is, how to get these 2^m combinations quickly?


It can be seen that the above problem is actually finding the Cartesian product of the following sets:

enter image description here


Solution

  • 2^m is actually a binary number, so we can use those to create linear indices. You'll get an array containing 1s and 0s, something like [1 1 0 0 1 0 1 0 1], which we can treat as column "indices", using a 0 to indicate the first column and a 1 to indicate the second.

    m = size(A, 1);
    % Build all binary numbers and create a logical matrix
    bin_idx = dec2bin(0:(2^m -1)) == '1';
    
    row = 3;  % Loop here over size(bin_idx,1) for all possible permutations
    linear_idx = [find(~bin_idx(row,:)) find(bin_idx(row,:))+m];
    A{linear_idx}  % the combination as specified by the permutation in out(row)
    

    On my R2007b version this runs virtually instant for m = 20.

    NB: this will take m * 2^m bytes of memory to store bin_idx. Where that's just 20 MB for m = 20, that's already 30 GB for m = 30, i.e. you'll be running out of memory fairly quickly, and that's for just storing permutations as booleans! If m is large in your case, you can't store all of your possibilities anyway, so I'd just select a random one:

    bin_idx = rand(m, 1);  % Generate m random numbers
    bin_idx(bin_idx > 0.5) = 1;  % Set half to 1
    bin_idx(bin_idx < 0.5) = 0;  % and half to 0
    

    Old, slow answer for large m

    perms()1 gives you all possible permutations of a given set. However, it does not take duplicate entries into account, so you'll need to call unique() to get the unique rows.

    unique(perms([1,1,2,2]), 'rows')
    
    ans =
    
         1     1     2     2
         1     2     1     2
         1     2     2     1
         2     1     1     2
         2     1     2     1
         2     2     1     1
    

    The only thing left now is to somehow do this over all possible amounts of 1s and 2s. I suggest using a simple loop:

    m = 5;
    out = [];
    
    for ii = 1:m
        my_tmp = ones(m,1);
        my_tmp(ii:end) = 2;
        out = [out; unique(perms(my_tmp),'rows')];
    end
    
    out = [out; ones(1,m)];  % Tack on the missing all-ones row
    
    out =
         2     2     2     2     2
         1     2     2     2     2
         2     1     2     2     2
         2     2     1     2     2
         2     2     2     1     2
         2     2     2     2     1
         1     1     2     2     2
         1     2     1     2     2
         1     2     2     1     2
         1     2     2     2     1
         2     1     1     2     2
         2     1     2     1     2
         2     1     2     2     1
         2     2     1     1     2
         2     2     1     2     1
         2     2     2     1     1
         1     1     1     2     2
         1     1     2     1     2
         1     1     2     2     1
         1     2     1     1     2
         1     2     1     2     1
         1     2     2     1     1
         2     1     1     1     2
         2     1     1     2     1
         2     1     2     1     1
         2     2     1     1     1
         1     1     1     1     2
         1     1     1     2     1
         1     1     2     1     1
         1     2     1     1     1
         2     1     1     1     1
         1     1     1     1     1
    

    NB: I've not initialised out, which will be slow especially for large m. Of course out = zeros(2^m, m) will be its final size, but you'll need to juggle the indices within the for loop to account for the changing sizes of the unique permutations.

    You can create linear indices from out using find()

    linear_idx = [find(out(row,:)==1);find(out(row,:)==2)+size(A,1)];
    A{linear_idx}  % the combination as specified by the permutation in out(row)
    

    Linear indices are row-major in MATLAB, thus whenever you need the matrix in column 1, simply use its row number and whenever you need the second column, use the row number + size(A,1), i.e. the total number of rows.

    Combining everything together:

    A = cell(8, 2);
    for row = 1:8
        for col = 1:2
            A{row, col} = rand(3, 3);
        end
    end
    
    m = size(A,1);
    out = [];
    
    for ii = 1:m
        my_tmp = ones(m,1);
        my_tmp(ii:end) = 2;
        out = [out; unique(perms(my_tmp),'rows')];
    end
    
    out = [out; ones(1,m)];
    
    row = 3;  % Loop here over size(out,1) for all possible permutations
    linear_idx = [find(out(row,:)==1).';find(out(row,:)==2).'+m];
    A{linear_idx}  % the combination as specified by the permutation in out(row)
    

    1 There's a note in the documentation:

    perms(v) is practical when length(v) is less than about 10.