重复数组元素的副本:在MATLAB中运行长度解码

时间:2009-12-29 17:20:52

标签: arrays matlab vectorization repeat run-length-encoding

我正在尝试使用'values'数组和'counter'数组将多个值插入到数组中。例如,如果:

a=[1,3,2,5]
b=[2,2,1,3]

我想要一些功能的输出

c=somefunction(a,b)

c=[1,1,3,3,2,5,5,5]

其中a(1)重复b(1)次,a(2)重复b(2)次等...

MATLAB中是否有内置函数可以执行此操作?如果可能的话,我想避免使用for循环。我尝试过'repmat()'和'kron()'的变体无济于事。

这基本上是Run-length encoding

5 个答案:

答案 0 :(得分:21)

问题陈述

我们有一系列值,vals和游程长度,runlens

vals     = [1,3,2,5]
runlens  = [2,2,1,3]

我们需要在vals中重复runlens中每个对应元素的每个元素。因此,最终的输出将是:

output = [1,1,3,3,2,5,5,5]

前瞻性方法

MATLAB最快的工具之一是cumsum,在处理不规则模式的矢量化问题时非常有用。在所述问题中,不规则性伴随着runlens中的不同元素。

现在,为了利用cumsum,我们需要在这里做两件事:初始化一个zeros数组,并将“适当的”值放在零数组上的“关键”位置,这样“ cumsum“已应用,我们最终将重复valsrunlens次。

步骤:让我们对上述步骤进行编号,使前瞻性方法更容易理解:

1)初始化零数组:长度必须是多少?由于我们重复runlens次,因此零数组的长度必须是所有runlens的总和。

2)找到关键位置/索引:现在这些关键位置是沿零点阵列的位置,其中vals的每个元素开始重复。 因此,对于runlens = [2,2,1,3],映射到零数组的关键位置将是:

[X 0 X 0 X X 0 0] % where X's are those key positions.

3)找到合适的值:在使用cumsum之前要敲打的最后一根钉子就是将“适当的”值放入这些关键位置。现在,由于我们很快会做cumsum,如果你仔细思考,你需要一个differentiated valuescumsum diffvalues在那些带回我们的runlens 。由于这些差异值将放置在由cumsum距离分隔的地方的零数组上,因此在使用vals后,我们会将每个runlens元素重复% Calculate cumsumed values of runLengths. % We would need this to initialize zeros array and find key positions later on. clens = cumsum(runlens) % Initalize zeros array array = zeros(1,(clens(end))) % Find key positions/indices key_pos = [1 clens(1:end-1)+1] % Find appropriate values app_vals = diff([0 vals]) % Map app_values at key_pos on array array(pos) = app_vals % cumsum array for final output output = cumsum(array) 次作为最终输出

解决方案代码

这是实施上述所有步骤的实现 -

array(clens(end)) = 0; % instead of array = zeros(1,(clens(end)))

预先分配黑客

可以看出,上面列出的代码使用预分配零。现在,根据这个UNDOCUMENTED MATLAB blog on faster pre-allocation,可以使用 -

实现更快的预分配
function out = rle_cumsum_diff(vals,runlens)
clens = cumsum(runlens);
idx(clens(end))=0;
idx([1 clens(1:end-1)+1]) = diff([0 vals]);
out = cumsum(idx);
return;

结束:功能代码

为了包装所有内容,我们将有一个紧凑的函数代码来实现这样的行程长度解码 -

cumsum+diff

基准

基准代码

