【问题标题】:Improve speed of appending array elements if not already in array如果尚未在数组中,则提高附加数组元素的速度
【发布时间】:2019-12-07 15:58:32
【问题描述】:

我有一个数组S,它有一些独特的元素。我想从数组N 中追加尚未在S 中的元素。

执行此操作的语法简单方法是:

S = union( S, N, 'stable' );

我发现手动追加可能会更快,使用ismember 或隐式扩展:

% ismember approach
S = [S; N(~ismember(N,S))];
% imp. expansion approach
S = [S; N(~any(S(:)==N(:).',1))];

但是,在循环中执行此操作仍然感觉很脏,并且对于大型输入,隐式扩展可能会很昂贵。

有没有更高效的选择?

如果有帮助,我们可以假设SN 只包含整数。但是,我们不能假设S 已排序,从N 追加的新元素可以是任何正整数。

小例子:

Ntest = [1 2 3 4
         2 5 3 6
         1 5 7 9];
S = [];
for ii = 1:3
    N = Ntest(ii,:).';
    S = union(S,N,'stable');
end
% S = [ 1; 2; 3; 4; 5; 6; 7; 9 ]

在实际情况下,我不知道 N 的潜在价值,就像我对上面的 Ntest 所做的那样。

这是 4 种方法的一些基准测试代码,结果如下。在我的情况下,我可能会为N 的不同值创建一个大循环,并且每个N 中的少量元素。这对应于该汇总表中最右侧的列,您可以在其中看到隐式扩展方法要快得多。

range(Ntest): 1 to 1e4     1 to 1e4     1 to 1e4    1 to 1e4
size(Ntest):  [1e3,1e3]    [1e4,1e3]    [1e2,1e3]   [1e2,1e4]
union:        0.972 sec    1.217 sec    0.779 sec   9.341 sec
ismember:     0.763 sec    0.559 sec    0.492 sec   5.439 sec
implicit:     6.966 sec    too long!    0.295 sec   3.886 sec
setdiff:      0.599 sec    0.534 sec    0.477 sec   5.364 sec
rng(0);
Ntest = randi([1,1e4],1e3,1e3);

f = @()f_union( Ntest );
fprintf( 'union: \t%.3f sec\n', timeit( f ) );
f = @()f_ismember( Ntest );
fprintf( 'ismember: \t%.3f sec\n', timeit( f ) );
f = @()f_implicit( Ntest );
fprintf( 'implicit: \t%.3f sec\n', timeit( f ) );
f = @()f_setdiff( Ntest );
fprintf( 'setdiff: \t%.3f sec\n', timeit( f ) );

function f_union( Ntest )
    S = [];
    for ii = 1:size(Ntest,2)
        N = Ntest(:,ii);
        S = union(S,N,'stable');
    end
end
function f_ismember( Ntest )
    S = [];
    for ii = 1:size(Ntest,2)
        N = Ntest(:,ii);
        S = [S; N(~ismember(N,S))];
    end    
end
function f_implicit( Ntest )
    S = [];
    for ii = 1:size(Ntest,2)
        N = Ntest(:,ii);
        S = [S; N(~any(S(:)==N(:).',1))];
    end    
end
function f_setdiff( Ntest )    
    S = [];
    for ii = 1:size(Ntest,2)
        N = Ntest(:,ii);
        S = [S;setdiff(N,S)];
    end
end

【问题讨论】:

    标签: matlab performance vectorization union


    【解决方案1】:

    由于假设数据类型为正整数,因此可以使用逻辑矩阵来存储整数的位置:

    function f_logical( Ntest )
        S = false;
        for ii = 1:size(Ntest,2)
            N = Ntest(:,ii);
            S(N) = true;
        end    
    end
    

    如果元素的范围很大并且数据具有稀疏性,则使用稀疏矩阵可能是有益的:

    function f_sparse( Ntest )
        S = sparse(false);
        for ii = 1:size(Ntest,2)
            N = Ntest(:,ii);
            S(N) = true;
        end    
    end
    

    与 Octave 中的ismember 解决方案比较:

    Elapsed time for <ismember> is 1.54181 seconds.
    Elapsed time for <sparse>   is 0.266474 seconds.
    Elapsed time for <logical>  is 0.0189412 seconds.
    

    【讨论】:

    • 这是一个很棒的视角转变,感谢您的想法!我一回到办公室就试试看
    • 这显然为我更广泛的算法测试用例节省了少量时间。我仍然惊讶地发现它为我的真实数据的大型运行节省了 ~90% 的处理时间!我也可以处理非稀疏数组,但感谢您的额外考虑。
    • 很高兴听到这个消息。感谢赏金!
    【解决方案2】:

    我猜你可以使用下面的代码来加速

    X = setdiff(N,S);
    S(end + (1:length(X))) = X;
    
    • 备注 X = N(~ismember(N,S))X = setdiff(N,S) 都可以找到N 而不是S 的元素,但是加快追加过程的关键步骤是以下方式
    S(end + (1:length(X))) = X;
    
    • 性能比较
    rng(0);
    Ntest = randi([1,1e4],1e4,1e4);
    
    f = @()f_union( Ntest );
    fprintf( 'union: \t%.3f sec\n', timeit( f ) );
    f = @()f_ismember_v1( Ntest );
    fprintf( 'ismember_v1: \t%.3f sec\n', timeit( f ) );
    f = @()f_ismember_v2( Ntest );
    fprintf( 'ismember_v2: \t%.3f sec\n', timeit( f ) );
    f = @()f_setdiff_v1( Ntest );
    fprintf( 'setdiff_v1: \t%.3f sec\n', timeit( f ) );
    f = @()f_setdiff_v2( Ntest );
    fprintf( 'setdiff_v2: \t%.3f sec\n', timeit( f ) );
    
    function f_union( Ntest )
        S = [];
        for ii = 1:size(Ntest,2)
            N = Ntest(:,ii);
            S = union(S,N,'stable');
        end
    end
    
    function f_ismember_v1( Ntest )
        S = [];
        for ii = 1:size(Ntest,2)
            N = Ntest(:,ii);
            S = [S; N(~ismember(N,S))];
        end    
    end
    
    function f_ismember_v2( Ntest )
        S = [];
        for ii = 1:size(Ntest,2)
            N = Ntest(:,ii);
            X = N(~ismember(N,S));
            S(end + (1:length(X))) = X;
        end    
    end
    
    function f_setdiff_v1( Ntest )    
        S = [];
        for ii = 1:size(Ntest,2)
            N = Ntest(:,ii);
            S = [S;setdiff(N,S)];
        end
    end
    
    function f_setdiff_v2( Ntest )    
        S = [];
        for ii = 1:size(Ntest,2)
            N = Ntest(:,ii);
            X = setdiff(N,S);
            S(end + (1:length(X))) = X;
        end
    end
    

    给予

    union:  13.314 sec
    ismember_v1:    5.836 sec
    ismember_v2:    5.658 sec
    setdiff_v1:     4.371 sec
    setdiff_v2:     4.248 sec
    

    【讨论】:

    • 是的,抱歉,我的问题中省略了,这对我来说比确定设置差异的 ismember 方法慢,也许令人惊讶。
    • @Wolfie 是的,你是对的,我猜ismember 是迄今为止我在答案中添加的三种方法中最快的,但与setdiff 方法相比只有很小的优势
    • @Wolfie 我更新了我的解决方案,现在比ismember 方法快得多
    • 如果你执行X= N(~ismember(N,S)) 然后像使用setdiff 方法一样追加?
    • 谢谢,我在我的问题中添加了一个基准,这可能会帮助您针对我的其他选项进行测试
    猜你喜欢
    • 2018-07-17
    • 1970-01-01
    • 2018-01-03
    • 1970-01-01
    • 1970-01-01
    • 2021-07-24
    • 1970-01-01
    • 2021-08-20
    • 2011-11-26
    相关资源
    最近更新 更多