Generate a matrix containing all combinations of elements taken from n vectors

后端 未结 4 1474
孤街浪徒
孤街浪徒 2020-11-21 11:52

This question pops up quite often in one form or another (see for example here or here). So I thought I\'d present it in a general form, and provide an answer which might se

4条回答
  •  一向
    一向 (楼主)
    2020-11-21 12:12

    Here's a do-it-yourself method that made me giggle with delight, using nchoosek, although it's not better than @Luis Mendo's accepted solution.

    For the example given, after 1,000 runs this solution took my machine on average 0.00065935 s, versus the accepted solution 0.00012877 s. For larger vectors, following @Luis Mendo's benchmarking post, this solution is consistently slower than the accepted answer. Nevertheless, I decided to post it in hopes that maybe you'll find something useful about it:

    Code:

    tic;
    v = {[1 2], [3 6 9], [10 20]};
    
    L = [0 cumsum(cellfun(@length,v))];
    V = cell2mat(v);
    
    J = nchoosek(1:L(end),length(v));
    J(any(J>repmat(L(2:end),[size(J,1) 1]),2) | ...
      any(J<=repmat(L(1:end-1),[size(J,1) 1]),2),:)  = [];
    
    V(J)
    toc
    

    gives

    ans =
    
     1     3    10
     1     3    20
     1     6    10
     1     6    20
     1     9    10
     1     9    20
     2     3    10
     2     3    20
     2     6    10
     2     6    20
     2     9    10
     2     9    20
    
    Elapsed time is 0.018434 seconds.
    

    Explanation:

    L gets the lengths of each vector using cellfun. Although cellfun is basically a loop, it's efficient here considering your number of vectors will have to be relatively low for this problem to even be practical.

    V concatenates all the vectors for easy access later (this assumes you entered all your vectors as rows. v' would work for column vectors.)

    nchoosek gets all the ways to pick n=length(v) elements from the total number of elements L(end). There will be more combinations here than what we need.

    J =
    
     1     2     3
     1     2     4
     1     2     5
     1     2     6
     1     2     7
     1     3     4
     1     3     5
     1     3     6
     1     3     7
     1     4     5
     1     4     6
     1     4     7
     1     5     6
     1     5     7
     1     6     7
     2     3     4
     2     3     5
     2     3     6
     2     3     7
     2     4     5
     2     4     6
     2     4     7
     2     5     6
     2     5     7
     2     6     7
     3     4     5
     3     4     6
     3     4     7
     3     5     6
     3     5     7
     3     6     7
     4     5     6
     4     5     7
     4     6     7
     5     6     7
    

    Since there are only two elements in v(1), we need to throw out any rows where J(:,1)>2. Similarly, where J(:,2)<3, J(:,2)>5, etc... Using L and repmat we can determine whether each element of J is in its appropriate range, and then use any to discard rows that have any bad element.

    Finally, these aren't the actual values from v, just indices. V(J) will return the desired matrix.

提交回复
热议问题