【问题标题】:Reverse lookup with non-unique values使用非唯一值反向查找
【发布时间】:2016-05-22 18:47:04
【问题描述】:

我想要做什么

我有一个数字数组:

>> A = [2 2 2 2 1 3 4 4];

我想找到可以找到每个数字的数组索引:

>> B = arrayfun(@(x) {find(A==x)}, 1:4);

换句话说,这个B应该告诉我:

>> for ii=1:4, fprintf('Item %d in location %s\n',ii,num2str(B{ii})); end
Item 1 in location 5
Item 2 in location 1  2  3  4
Item 3 in location 6
Item 4 in location 7  8

这就像 unique 的第二个输出参数,但不是第一次(或最后一次)出现,我想要 所有 出现。我认为这称为反向查找(其中原始键是数组索引),但如果我错了,请纠正我。

我怎样才能更快地做到这一点?

我上面给出的答案是正确的,但它会随着唯一值的数量而急剧增加。对于一个真正的问题(A 有 10M 个元素和 100k 个唯一值),即使这个愚蠢的 for 循环也快 100 倍:

>> B = cell(max(A),1);
>> for ii=1:numel(A), B{A(ii)}(end+1)=ii; end

但我觉得这不可能是最好的方法。

我们可以假设A 只包含从 1 到最大值的整数(因为如果它不包含,我总是可以通过 unique 来实现它)。

【问题讨论】:

    标签: arrays matlab vectorization reverse-lookup


    【解决方案1】:

    accumarray 的任务很简单:

    out = accumarray(A(:),(1:numel(A)).',[],@(x) {x})  %'
    
    out{1} = 5
    out{2} = 3 4 2 1
    out{3} = 6
    out{4} = 8 7  
    

    但是,accumarray 存在稳定(就unique 的功能而言),所以如果有问题,您可能想看看here for a stable version of accumarray。


    上述解决方案还假设A 用整数填充,最好在两者之间没有间隙。如果不是这种情况,则无法提前致电unique:

    A = [2.1 2.1 2.1 2.1 1.1 3.1 4.1 4.1];
    
    [~,~,subs] = unique(A)
    out = accumarray(subs(:),(1:numel(A)).',[],@(x) {x})
    

    总而言之,最通用的解决方案是使用浮点数并返回已排序的输出:

    [~,~,subs] = unique(A)
    [subs(:,end:-1:1), I] = sortrows(subs(:,end:-1:1));  %// optional
    vals = 1:numel(A);                                   
    vals = vals(I);                                      %// optional
    out = accumarray(subs, vals , [],@(x) {x});
    
    out{1} = 5
    out{2} = 1 2 3 4
    out{3} = 6
    out{4} = 7 8  
    

    基准测试

    function [t] = bench()
        %// data
        a = rand(100);
        b = repmat(a,100);
        A = b(randperm(10000));
    
        %// functions to compare
        fcns = {
            @() thewaywewalk(A(:).');
            @() cst(A(:).');
        }; 
    
        % timeit
        t = zeros(2,1);
        for ii = 1:100;
            t = t + cellfun(@timeit, fcns);
        end
        format long
    end
    
    function out = thewaywewalk(A) 
        [~,~,subs] = unique(A);
        [subs(:,end:-1:1), I] = sortrows(subs(:,end:-1:1));
        idx = 1:numel(A);
        out = accumarray(subs, idx(I), [],@(x) {x});
    end
    function out = cst(A) 
        [B, IX] = sort(A);
        out  = mat2cell(IX, 1, diff(find(diff([-Inf,B,Inf])~=0)));
    end
    

    0.444075509687511  %// thewaywewalk
    0.221888202987325  %// CST-Link
    

    令人惊讶的是,具有稳定 accumarray 的版本比不稳定的版本更快,因为 Matlab 更喜欢使用排序数组。

    【讨论】:

    • 为什么是 sortrows() 而不仅仅是排序?
    • @Oleg 解释在链接的答案中:我们可以使用 sortrows 作为预处理步骤,首先对索引和对应值进行排序 SORTROWS 使用稳定版本的快速排序跨度>
    • 起初这也让我感到困惑,但我的理解是sortrows 和end:-1:1 列顺序用于链接答案中以正确处理多维下标。在这种特殊情况下,subs 始终是一个向量,因此 sort 和 sortrows 是等价的。
    • @thewaywewalk 很好的链接答案。这可能也是性能损失的地方。我通常使用accumarray() 到mat2cell() 一个数组,因为它更快,但我不在乎保持顺序。
    【解决方案2】:

    由于排序,该解决方案应该在 O(N*log(N)) 中工作,但是非常占用内存(需要 3 倍的输入内存量):

    [U, X] = sort(A);
        B  = mat2cell(X, 1, diff(find(diff([Inf,U,-Inf])~=0)));
    

    不过我对性能很好奇。

    【讨论】:

    • 它(令人惊讶的是,我承认)很快!
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2017-08-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-26
    • 2017-12-06
    相关资源
    最近更新 更多