重复数组元素的副本:在MATLAB中运行长度解码
我试图插入多个值到一个数组使用'值'数组和'计数器'数组。 例如,如果:
a=[1,3,2,5] b=[2,2,1,3]
我想要一些函数的输出
c=somefunction(a,b)
成为
c=[1,1,3,3,2,5,5,5]
(1)重复b(1)次,a(2)重复b(2)次,等等
MATLAB中有内置函数吗? 如果可能,我想避免使用for循环。 我已经试过了'repmat()'和'kron()'的变种无济于事。
这基本上是Run-length encoding
。
问题陈述
我们有一系列值, vals
和runlens
, runlens
:
vals = [1,3,2,5] runlens = [2,2,1,3]
我们需要在runlens
每个对应元素的vals
次重复每个元素。 因此,最终的输出是:
output = [1,1,3,3,2,5,5,5]
前瞻性的方法
MATLAB中速度最快的工具之一是cumsum
,在处理不规则模式的矢量化问题时非常有用。 在陈述的问题中, runlens
的不同元素会带来不规则性。
现在,为了利用cumsum
,我们需要在这里做两件事:初始化一个zeros
数组,并将“合适的”值放置在零数组上的“关键”位置,使得在应用“ cumsum
”之后, runlens
时间的重复vals
的最后阵列。
步骤:让我们对上述步骤进行编号,使预期的方法更容易:
1)初始化零数组:长度是多少? 由于我们重复runlens
时间,零数组的长度必须是所有runlens
的总和。
2)查找关键位置/索引:现在这些关键位置是沿着零点阵列的位置,其中来自vals
每个元素开始重复。 因此,对于runlens = [2,2,1,3]
,映射到零数组上的关键位置将是:
[X 0 X 0 XX 0 0], where X's are those key positions.
3)找到合适的价值:在使用cumsum
之前最后打的钉子是将“合适”的数值放到这些关键位置。 现在,既然我们会在不久之后做cumsum
,如果你仔细考虑,你需要differentiated
的diff
values
版本,这样cumsum
就会带回我们的values
。 由于这些差异值将被放置在由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)
预先分配Hack
可以看出,上面列出的代码使用了预分配零。 现在,根据这个UNDOCUMENTED MATLAB blog on faster pre-allocation
,可以实现更快的预分配,
`array(clens(end)) = 0` instead of `array = zeros(1,(clens(end)))`
包装:功能代码
总结一切,我们将有一个紧凑的功能代码来实现这种运行长度解码,
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
上的其他cumsum-only
方法 –
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')
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
方法,这是约3x
!
为什么这个新的基于cumsum+diff
的方法比以前的cumsum-only
方法更好?
那么,理由的精髓在于需要将“累积”值映射到vals
的cumsum-only
方法的最后一步。 在新的基于cumsum+diff
的方法中,我们正在执行diff(vals)
而对于其中的MATLAB只处理n
元素(其中n是runLengths的数量)与sum(runLengths)
元素的映射相比, cumsum-only
这个方法,这个数字必须比n
多很多倍,因此这个新方法的显着加速!
基准
针对R2015b进行了更新 :对于所有数据大小而言, repelem
速度最快。
测试功能:
- 在
repelem
中增加了MATLAB的内置repelem
功能 - gnovice的
cumsum
解决方案(rld_cumsum
) - Divakar的
cumsum
+diff
解决方案(rld_cumsum_diff
) - knedlsepp的
accumarray
解决方案(knedlsepp5cumsumaccumarray
)从这个职位 - 天真的基于循环的实现(
naive_jit_test.m
)来测试即时编译器
test_rld.m
上test_rld.m
结果b :
旧的时间情节使用R2015 在 这里 。
调查结果 :
-
repelem
总是最快的大约是2倍。 -
rld_cumsum_diff
始终比rld_cumsum
快。 -
对于小数据量(小于大约300-500个元素),repelem
是最快的, -
rld_cumsum_diff
变得比repelem
5000个元素快得多 -
在三万到三十万个元素之间,repelem
变得比rld_cumsum
慢 -
rld_cumsum
与rld_cumsum
具有大致相同的性能 -
naive_jit_test.m
具有几乎恒定的速度,与rld_cumsum
和knedlsepp5cumsumaccumarray
对于较小的尺寸,对于较大的尺寸稍快
老利率情节使用R2015 在 这里 。
结论
在下面大约5000个元素和使用cumsum
+ diff
解上面repelem
。
没有我知道的内置函数,但是这里有一个解决方案:
index = zeros(1,sum(b)); index([1 cumsum(b(1:end-1))+1]) = 1; c = a(cumsum(index));
说明:
首先创建与输出数组长度相同的矢量(即b
中所有复制的总和)。 然后将一个元素放置在第一个元素中,每个后续元素代表输出中新值序列的开始位置。 然后可以使用矢量index
的累加和来索引a
,将每个值复制为期望的次数。
为了清楚起见,这是问题中给出的a
和b
的值的各种向量的样子:
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{:}];
最后有(作为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
MATLAB的内置repelem
的性能问题在repelem
已经被固定。 我已经从chappjc的R2015b文章中运行了test_rld.m
程序,现在repelem
比其他算法快了大约2倍: