Matlab索引转逻辑索引

10

我已经给出了一个索引列表,例如 i = [3 5] 和一个向量v = 1:6。 我需要一个函数f,该函数返回给定索引i的逻辑映射结果,针对向量v,例如:

f(i, length(v)) = [0 0 1 0 1 0]

由于我将调用此函数数百万次,因此希望尽可能快地运行它。是否有一个内置函数可以执行此任务?


2
使用 i= [3 5],逻辑映射难道不应该是 [0 0 1 0 1 0] 吗? - H.Muster
@H.Muster:当然,谢谢你注意到这个问题! - blubb
请注意,这是“find”的逆操作,即find(ismember(v,i))返回i - TTT
7个回答

10

我知道我来晚了,但我真的想找到一个和ismember一样优美的快速解决方案。实际上有这样一个方案,它使用了未记录的ismembc函数:

ismembc(v, i)

基准测试

N = 7;
i = [3 5];

%// slayton's solution
tic
for ii = 1:1e5
    clear idx;
    idx(N) = false;
    idx(i) = true;
end
toc

%// H.Muster's solution
tic
for ii = 1:1e5
    v = 1:N;
    idx = ismember(v, i);
end
toc

%// Jonas' solution
tic
for ii = 1:1e5
    idx = sparse(i, 1, true, N, 1);
end
toc

%// ismembc solution
tic
for ii = 1:1e5
    v = 1:N;
    idx = ismembc(v, i);
end
toc

这是我得到的内容:

Elapsed time is 1.482971 seconds.
Elapsed time is 6.369626 seconds.
Elapsed time is 2.039481 seconds.
Elapsed time is 0.776234 seconds.

惊人的是,ismembc确实是最快的!

编辑:
对于非常大的N值(即当v是一个大型数组时),更快的解决方案实际上是Slayton的(以及HebeleHododo的)。你有很多策略可供选择,请仔细选择 :)

由H.Muster编辑:
这里是包括_ismemberoneoutput在内的基准测试结果:

Slayton's solution:
   Elapsed time is 1.075650 seconds.
ismember:
   Elapsed time is 3.163412 seconds.
ismembc:
   Elapsed time is 0.390953 seconds.
_ismemberoneoutput:
   Elapsed time is 0.477098 seconds.

有趣的是,Jonas的解决方案对我来说无法运行,因为我遇到了Index exceeds matrix dimensions.错误...

由hoogamaphone编辑:
值得注意的是,ismembc要求两个输入都是数字、排序、非稀疏、非NaN值,这是一些细节,在源文档中很容易被忽略。


1
我用你的基准测试了 _ismemberoneoutput,发现它比 ismembc 稍微慢一些。这对 @blubb 也可能是有趣的信息。 - H.Muster
我忘记对HebeleHododo的解决方案进行基准测试了,它的性能几乎与slayton的一样快。此外,请确保在Jonas的解决方案中使用正确的变量(我最初也遇到了这个错误)。 - Eitan T
1
感谢您的精彩汇编。这是使SE社区如此独特的完美例子! - blubb
1
如果索引是排序的,ismembc 函数运行良好,否则它将失败。 - hoogamaphone
@Chris 是的,但问题中暗示了 v 已经排序。 - Eitan T
显示剩余6条评论

5

只需创建一个逻辑索引向量,然后将所需位置设置为true/false即可。

idx = false( size( v) );
idx( i ) = true;

这可以包装在一个函数中,就像这样:
function idx = getLogicalIdx(size, i)
  idx = false(size);
  idx(i) = true;
end

如果您需要为每个操作分配相同大小的索引向量,请先分配一次向量,然后在每次迭代中对其进行操作。
idx = false(size(v)); % allocate the vector
while( keepGoing)

  idx(i) = true; % set the desired values to true for this iteration

  doSomethingWithIndecies(idx);

  idx(i) = false; % set indices back to false for next iteration

end

如果您真的需要更好的性能,那么可以编写一个mex函数来为您执行此操作。这是我编写的一个非常基本的、未经测试的函数,大约比其他方法快2倍:

#include <math.h>
#include <matrix.h>
#include <mex.h>

