问题陈述
我们有一个值数组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
并将“适当的”值放置在 zeros 数组上的“关键”位置,这样在应用“ cumsum
”之后,我们将得到一个最终数组多次重复vals
。runlens
步骤:让我们对上述步骤进行编号,以使前瞻性方法更容易理解:
1) 初始化 zeros 数组:长度必须是多少?由于我们重复runlens
次数,所以 zeros 数组的长度必须是所有 的总和runlens
。
2)查找关键位置/索引:现在这些关键位置是沿着 zeros 数组的位置,每个元素从vals
开始到重复。因此,对于runlens = [2,2,1,3]
,映射到 zeros 数组的关键位置将是:
[X 0 X 0 X X 0 0] % where X's are those key positions.
3)找到合适的值:在使用之前要敲打的最后一颗钉子是cumsum
将“合适”的值放入那些关键位置。现在,由于我们很快就会这样做cumsum
,如果您仔细考虑,您将需要一个withdifferentiated
版本,以便在那些上带回我们的. 由于这些微分值将放置在由距离分隔的位置的 zeros 数组中,因此在使用后,我们将每个元素重复多次作为最终输出。values
diff
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)
预分配黑客
可以看出,上面列出的代码使用零预分配。现在,根据这个关于更快预分配的 UNDOCUMENTED MATLAB 博客,可以通过以下方式实现更快的预分配 -
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
方法与其他cumsum-only
基于方法的运行时和加速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')
相关功能代码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
,大约是3 倍!
为什么这种cumsum+diff
基于新方法的方法比以前的方法更好cumsum-only
?
好吧,原因的本质在于cumsum-only
方法的最后一步,需要将“累积”值映射到vals
. 在基于新cumsum+diff
的方法中,我们正在做diff(vals)
的是 MATLAB 仅处理n
元素(其中 n 是 runLength 的数量),而不是sum(runLengths)
该方法的元素数量的映射,cumsum-only
并且该数量必须比该方法多很多倍n
,因此这种新方法显着加速!