【问题标题】:Repeat elements of vector [duplicate]重复向量的元素[重复]
【发布时间】:2014-11-21 17:41:25
【问题描述】:

我有一个值向量A 包含元素i,例如:

A = [0.1 0.2 0.3 0.4 0.5]; 然后说r = [5 2 3 2 1];

现在我想创建一个新向量Anew,其中包含Ai 值的重复值r(i),这样Anew 中的第一个r(1)=5 项具有值A(1) 和长度新向量是sum(r)。因此:

Anew = [0.1 0.1 0.1 0.1 0.1 0.2 0.2 0.3 0.3 0.3 0.4 0.4 0.5]

我确信这可以通过精心设计的 for-loop 组合来完成,例如repmat,但是有人知道如何以更流畅的方式做到这一点吗?

【问题讨论】:

    标签: matlab vector repeat run-length-encoding


    【解决方案1】:

    据我所知,虽然Rrep 可以为您做到这一点,但在 MATLAB 中没有等效的功能......所以嫉妒。

    无论如何,我建议的唯一方法是按照您的建议使用repmat 运行for 循环。但是,如果您想将其作为单行来执行,则也许可以使用arrayfun 来代替……从技术上讲,进行将其放入单个向量所需的后处理是两个。因此,你可以试试这个:

    Anew = arrayfun(@(x) repmat(A(x), r(x), 1), 1:numel(A), 'uni', 0);
    Anew = vertcat(Anew{:});
    

    这实质上是用更少的代码完成for 循环和复制向量的连接。我们遍历Ar 中的每一对值并吐出复制的向量。它们中的每一个都将位于一个单元格数组中,这就是为什么需要vertcat 将它们全部放入一个向量中的原因。

    我们得到:

    Anew =
    
        0.1000
        0.1000
        0.1000
        0.1000
        0.1000
        0.2000
        0.2000
        0.3000
        0.3000
        0.3000
        0.4000
        0.4000
        0.5000
    

    请注意,其他人已经尝试过与您在这篇文章中所做的类似的事情:A similar function to R's rep in Matlab。这本质上是在模仿Rrep 的方式,这就是你想要做的!


    替代方案 - 使用 for 循环

    由于@Divakar 的基准测试,我很想知道如何预先分配数组,然后使用实际的for 循环遍历Ar 并通过索引填充它会进行基准测试。因此,上面使用for 循环和索引的等效代码将是:

    Anew = zeros(sum(r), 1);
    counter = 1;
    for idx = 1 : numel(r)
        Anew(counter : counter + r(idx) - 1) = A(idx);
        counter = counter + r(idx);
    end
    

    我们需要一个变量来跟踪我们需要在数组中插入元素的位置,该变量存储在counter 中。我们用每个数字要复制的元素总数来抵消它,它存储在r 的每个值中。

    因此,这种方法完全避免使用repmat,而是使用索引来生成我们的复制向量。


    基准测试(à la Divakar)

    基于 Divakar 的基准测试代码,我实际上尝试在我的机器上运行所有测试,除了 for 循环方法。我只是将他的基准代码与相同的测试用例一起使用。

    这些是我根据算法得到的计时结果:

    案例 #1 - N = 4000, max_repeat = 4000

    -------------------  With arrayfun
    Elapsed time is 1.202805 seconds.
    -------------------  With cumsum
    Elapsed time is 1.691591 seconds.
    -------------------  With bsxfun
    Elapsed time is 0.835201 seconds.
    -------------------  With for loop
    Elapsed time is 0.136628 seconds.
    

    案例 #2 - N = 10000, max_repeat = 1000

    -------------------  With arrayfun
    Elapsed time is 2.117631 seconds.
    -------------------  With cumsum
    Elapsed time is 1.080247 seconds.
    -------------------  With bsxfun
    Elapsed time is 0.540892 seconds.
    -------------------  With for loop
    Elapsed time is 0.127728 seconds.
    

    在这些情况下,cumsum 实际上击败了arrayfun...这是我最初的预期。 bsxfun 击败了其他所有人,除了 for 循环。我的猜测是我和 Divakar 在arrayfun 中的不同时间,我们在不同的架构上运行我们的代码。我目前正在使用 MATLAB R2013a 在 Mac OS X 10.9.5 MacBook Pro 机器上运行我的测试。

    正如我们所见,for 循环要快得多。我知道一个事实,当涉及到for 循环中的索引操作时,JIT 会发挥作用并为您提供更好的性能。

    【讨论】:

    • +1 实现又好又快
    • 你可以把它变成单行:cell2mat(arrayfun(@(n) repmat(A(n), [1 r(n)]), 1:numel(r), 'uniformoutput',0))
    • 如果我有一些繁重的工作,我会发给你的! ;) 啊,我有没有说责任!!? :D
    • @Divakar - 哈哈,这是我能做的至少 :) 你已经帮了我很多忙。我的 MacBook 上的 GPU 不如我家里的机器强大……但那台机器只有 8 GB 的 RAM。可能是时候升级了。
    • 感谢所有真正有用的建议和基准测试,我想我会选择for 循环,但我确实需要查看我的 RAM,尽管 16 GB 必须足够! :)
    【解决方案2】:

    首先想到形成一个索引向量[1 1 1 1 1 2 2 3 3 3 4 4 5]。注意到这里的规则增量让我想到了 cumsum:我们可以通过将一个放在零向量中的正确位置来获得这些步骤:[1 0 0 0 0 1 0 1 0 0 1 0 1]那个我们可以通过在输入列表上运行另一个cumsum 来获得。在调整结束条件和基于 1 的索引后,我们得到:

    B(cumsum(r) + 1) = 1;
    idx = cumsum(B) + 1;
    idx(end) = [];
    A(idx)
    

    【讨论】:

    • 这必须非常快! +1
    【解决方案3】:

    bsxfun 基于方法 -

    A = [0.1 0.2 0.3 0.4 0.5]
    r = [5 2 3 2 1]
    
    repeats = bsxfun(@le,[1:max(r)]',r) %//' logical 2D array with ones in each column 
                                        %// same as the repeats for each entry
    A1 = A(ones(1,max(r)),:) %// 2D matrix of all entries repeated maximum r times
                             %// and this resembles your repmat 
    out = A1(repeats) %// desired output with repeated entries
    

    它本质上可以变成一条双线 -

    A1 = A(ones(1,max(r)),:);
    out = A1(bsxfun(@le,[1:max(r)]',r));
    

    输出 -

    out =
        0.1000
        0.1000
        0.1000
        0.1000
        0.1000
        0.2000
        0.2000
        0.3000
        0.3000
        0.3000
        0.4000
        0.4000
        0.5000
    

    基准测试

    到目前为止,此处介绍的解决方案可能会产生一些基准测试结果。

    基准代码 - 案例 I

    %// Parameters and input data
    N = 4000;
    max_repeat = 4000;
    A = rand(1,N);
    r = randi(max_repeat,1,N);
    num_runs = 10; %// no. of times each solution is repeated for better benchmarking
    
    disp('-------------------  With arrayfun')
    tic
    for k1 = 1:num_runs
        Anew = arrayfun(@(x) repmat(A(x), r(x), 1), 1:numel(A), 'uni', 0);
        Anew = vertcat(Anew{:});
    end
    toc, clear Anew
    
    disp('-------------------  With cumsum')
    tic
    for k1 = 1:num_runs
        B(cumsum(r) + 1) = 1;
        idx = cumsum(B) + 1;
        idx(end) = [];
        out1 = A(idx);
    end
    toc,clear B idx out1
    
    disp('-------------------  With bsxfun')
    tic
    for k1 = 1:num_runs
        A1 = A(ones(1,max(r)),:);
        out2 = A1(bsxfun(@le,[1:max(r)]',r));
    end
    toc
    

    结果

    -------------------  With arrayfun
    Elapsed time is 2.198521 seconds.
    -------------------  With cumsum
    Elapsed time is 5.360725 seconds.
    -------------------  With bsxfun
    Elapsed time is 2.896414 seconds.
    

    基准代码 - 案例 II [更大的数据大小,但 r 的最大值更小]

    %// Parameters and input data
    N = 10000;
    max_repeat = 1000;
    

    结果

    -------------------  With arrayfun
    Elapsed time is 2.641980 seconds.
    -------------------  With cumsum
    Elapsed time is 3.426921 seconds.
    -------------------  With bsxfun
    Elapsed time is 1.858007 seconds.
    

    基准测试的结论

    对于case Iarrayfun 似乎是要走的路,而对于Case IIbsxfun 可能是首选武器。因此,您正在处理的数据类型似乎会真正决定采用哪种方法。

    【讨论】:

    • 像往常一样出色的基准测试工作!你已经有我的投票了
    • @LuisMendo 谢谢!我并不是真的为bsxfun 担保,但是哇!实际上,我认为带有二进制相关函数句柄的bsxfun 真的很便宜。
    • 忘记给你投票了。 +1 和出色的基准测试工作。在选择要使用的方法之前,您必须了解 r 的结构,这很酷。
    猜你喜欢
    • 2018-09-30
    • 2016-09-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-08-04
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多