对于
等矩阵A = [...
12 34 67;
90 78 15;
10 71 24];
如果它是更大矩阵的子集,我们怎么能有效地确定?
B = [...
12 34 67; % found
89 67 45;
90 78 15; % found
10 71 24; % found, so A is subset of B.
54 34 11];
以下是条件:
修改
似乎ismember
对于这个问题的情况,只召唤几次就可以了。我最初的印象是由于之前的经验,在嵌套循环中多次调用ismember
导致性能最差。
clear all; clc
n = 200000;
k = 10;
B = randi(n,n,k);
f = randperm(n);
A = B(f(1:1000),:);
tic
assert(sum(ismember(A,B,'rows')) == size(A,1));
toc
tic
assert(all(any(all(bsxfun(@eq,B,permute(A,[3,2,1])),2),1))); %user2999345
toc
导致:
Elapsed time is 1.088552 seconds.
Elapsed time is 12.154969 seconds.
以下是更多基准:
clear all; clc
n = 20000;
f = randperm(n);
k = 10;
t1 = 0;
t2 = 0;
t3 = 0;
for i=1:7
B = randi(n,n,k);
A = B(f(1:n/10),:);
%A(100,2) = 0; % to make A not submat of B
tic
b = sum(ismember(A,B,'rows')) == size(A,1);
t1 = t1+toc;
assert(b);
tic
b = ismember_mex(A,sortrows(B));
t2 = t2+toc;
assert(b);
tic
b = issubmat(A,B);
t3 = t3+toc;
assert(b);
end
George's skm's
ismember | ismember_mex | issubmat
n=20000,k=10 0.6326 0.1064 11.6899
n=1000,k=100 0.2652 0.0155 0.0577
n=1000,k=1000 1.1705 0.1582 0.2202
n=1000,k=10000 13.2470 2.0033 2.6367
*issubmat eats RAM when n or k is over 10000!
*issubmat(A,B), A is being checked as submat of B.
答案 0 :(得分:1)
对于小矩阵ismember
应该足够了。
用法:ismember(B,A,'rows')
ans =
1
0
1
1
0
我在这里提出这个答案,强调需要具有更高性能的解决方案。只有在没有更好的解决方案时,我才接受这个答案。
答案 1 :(得分:1)
使用ismember
,如果A
中有B
行,而另一行丢失,可能会错误地指出A
是B
的成员}。如果A
和B
的行不需要处于相同的顺序,则以下解决方案是合适的。但是,我还没有测试过大型矩阵的性能。
A = [...
34 12 67;
90 78 15;
10 71 24];
B = [...
34 12 67; % found
89 67 45;
90 78 15; % found
10 71 24; % found, so A is subset of B.
54 34 11];
A = permute(A,[3 2 1]);
rowIdx = all(bsxfun(@eq,B,A),2);
colIdx = any(rowIdx,1);
isAMemberB = all(colIdx);
答案 2 :(得分:1)
您已经说过列数< = 10.此外,如果矩阵元素都是可以表示为字节的整数,则可以将每行编码为两个64位整数。这样可以将比较次数减少64倍。
对于一般情况,对于薄矩阵,以下情况可能不是那么好,但由于3级乘法导致矩阵变胖,因此可以很好地扩展:
function yes = is_submat(A,B)
ma = size(A, 1);
mb = size(B, 1);
n = size(B, 2);
yes = false;
if ma >= mb
a = A(:,1);
b = B(:,1);
D = (0 == bsxfun(@minus, a, b'));
q = any(D, 2);
yes = all(any(D,1));
if yes && (n > 1)
A = A(q, :);
C = B*A';
za = sum(A.*A, 2);
zb = sum(B.*B, 2);
Z = sqrt(zb)*sqrt(za');
[~, ix] = max(C./Z, [], 2);
A = A(ix,:);
yes = all(A(:) == B(:));
end
end
end
在上文中,我使用了当两个单位向量相等时点积最大化的事实。
对于具有大量独特元素的脂肪矩阵(比如说5000多列),性能节拍非常方便,但除此之外,它比成员慢。对于薄矩阵,成员的速度要快一个数量级。
此功能的最佳案例测试:
A = randi(50000, [10000, 10000]);
B = A(2:3:end, :);
B = B(randperm(size(B,1)),:);
fprintf('%s: %u\n', 'Number of columns', size(A,2));
fprintf('%s: %u\n', 'Element spread', 50000);
tic; is_submat(A,B); toc;
tic; all(ismember(B,A,'rows')); toc;
fprintf('________\n\n');
is_submat_test;
列数:10000
元素差价:50000
经过的时间是10.713310秒(is_submat)。
经过的时间是17.446682秒(ismember)。
所以我不得不承认,全面的成员似乎要好得多。
编辑:当只有一列时编辑纠正错误 - 修复此问题也会导致更高效的代码。此前的版本也没有区分正数和负数。增加了时间测试。
答案 3 :(得分:1)
似乎ismember很难被击败,至少使用MATLAB代码。我创建了一个可以使用MEX编译器使用的C实现。
#include "mex.h"
#if MX_API_VER < 0x07030000
typedef int mwIndex;
typedef int mwSize;
#endif /* MX_API_VER */
#include <math.h>
#include <stdlib.h>
#include <string.h>
int ismember(const double *y, const double *x, int yrow, int xrow, int ncol);
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
mwSize xcol, ycol, xrow, yrow;
/* output data */
int* result;
/* arguments */
const mxArray* y;
const mxArray* x;
if (nrhs != 2)
{
mexErrMsgTxt("2 input required.");
}
y = prhs[0];
x = prhs[1];
ycol = mxGetN(y);
yrow = mxGetM(y);
xcol = mxGetN(x);
xrow = mxGetM(x);
/* The first input must be a sparse matrix. */
if (!mxIsDouble(y) || !mxIsDouble(x))
{
mexErrMsgTxt("Input must be of type 'double'.");
}
if (xcol != ycol)
{
mexErrMsgTxt("Inputs must have the same number of columns");
}
plhs[0] = mxCreateLogicalMatrix(1, 1);
result = mxGetPr(plhs[0]);
*result = ismember(mxGetPr(y), mxGetPr(x), yrow, xrow, ycol);
}
int ismemberinner(const double *y, int idx, const double *x, int yrow, int xrow, int ncol) {
int from, to, i;
from = 0;
to = xrow-1;
for(i = 0; i < ncol; ++i) {
// Perform binary search
double yi = *(y + i * yrow + idx);
double *curx = x + i * xrow;
int l = from;
int u = to;
while(l <= u) {
int mididx = l + (u-l)/2;
if(yi < curx[mididx]) {
u = mididx-1;
}
else if(yi > curx[mididx]) {
l = mididx+1;
}
else {
// This can be further optimized by performing additional binary searches
for(from = mididx; from > l && curx[from-1] == yi; --from);
for(to = mididx; to < u && curx[to+1] == yi; ++to);
break;
}
}
if(l > u) {
return 0;
}
}
return 1;
}
int ismember(const double *y, const double *x, int yrow, int xrow, int ncol) {
int i;
for(i = 0; i < yrow; ++i) {
if(!ismemberinner(y, i, x, yrow, xrow, ncol)) {
return 0;
}
}
return 1;
}
使用以下方式编译:
mex -O ismember_mex.c
可以如下调用:
ismember_mex(x, sortrows(x))
首先,它假设矩阵的列具有相同的大小。它的工作原理是首先对较大矩阵的行进行排序(在本例中为x,函数的第二个参数)。然后,采用一种二进制搜索来识别较小矩阵(以下称为y)的行是否包含在x中。这是针对y的每一行单独完成的(参见ismember
C函数)。
对于给定的y行,它从第一个条目开始,并使用二进制搜索找到与x的第一列匹配的索引范围(使用from
和to
变量)。对于其余条目重复此操作,除非找不到某个值,在这种情况下,它将终止并返回0.
我尝试在MATLAB中实现这个想法,但它没有那么好用。关于性能,我发现:(a)如果存在不匹配,它通常比ismember
(b)快得多,以防x和y中的值范围很大,它再次快于{{ 1}}和(c)如果一切都匹配并且x和y中可能值的数量很小(例如小于1000),那么在某些情况下ismember
可能会更快。
最后,我想指出C实现的某些部分可能会进一步优化。
编辑1
我修正了警告并进一步改进了功能。
ismember
使用此版本,我无法确定#include "mex.h"
#include <math.h>
#include <stdlib.h>
#include <string.h>
int ismember(const double *y, const double *x, unsigned int nrowy, unsigned int nrowx, unsigned int ncol);
void mexFunction(int nlhs, mxArray *plhs[],
int nrhs, const mxArray *prhs[])
{
unsigned int xcol, ycol, nrowx, nrowy;
/* arguments */
const mxArray* y;
const mxArray* x;
if (nrhs != 2)
{
mexErrMsgTxt("2 inputs required.");
}
y = prhs[0];
x = prhs[1];
ycol = (unsigned int) mxGetN(y);
nrowy = (unsigned int) mxGetM(y);
xcol = (unsigned int) mxGetN(x);
nrowx = (unsigned int) mxGetM(x);
/* The first input must be a sparse matrix. */
if (!mxIsDouble(y) || !mxIsDouble(x))
{
mexErrMsgTxt("Input must be of type 'double'.");
}
if (xcol != ycol)
{
mexErrMsgTxt("Inputs must have the same number of columns");
}
plhs[0] = mxCreateLogicalScalar(ismember(mxGetPr(y), mxGetPr(x), nrowy, nrowx, ycol));
}
int ismemberinner(const double *y, const double *x, unsigned int nrowy, unsigned int nrowx, unsigned int ncol) {
unsigned int from = 0, to = nrowx-1, i;
for(i = 0; i < ncol; ++i) {
// Perform binary search
const double yi = *(y + i * nrowy);
const double *curx = x + i * nrowx;
unsigned int l = from;
unsigned int u = to;
while(l <= u) {
const unsigned int mididx = l + (u-l)/2;
const double midx = curx[mididx];
if(yi < midx) {
u = mididx-1;
}
else if(yi > midx) {
l = mididx+1;
}
else {
{
// Binary search to identify smallest index of x that equals yi
// Equivalent to for(from = mididx; from > l && curx[from-1] == yi; --from)
unsigned int limit = mididx;
while(curx[from] != yi) {
const unsigned int mididx = from + (limit-from)/2;
if(curx[mididx] < yi) {
from = mididx+1;
}
else {
limit = mididx-1;
}
}
}
{
// Binary search to identify largest index of x that equals yi
// Equivalent to for(to = mididx; to < u && curx[to+1] == yi; ++to);
unsigned int limit = mididx;
while(curx[to] != yi) {
const unsigned int mididx = limit + (to-limit)/2;
if(curx[mididx] > yi) {
to = mididx-1;
}
else {
limit = mididx+1;
}
}
}
break;
}
}
if(l > u) {
return 0;
}
}
return 1;
}
int ismember(const double *y, const double *x, unsigned int nrowy, unsigned int nrowx, unsigned int ncol) {
unsigned int i;
for(i = 0; i < nrowy; ++i) {
if(!ismemberinner(y + i, x, nrowy, nrowx, ncol)) {
return 0;
}
}
return 1;
}
更快的任何情况。此外,我注意到ismember
很难被击败的一个原因是它使用了机器的所有内核!当然,我提供的功能也可以进行优化,但这需要更多的努力。
最后,在使用我的实现之前,我会建议你做大量的测试。我做了一些测试,它似乎工作,但我建议你也做一些额外的测试。