web-dev-qa-db-ja.com

n個のベクトルから取得した要素のすべての組み合わせを含む行列を生成します

この質問は、頻繁にポップアップ表示されます(たとえば、 here または here を参照)。だから私はそれを一般的な形で提示し、将来の参考に役立つかもしれない答えを提供すると思った。

サイズが異なる可能性のある任意の数のnベクトルが与えられた場合、それらのベクトル(デカルト積)から取得した要素のすべての組み合わせを行が記述するn- column行列を生成します。

例えば、

vectors = { [1 2], [3 6 9], [10 20] }

与えるべき

combs = [ 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 ]
52
Luis Mendo

ndgrid 関数はほとんど答えを提供しますが、警告が1つあります。それを呼び出すには、n出力変数を明示的に定義する必要があります。 nは任意なので、最良の方法は コンマ区切りリストncellsを使用してセル配列から生成)を使用して出力として使用することです。次に、結果のn行列は、目的のn- column行列に連結されます。

vectors = { [1 2], [3 6 9], [10 20] }; %// input data: cell array of vectors

n = numel(vectors); %// number of vectors
combs = cell(1,n); %// pre-define to generate comma-separated list
[combs{end:-1:1}] = ndgrid(vectors{end:-1:1}); %// the reverse order in these two
%// comma-separated lists is needed to produce the rows of the result matrix in
%// lexicographical order 
combs = cat(n+1, combs{:}); %// concat the n n-dim arrays along dimension n+1
combs = reshape(combs,[],n); %// reshape to obtain desired matrix
46
Luis Mendo

もう少し簡単です... Neural Networkツールボックスがある場合は、単に combvec を使用できます。

vectors = {[1 2], [3 6 9], [10 20]};
combs = combvec(vectors{:}).' % Use cells as arguments

これは、わずかに異なる順序でマトリックスを返します。

combs =

     1     3    10
     2     3    10
     1     6    10
     2     6    10
     1     9    10
     2     9    10
     1     3    20
     2     3    20
     1     6    20
     2     6    20
     1     9    20
     2     9    20

問題のマトリックスが必要な場合は、 sortrows を使用できます。

combs = sortrows(combvec(vectors{:}).')
% Or equivalently as per @LuisMendo in the comments: 
% combs = fliplr(combvec(vectors{end:-1:1}).') 

与える

combs =

     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

combvecの内部を見ると(タイプedit combvecコマンドウィンドウで)、@ LuisMendoの答えとは異なるコードを使用していることがわかります。どちらが全体的に効率的かは言えません。

行が以前のセル配列に似ている行列がある場合は、次を使用できます。

vectors = [1 2;3 6;10 20];
vectors = num2cell(vectors,2);
combs = sortrows(combvec(vectors{:}).')
27
horchler

提案された2つのソリューションのベンチマークを実行しました。ベンチマークコードは timeit function に基づいており、この投稿の最後に含まれています。

私は2つのケースを考えます:サイズnの3つのベクトル、サイズn/10nおよびn*10の3つのベクトル(両方とも同じ数の組み合わせを与えます) 。 nは最大240まで変化します(ラップトップコンピューターで仮想メモリの使用を避けるためにこの値を選択します)。

結果を次の図に示します。 ndgridベースのソリューションは、一貫してcombvecよりも時間がかかるようです。 combvecにかかる時間は、サイズが異なる場合でも規則的に少しずつ変化することに注意することも興味深いです。

enter image description here


ベンチマークコード

ndgridベースのソリューションの関数:

function combs = f1(vectors)
n = numel(vectors); %// number of vectors
combs = cell(1,n); %// pre-define to generate comma-separated list
[combs{end:-1:1}] = ndgrid(vectors{end:-1:1}); %// the reverse order in these two
%// comma-separated lists is needed to produce the rows of the result matrix in
%// lexicographical order
combs = cat(n+1, combs{:}); %// concat the n n-dim arrays along dimension n+1
combs = reshape(combs,[],n);

combvecソリューションの関数:

function combs = f2(vectors)
combs = combvec(vectors{:}).';

これらの関数でtimeitを呼び出して時間を測定するスクリプト:

nn = 20:20:240;
t1 = [];
t2 = [];
for n = nn;
    %//vectors = {1:n, 1:n, 1:n};
    vectors = {1:n/10, 1:n, 1:n*10};
    t = timeit(@() f1(vectors));
    t1 = [t1; t];
    t = timeit(@() f2(vectors));
    t2 = [t2; t];
end
13
Luis Mendo

これは、nchoosekを使用して喜びでくすぐった日曜大工のメソッドです。ただし、@ Luis Mendoが受け入れたnotよりも優れています解決。

与えられた例では、1,000回の実行後、このソリューションは平均0.00065935秒でマシンを使用しましたが、受け入れられたソリューションは0.00012877秒でした。大きなベクトルの場合、@ Luis Mendoのベンチマークポストに従って、このソリューションは一貫して受け入れられている答えよりも低速です。それにもかかわらず、私はあなたがそれについて何か有用なものを見つけるかもしれないことを期待してそれを投稿することにしました:

コード:

_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
_

与える

_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.
_

説明:

Lは、cellfunを使用して各ベクトルの長さを取得します。 cellfunは基本的にループですが、この問題を実用的にするにはベクトルの数を比較的少なくする必要があるため、ここでは効率的です。

Vは、後で簡単にアクセスできるようにすべてのベクトルを連結します(これはすべてのベクトルを行として入力したことを前提としています。v 'は列ベクトルに対して機能します)。

nchoosekは、要素の総数n=length(v)からL(end)要素を選択するすべての方法を取得します。 ここに必要な組み合わせよりも多くの組み合わせがあります。

_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
_

v(1)には2つの要素しかないため、J(:,1)>2の行をすべて破棄する必要があります。同様に、J(:,2)<3J(:,2)>5など... Lおよびrepmatを使用して、Jの各要素を判断できます。が適切な範囲にある場合、anyを使用して、不良要素がある行を破棄します。

最後に、これらはvからの実際の値ではなく、単なるインデックスです。 V(J)は、目的のマトリックスを返します。

2
Geoff