确定矩阵A是否是矩阵B的子集

时间:2017-03-26 17:42:43

标签: matlab matrix

对于

等矩阵
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];

以下是条件:

  • 所有数字都是整数
  • 矩阵如此之大,即行#> 100000,列#可能在1到10之间变化(A和B相同)。

修改 似乎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. 

4 个答案:

答案 0 :(得分:1)

对于小矩阵ismember应该足够了。 用法:ismember(B,A,'rows')

ans =
   1
   0
   1
   1
   0

我在这里提出这个答案,强调需要具有更高性能的解决方案。只有在没有更好的解决方案时,我才接受这个答案。

答案 1 :(得分:1)

使用ismember,如果A中有B行,而另一行丢失,可能会错误地指出AB的成员}。如果AB的行不需要处于相同的顺序,则以下解决方案是合适的。但是,我还没有测试过大型矩阵的性能。

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的第一列匹配的索引范围(使用fromto变量)。对于其余条目重复此操作,除非找不到某个值,在这种情况下,它将终止并返回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很难被击败的一个原因是它使用了机器的所有内核!当然,我提供的功能也可以进行优化,但这需要更多的努力。

最后,在使用我的实现之前,我会建议你做大量的测试。我做了一些测试,它似乎工作,但我建议你也做一些额外的测试。