通过来自另一个单元阵列的索引对数组求和

时间:2013-05-26 08:15:44

标签: matlab vectorization matrix-indexing

我有一个数组:

a = [109, 894, 566, 453, 342, 25]

和另一个a子索引的单元格数组,表示为:

subs = { [1,3,4], [2,5,6], [1,3], [3,4], [2,3,4], [6] };    

我想避免for循环通过MATLAB计算以下求和:

for i=1:6
    sums_a(i) = sum(a(subs{i}));
end

arrayfun这样的快速方式来实现这个吗?感谢。

2 个答案:

答案 0 :(得分:8)

使用cellfun

sums_a = cellfun( @(sx) sum( a(sx) ), subs );

PS,
最好not to use i and j as variable names in Matlab

答案 1 :(得分:2)

如果您正在寻找速度,arrayfun可能会相当慢。正如Andrew Horchler所评论的那样,由于JIT acceleration,最新版本的MATLAB for循环可以非常快。如果你仍然坚持避免循环,这里是一个棘手的解决方案,没有使用accumarray的循环:

idx = cumsum(cellfun('length', subs));
x = diff(bsxfun(@ge, [0; idx(:)], 1:max(idx)));
x = sum(bsxfun(@times, x', 1:numel(subs)), 2);  %'// Produce subscripts
y = a([subs{:}]);                               % // Obtain values
sums_a = accumarray(x, y);                      % // Accumulate values

这实际上可以写成(相当长的)单行,但为了清晰起见,它被分成几行。

解释

要累积的值如下所示:

y = a([subs{:}]);

在您的示例中,相应的索引应为:

1    1    1    2    2    2    3    3    4    4    5    5    5    6

那是:

  1. 累计y的前3个值,结果存储为输出中的 first 元素。
  2. 累计接下来的3个值,结果将作为 second 元素存储在输出中。
  3. 累计接下来的两个值,结果将作为第三个​​元素存储在输出中。
  4. 依旧......

    以下几行可以产生这样的索引x矢量:

    idx = cumsum(cellfun('length', subs));
    x = diff(bsxfun(@ge, [0; idx(:)], 1:max(idx)));
    x = sum(bsxfun(@times, x', 1:numel(subs)), 2);
    

    最后,xy被输入accumarray

    sums_a = accumarray(x, y);
    

    瞧。

    基准

    以下是基准测试代码:

    a = [109,894,566,453,342,25];
    subs = {[1,3,4], [2,5,6], [1,3], [3,4], [2,3,4], 6};
    
    % // Solution with arrayfun
    tic
    for k = 1:1000
        clear sums_a1
        sums_a1 = cellfun( @(subs) sum( a(subs) ), subs );
    end
    toc
    
    % // Solution with accumarray
    tic
    for k = 1:1000
        clear sums_a2
        idx = cumsum(cellfun('length', subs));
        x = diff(bsxfun(@ge, [0; idx(:)], 1:max(idx)));
        x = sum(bsxfun(@times, x', 1:numel(subs)), 2);
        sums_a2 = accumarray(x, a([subs{:}]));
    end
    toc
    
    %'// Solution with for loop
    tic
    for k = 1:1000
        clear sums_a3
        for n = 1:6
            sums_a3(n) = sum(a(subs{n}));
        end
    end
    toc
    

    我机器上的结果是:

    Elapsed time is 0.027746 seconds.
    Elapsed time is 0.004228 seconds.
    Elapsed time is 0.001959 seconds.
    

    accumarrayarrayfun相比,加速几乎快了十倍,但请注意for循环仍然胜过两者。