【问题标题】:Summing array by indices from another cell array按来自另一个元胞数组的索引对数组求和
【发布时间】:2013-05-21 09:05:22
【问题描述】:

我有一个数组:

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

【问题讨论】:

  • 我修复了l 赋值中的大括号(注意:l 是目前最糟糕的变量名)。
  • @JohnSmith 为什么要避免循环? arrayfun/cellfun 通常比循环慢。
  • 哦,真的吗?有人告诉我arrayfun 会更有效率,但我不确定。
  • @JohnSmith - 请参阅 this question 讨论循环和 *-fun 以迭代数组/结构和单元格。
  • 仅供参考,如果您想要速度,到目前为止所有答案都比您提供的简单 for 循环慢得多(它比 @EitanT 提供的“棘手”解决方案快四倍以上)。不要be paranoid about for loops,尤其是没有分支的小型简单的(if 语句)。十年前可能,但今天很多时候for 循环将是既方便又最快的解决方案。在代码可读性方面for 循环很简单。

标签: matlab vectorization matrix-indexing


【解决方案1】:

使用cellfun

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

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

【讨论】:

  • 我觉得 l 作为变量名更糟糕(至少对于我们这些很少进入想象世界的人来说)。
  • @Shai,l 是什么意思?
  • @JohnSmith:请参阅:变量名的错误选择(除了容易与 1I 混淆,具体取决于您的字体)
【解决方案2】:

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

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. 接下来的 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);

最后,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 循环仍然胜过两者。

【讨论】:

  • @Shai he he :) 我已按要求添加了解释。
猜你喜欢
  • 2011-07-17
  • 2017-04-14
  • 1970-01-01
  • 1970-01-01
  • 2016-06-18
  • 1970-01-01
  • 2014-11-17
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多