下面列出的是基准代码,用于比较此帖中所述MATLAB 2014B方法的运行时和加速比datasizes = [reshape(linspace(10,70,4).'*10.^(0:4),1,[]) 10^6 2*10^6]; % fcns = {'rld_cumsum','rld_cumsum_diff'}; % approaches to be benchmarked for k1 = 1:numel(datasizes) n = datasizes(k1); % Create random inputs vals = randi(200,1,n); runs = [5000 randi(200,1,n-1)]; % 5000 acts as an aberration for k2 = 1:numel(fcns) % Time approaches tsec(k2,k1) = timeit(@() feval(fcns{k2}, vals,runs), 1); end end figure, % Plot runtimes loglog(datasizes,tsec(1,:),'-bo'), hold on loglog(datasizes,tsec(2,:),'-k+') set(gca,'xgrid','on'),set(gca,'ygrid','on'), xlabel('Datasize ->'), ylabel('Runtimes (s)') legend(upper(strrep(fcns,'_',' '))),title('Runtime Plot') figure, % Plot speedups semilogx(datasizes,tsec(1,:)./tsec(2,:),'-rx') set(gca,'ygrid','on'), xlabel('Datasize ->') legend('Speedup(x) with cumsum+diff over cumsum-only'),title('Speedup Plot') 上的other cumsum-only based approach -

rld_cumsum.m

function out = rld_cumsum(vals,runlens) index = zeros(1,sum(runlens)); index([1 cumsum(runlens(1:end-1))+1]) = 1; out = vals(cumsum(index)); return; 的相关功能代码:

cumsum-only

运行时和加速图

enter image description here

enter image description here

结论

建议的方法似乎给我们一个明显的加速cumsum+diff方法,大约 3x

为什么这种基于cumsum-only的新方法比之前的cumsum-only方法更好?

嗯,原因的本质在于vals方法的最后一步,需要将“cumsumed”值映射到cumsum+diff。在基于diff(vals)的新方法中,我们正在进行n而不是MATLAB仅处理sum(runLengths)元素(其中n是runLengths的数量)与{{1}的映射相比较} cumsum-only方法的元素数量,这个数字必须比n多很多倍,因此采用这种新方法可以显着加快速度!

答案 1 :(得分:20)

基准

针对R2015b进行了更新repelem现在对所有数据大小都是最快的。


经测试的功能:

  1. MATLAB在R2015a中添加的内置repelem功能
  2. gnovice' cumsum解决方案(rld_cumsum
  3. Divakar的cumsum + diff解决方案(rld_cumsum_diff
  4. 来自knedlsepp5cumsumaccumarray
  5. knedlsepp' accumarray解决方案(this post
  6. 基于Naive循环的实现(naive_jit_test.m)来测试即时编译器
  7. 2015年R2015上test_rld.m的结果 b

    repelem time

    旧时序图使用R2015 a here

    <强>首饰

    • repelem总是最快的大约2倍。
    • rld_cumsum_diff始终比rld_cumsum快。
    • repelem对于小数据量(小于约300-500个元素)来说速度最快
    • rld_cumsum_diff明显快于repelem约5 000个元素
    • repelem变得慢于rld_cumsum介于30 000到300 000个元素之间
    • rld_cumsumknedlsepp5cumsumaccumarray
    • 的效果大致相同
    • naive_jit_test.m速度几乎保持恒定,与rld_cumsumknedlsepp5cumsumaccumarray相比,尺寸更小,大尺寸更快一点

    enter image description here

    使用R2015 a here的旧费率图。

    <强>结论

    使用repelem 下面的约5 000个元素以及上面的cumsum + diff解决方案

答案 2 :(得分:16)

我知道没有内置功能,但这是一个解决方案:

index = zeros(1,sum(b));
index([1 cumsum(b(1:end-1))+1]) = 1;
c = a(cumsum(index));

说明:

首先创建与输出数组长度相同的零向量(即b中所有复制的总和)。然后将其中的一个放在第一个元素中,每个后续元素表示新值序列的开始位于输出中的位置。然后可以使用向量index的累积和来索引a,将每个值复制所需的次数。

为了清楚起见,这就是问题中给出的ab值的各种向量的样子:

        index = [1 0 1 0 1 1 0 0]
cumsum(index) = [1 1 2 2 3 4 4 4]
            c = [1 1 3 3 2 5 5 5]

编辑:为了完整起见,使用ARRAYFUN的另一种选择,但这似乎需要20到100倍的时间才能运行比上述解决方案的载体长达10,000个元素:

c = arrayfun(@(x,y) x.*ones(1,y),a,b,'UniformOutput',false);
c = [c{:}];

答案 3 :(得分:12)

最后(从 R2015a 开始)有一个内置且记录在案的函数,repelem。以下语法(其中第二个参数是向量)与此相关:

  带有向量W = repelem(V,N)和向量V

N会创建一个向量W,其中元素V(i)重复N(i)次。

或换句话说,“N的每个元素都指定重复V的相应元素的次数。”

示例:

>> a=[1,3,2,5]
a =
     1     3     2     5
>> b=[2,2,1,3]
b =
     2     2     1     3
>> repelem(a,b)
ans =
     1     1     3     3     2     5     5     5

答案 4 :(得分:3)

自R2015b起,MATLAB内置repelem的性能问题已得到修复。我在R2015b的chappjc帖子中运行了test_rld.m程序,repelem现在比其他算法快了大约2倍:

Updated plot comparing repelem execution time of different methods Speedup of repelem over cumsum+diff (about a factor 2)