【问题标题】:Looping over a vector efficiently in MATLAB在 MATLAB 中有效地循环一个向量
【发布时间】:2015-11-19 21:07:43
【问题描述】:

在 Matlab 中我们有这样的场景:

v =[1 1 1 1 1 1 1 1 2 2 2 2 2 2 2 2 2 2 2 2 3 3 3 3 3 .... N N N N];

其中 v 中的元素始终按从 1 到 N 递增的顺序排列,我们知道 N 的值。我们想计算 v 中 '1' 和 '2' ... 的数量。

当然,我们可以使用如下循环:

for i =  1 : N
    % method A
    tic
    ind = find(v == i)
     ---> do sth with ind 
    t1 = toc;

    % method B
    tic
    ind = v(v == i)
     ---> do sth with ind 
    t2 = toc;

    % method C
    tic
    ind = ismember(v , i)
     ---> do sth with ind 
    t3 = toc;


end

每种方法所需的时间大致等于 $t1=0.02 sec$、$t2=0.02 sec$ 和 $t3=0.03 sec$。在我的实际工作中,N 很大,整个循环需要 2 -3 个小时!

您是否认为可以增加执行此过程的时间?任何想法都值得赞赏。

【问题讨论】:

    标签: matlab vector vectorization


    【解决方案1】:

    具体情况:排序输入,仅计数

    如果您希望获得计数,这里可以推荐几种方法。

    方法#1:

    accumarray(v(:),1)
    

    方法#2:

    diff([0 find([diff(v) 1])])
    

    方法#3:

    histc(v,1:max(v))
    

    为了表现,我会赌diff,然后是accumarray,最后一个赌histc


    一般情况:未排序的输入、计数和索引

    对于输入向量v 未排序且您可能还需要与每组相同数字对应的索引的一般情况,这是将索引存储在元胞数组中的一种方法 -

    [~,sort_idx] = sort(v);
    sorted_v = v(sort_idx);
    counts = diff([0 find([diff(sorted_v) 1])])
    indices_per_grp = mat2cell(sort_idx,1,counts);
    

    示例运行 -

    v =
         2     1     3     3     2     4     1     2     1     1     4     3     4   3
    counts =
         4     3     4     3
    indices_per_grp{1} =
         2     7     9    10
    indices_per_grp{2} =
         1     5     8
    indices_per_grp{3} =
         3     4    12    14
    indices_per_grp{4} =
         6    11    13
    

    【讨论】:

    • @macduf 然后 OP 会 fliplr 我的建议顺序 ;)
    • @TroyHaskin 我们用 Ray 做了一些基准测试来比较 accumarrayhistcaccumarray 基本上快疯了。 diff 我知道效率很高,尤其是在处理大型数组时。
    • @YAS 元素按任何顺序排列,不一定是有序的这一事实不会改变您在计算出现次数时调用accumarray 的方式。无论如何它都会起作用。但是,如果您想要值出现位置的索引,则可以使用以下方法:out = accumarray(v(:), (1:numel(v)).', [], @(x) {sort(x)}); 这将返回一个单元格数组,其中每个单元格将为您提供每个值出现的位置。 sort 是必需的,因为被分箱到每个唯一 ID 的索引是随机顺序的,因此排序可以保证以递增格式正确排序。
    • @YAS 为此添加了新方法。基本上我们需要将索引存储在一个元胞数组中。对此,这篇文章提出了一种基于mat2cell 的方法。此外,Ray 提出了一种基于accumarray 的方法,它也必须有效。所有这些方法都留给你看,祝你好运!
    • @rayryeng 导致比较 histc 和 accumarray 是一些图像处理问题,对吧?我不记得那是什么问题。我想再次测试并一劳永逸地密封它会很好!我可能会在这篇文章中添加这些......很快!
    【解决方案2】:

    我在这里偏爱bsxfun

    counts = sum(bsxfun(@eq,v(:),1:max(v)));
    

    【讨论】:

    • 我对使用 bsxfcn 很感兴趣。但是,您的建议似乎已经可以使用了:Error using bsxfun Non-singleton dimension of the two input array must match each other.
    • @YAS 使用sum(bsxfun(@eq,v(:),1:max(v)))。您的 v 似乎是一个列向量。
    • 谢谢。我收到此内存错误:请求的 21622187x234934 (4730.9GB) 数组超出了最大数组大小首选项。创建大于此限制的数组可能需要很长时间并导致 MATLAB 变得无响应。有关更多信息,请参阅数组大小限制或首选项面板。
    • @YAS (1) 发布的代码假定每个问题中的示例都有一个行向量;但是,您只需将其列在列即可获得正确的解决方案。 (2)N这里有多大?
    • 我很抱歉。使用您的原始代码和行向量,我收到相同的内存错误。 N 约为 250000 ;
    【解决方案3】:

    由于它们已排序,因此您可以提高效率!

    怎么样

    lastfound = 1;
    for i =  1 : N
       % find first location after current pos, where value is i
       indStart = find(v(lastfound:end) == i, 1)
       % find first location, after this, where value is not i
       indEnd   = find(v(indStart:end) ~= i, 1)
       % now you have the range indStart:indEnd-1
       ...
    
       lastfound = indEnd; % start next one after the end of the current value
     end
    

    即只需从最后找到的项目向前搜索。

    find(..., 1) 只找到第一项,我相信。

    【讨论】:

    • 这几乎完成了 OP 已经尝试过的find。事实上,我很确定 find 会进行线性搜索,因此即使数组已排序,find 也会为您提供相同的搜索复杂性,无论数组处于什么状态。
    • 好吧,关键是它只在整个数组中搜索一次,而不是对i 的每个值搜索一次。换句话说,它应该加速N。由于 find 只搜索到下一个值发生变化的点,它应该快得多,对吧?
    猜你喜欢
    • 2014-09-29
    • 1970-01-01
    • 1970-01-01
    • 2012-10-02
    • 2015-07-06
    • 2013-02-18
    • 1970-01-01
    • 2023-03-22
    相关资源
    最近更新 更多