生成一个矩阵,其中包含取自 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(从带有n
cells 的单元阵列生成)作为输出。然后将生成的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/10
、n
和n*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)>2
的行。同理,其中J(:,2)<3
、J(:,2)>5
等...使用L
和repmat
我们可以判断J
的每个元素是否在其合适的范围内,然后使用any
丢弃具有任何不良元素。
最后,这些不是来自v
的实际值,只是索引。 V(J)
将返回所需的矩阵。
【讨论】:
最好有其他方法来解决问题!只是我通常的评论:'
不是转置; .'
是
谢谢路易斯!你是对的,但经过重新考虑,它可以使用 v' 或 v'。因为 V 的形状最终变得无关紧要。
@Luis Mendo doh!我偶然发现了this post ...感谢您原谅我的无知:)
您找到了很好的问答! :-D以上是关于生成一个矩阵,其中包含取自 n 个向量的元素的所有组合的主要内容,如果未能解决你的问题,请参考以下文章
R - 给定一个矩阵和一个幂,生成多个矩阵,其中包含矩阵列的所有唯一组合
Leetcode练习(Python):数组类:第54题:给定一个包含 m x n 个元素的矩阵(m 行, n 列),请按照顺时针螺旋顺序,返回矩阵中的所有元素。