生成一个矩阵,其中包含取自 n 个向量的元素的所有组合

Posted

技术标签:

【中文标题】生成一个矩阵,其中包含取自 n 个向量的元素的所有组合【英文标题】:Generate a matrix containing all combinations of elements taken from n vectors 【发布时间】:2014-03-20 15:50:16 【问题描述】:

这个问题经常以一种或另一种形式出现(例如参见here 或here)。所以我想我会以一般形式呈现它,并提供一个答案,以供将来参考。

给定任意数量的n 可能不同大小的向量,生成一个n-列矩阵,其行描述取自这些向量的所有元素组合(笛卡尔积)。

例如,

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 ]

【问题讨论】:

嘿@bla,你最近摆脱了一些代表! :-) 我决定开始一个 SO 版本的“The Giving Pledge”,即我 90% 的代表要回馈给贡献者,2-3K 对我来说就足够了...... 哇!这是相当多的代表。但是请考虑一下:您应该与其他贡献者一样多地获得该代表。如果这些贡献者应用该标准,那么一切都将被重新分配,并再次重新分配,最终或多或少像一开始一样:-) @bla 无论如何,我对赏金感到非常荣幸。谢谢! 【参考方案1】:

ndgrid 函数几乎给出了答案,但有一个警告:n 输出变量必须显式定义才能调用它。由于n 是任意的,因此最好的方法是使用comma-separated list(从带有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
[combsend:-1:1] = ndgrid(vectorsend:-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

【讨论】:

这确实是一个不错的技巧。这是generalize the Cartesian product to N dimensions 的有用方法。 cat(n+1,...) 部分特别聪明。 ;) @chappjc 谢谢! In the past 在连接它们之前,我使用了对 cellfun 的调用来线性化 n-dim 数组,但是是的,我更喜欢这个 我接受我自己的答案,因为根据我的benchmarking,事实证明它更快。也感谢@horchler 的回答! 这不是 allcomb 函数在 MATLAB 文件交换中的作用吗? (只是问)。 我刚刚用allcomb做了一些测试。我确认它会产生与我的答案相同的结果,并且顺序相同。至于性能,allcomb 似乎只比我的解决方案花费的时间略多@ParagS.Chandakkar【参考方案2】:

简单一点……如果你有神经网络工具箱,你可以简单地使用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(vectorsend:-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:).')

【讨论】:

好建议。我目前没有,但很高兴知道。 +1 我不知道那个功能。可惜我没有那个工具箱。也许您可以使用combs = fliplr(combvec(vectorsend:-1:1).') 而不是使用sortrows 来节省时间? @horchler 我已经用过很多次了,所以觉得有义务+1 :)【参考方案3】:

我已经对两个提议的解决方案进行了一些基准测试。基准测试代码基于timeit function,包含在本文末尾。

我考虑两种情况:三个大小为n 的向量,以及三个大小分别为n/10nn*10 的向量(两种情况给出相同数量的组合)。 n 最大变化为 240(我选择这个值是为了避免在我的笔记本电脑中使用虚拟内存)。

结果如下图所示。基于ndgrid 的解决方案被认为比combvec 花费的时间更少。值得注意的是,combvec 所花费的时间在不同大小的情况下变化不大。


基准代码

基于ndgrid的解决方案的功能:

function combs = f1(vectors)
n = numel(vectors); %// number of vectors
combs = cell(1,n); %// pre-define to generate comma-separated list
[combsend:-1:1] = ndgrid(vectorsend:-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

【讨论】:

我从未使用过 matlab,所以我不知道我在 Java ***.com/a/10083452/312172 中的解决方案是否适用于 mathlab。它在不生成笛卡尔积的情况下工作,而是为每个给定索引计算在该索引处给定的元素组合。所以它可以用于必须考虑速度和内存使用的地方。它可以被 long 或 BigInteger 采用,好吧,至少在很长一段时间内,我应该这样做。单次访问总是有点费时,但对于数十亿范围内的随机访问,它仍然应该在恒定时间内工作。也许你感兴趣。【参考方案4】:

这是一种自己动手的方法,使用nchoosek,虽然它比@Luis Mendo 公认的解决方案更好,但让我高兴地咯咯笑。

对于给出的示例,在运行 1000 次后,此解决方案平均占用我的机器 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 获得从元素总数L(end) 中挑选n=length(v) 元素的所有方法。 这里的组合会比我们需要的多。

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) 中只有两个元素,我们需要丢弃所有包含J(:,1)&gt;2 的行。同理,其中J(:,2)&lt;3J(:,2)&gt;5等...使用Lrepmat我们可以判断J的每个元素是否在其合适的范围内,然后使用any丢弃具有任何不良元素。

最后,这些不是来自v 的实际值,只是索引。 V(J) 将返回所需的矩阵。

【讨论】:

最好有其他方法来解决问题!只是我通常的评论:' 不是转置; .' 谢谢路易斯!你是对的,但经过重新考虑,它可以使用 v' 或 v'。因为 V 的形状最终变得无关紧要。 @Luis Mendo doh!我偶然发现了this post ...感谢您原谅我的无知:) 您找到了很好的问答! :-D

以上是关于生成一个矩阵,其中包含取自 n 个向量的元素的所有组合的主要内容,如果未能解决你的问题,请参考以下文章

R中N个元素与q个元素的组合

如何为排除的元素生成带有零的向量组合矩阵?

R - 给定一个矩阵和一个幂,生成多个矩阵,其中包含矩阵列的所有唯一组合

C ++犰狳生成给定vec或矩阵的索引uvec而不循环它

Leetcode练习(Python):数组类:第54题:给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。

生成一个包含所有可能掷骰子结果的矩阵(忽略顺序)