問題文
値の配列vals
とランレングスがありrunlens
ます。
vals = [1,3,2,5]
runlens = [2,2,1,3]
vals
の対応する各要素を の回数だけ繰り返す必要がありますrunlens
。したがって、最終的な出力は次のようになります。
output = [1,1,3,3,2,5,5,5]
前向きアプローチ
MATLAB で最も高速なツールの 1 つは、cumsum
不規則なパターンで機能するベクトル化の問題を処理する場合に非常に役立ちます。上記の問題では、不規則性は のさまざまな要素に伴いrunlens
ます。
を悪用するcumsum
には、ここで 2 つのことを行う必要があります: の配列を初期化し、zeros
「適切な」値を zeros 配列の「キー」位置に配置して、「cumsum
」が適用された後に最終的に何度も繰り返しvals
ますrunlens
。
手順:上記の手順に番号を付けて、将来のアプローチをより簡単に理解できるようにします。
1) ゼロ配列の初期化: 長さは? 時間を繰り返しているためrunlens
、ゼロ配列の長さはすべての合計でなければなりませんrunlens
。
2) キーの位置/インデックスを見つける: これらのキーの位置は、各要素が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
よく考えてみると、withのdifferentiated
バージョンが必要になるでしょう。これらの微分された値は、距離で区切られた場所でゼロ配列に配置されるため、使用後、最終出力として各要素が何度も繰り返されます。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
ベースのアプローチでは、アプローチの要素数のマッピングと比較して、MATLAB が要素 (n は runLengths の数)diff(vals)
のみを処理する代わりに、この数は何倍も多くなければならないため、この新しいアプローチによる顕著なスピードアップ!n
sum(runLengths)
cumsum-only
n