matlab – 通过另一个单元格数组的索引对数组求和

我有一个数组:

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这样的快速方法来实现它?谢谢.

最佳答案
如果你正在寻找速度,arrayfun可能会相当慢.正如Andrew Horchler所评论的那样,在最新版本的MATLAB for循环可以非常快,这要归功于JIT acceleration.如果你仍然坚持避免循环,这里是一个棘手的解决方案,没有使用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

那是:

>累计y中的前3个值,结果存储为输出中的第一个元素.
>累计接下来的3个值,结果存储为输出中的第二个元素.
>累计接下来的两个值,结果存储为输出中的第三个元素.

等等…

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

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

最后,x和y被输入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.

accumarray与arrayfun的加速几乎是十倍,但请注意for循环仍然比两者都快.

转载注明原文:matlab – 通过另一个单元格数组的索引对数组求和 - 代码日志