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

Joh*_*ith 5 matlab vectorization matrix-indexing

我有一个数组:

a = [109, 894, 566, 453, 342, 25]
Run Code Online (Sandbox Code Playgroud)

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

subs = { [1,3,4], [2,5,6], [1,3], [3,4], [2,3,4], [6] };    
Run Code Online (Sandbox Code Playgroud)

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

for i=1:6
    sums_a(i) = sum(a(subs{i}));
end
Run Code Online (Sandbox Code Playgroud)

有没有快速的方法arrayfun来实现这个?谢谢.

Sha*_*hai 8

使用 cellfun

sums_a = cellfun( @(sx) sum( a(sx) ), subs );
Run Code Online (Sandbox Code Playgroud)

PS,
最好不要在Matlab中使用ij作为变量名.

  • 我觉得`l`作为一个变量名称更糟糕(至少对于我们这些很少徘徊在想象中的人来说). (6认同)

Eit*_*n T 2

如果你追求速度,arrayfun可能会相当慢。正如Andrew Horchler所评论的,在最新版本的 MATLAB 中,由于JIT 加速, for 循环可以变得相当快。如果您仍然坚持避免循环,这里有一个不使用 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
Run Code Online (Sandbox Code Playgroud)

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

解释

要累积的值是这样获得的:

y = a([subs{:}]);
Run Code Online (Sandbox Code Playgroud)

在您的示例中,它们相应的索引应该是:

1    1    1    2    2    2    3    3    4    4    5    5    5    6
Run Code Online (Sandbox Code Playgroud)

那是:

  1. 前 3 个值将y被累加,结果将作为第一个元素存储在输出中。
  2. 接下来的 3 个值被累加,结果存储为第二个值元素存储在输出中。
  3. 接下来的 2 个值将被累加,结果将作为第三个元素存储在输出中。

等等...

以下几行神奇地产生了这样一个索引向量x

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

最后,xy被输入accumarray

sums_a = accumarray(x, y);
Run Code Online (Sandbox Code Playgroud)

瞧。

基准

这是基准测试代码:

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
Run Code Online (Sandbox Code Playgroud)

我机器上的结果是:

Elapsed time is 0.027746 seconds.
Elapsed time is 0.004228 seconds.
Elapsed time is 0.001959 seconds.
Run Code Online (Sandbox Code Playgroud)

accumarrayfor与 相比,速度几乎提高了十倍 arrayfun,但请注意,for 循环仍然胜过两者。