void mexFunction(int nlhs, mxArray *plhs[],
                 int nrhs, const mxArray *prhs[])
{
    double M;
    double *in;

    M = mxGetScalar(prhs[0]);
    in = mxGetPr(prhs[1]);
    size_t N = mxGetNumberOfElements(prhs[1]);



    plhs[0] = mxCreateLogicalMatrix( M,1 );
    mxLogical *out= mxGetLogicals( plhs[0] );


    int i, ind;
    for (i=0; i<N; i++){
        out[ (int)in[i] ] = 1;
    }

}

在Matlab中,有几种不同的向量分配方式。其中一些比其他方式更快,可以参考这篇未经文件化的Matlab文章进行了解:

以下是一些快速对比不同方法的基准测试。最后一种方法是最快的,但需要您为每个操作使用相同大小的逻辑索引向量。

N = 1000;
ITER = 1e5;

i = randi(5000,100,1);
sz = [N, 1];

fprintf('Create using false()\n');
tic;
for j = 1:ITER
    clear idx;
    idx = false( N, 1 );
    idx(i) = true;
end
toc;

fprintf('Create using indexing\n');
tic;
for j = 1:ITER
    clear idx;
    idx(N) = false;
    idx(i) = true;
end
toc;

fprintf('Create once, update as needed\n');
tic;
idx = false(N,1);
for j = 1:ITER
    idx(i) = true;
    idx(i) = false;
end
toc;

fprintf('Create using ismembc\n');
a = ones(N,1);
tic;
for j = 1:ITER

    idx = ismembc(1:N, i);
end
toc;

谢谢。这个效果还算不错,但是我需要执行这个操作数百万次。有什么办法可以加快速度吗? - blubb
@blubb 是的,你可以用多种方式加快速度。其中大部分与预分配有关,例如,如果你要创建相同长度的逻辑索引向量,则先创建它,然后再进行操作... - slayton

5

您可以使用ismember

 i = [3 5];
 v = 1:6;

 ismember(v,i)

将返回

ans =

     0     0     1     0     1     0

对于可能更快的版本,您可以尝试

builtin('_ismemberoneoutput', v, i)

请注意,我仅测试了像您指定的行向量这样的情况。

这就是我一直在寻找的。但需要注意的是,在我的特定情况下,如果使用预分配实现,@slayton的解决方案要快大约30%。 - blubb
如果您还没有向量v,那么这将会很慢,因为每次调用此函数时都需要分配一个包含1:N的向量。 - slayton
@blubb:请注意Eithan的回答,它与ismember一样好,但速度明显更快。接受他的答案似乎是公平的,而不是我的。 - H.Muster
@H.Muster 谢谢,但是当 v 是一个大数组时,它比 slayton 的解决方案要慢。 - Eitan T
@EitanT:是的,但无论出于何种原因,OP更喜欢ismember而不是slayton的解决方案,尽管它更慢。因此,他应该更喜欢你的解决方案... - H.Muster

2
只需使用idx矩阵创建一个新变量,它会自动填充零:
idx = [3 5];
a(idx) = true

不需要使用函数,也不需要传递长度,除非您也想要结尾的零。


因为a将会是[0 0 1 0 1]...? - Dan
如果您喜欢,可以将其设置为true。 - Dan
但输入大小为1x6而不是1x5。 - slayton
因此,回到我的最后一句话。你可以用同样的方法初始化变量,即a(length(v)) = false... - Dan

2

我认为@slayton的解决方案是最快的。然而,这里有一个一行代码的替代方案,如果向量很大,可能至少可以节省一些内存。

vecLen = 6;
logicalIdx = sparse(idx,1,true,vecLen,1);

1
你可以编写像这样的函数:

function logicalIdx = getLogicalIdx(idx, v)
    logicalIdx = zeros(1,size(v,2));
    logicalIdx(idx) = 1;
end

当你调用函数时:
v = 1:6;
idx = [3 5];
getLogicalIdx(idx,v)

输出将是:
ans =

     0     0     1     0     1     0

1

你能简单地执行v(i) = 1吗?

例如,如果你说x = zeros(1,10); 并且a = [1 3 4];

x(a) = 1将返回1 0 1 1 0 0 0 0 0 0


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接