Repeat copies of array elements: Run-length decoding in MATLAB
Próbuję wstawić wiele wartości do tablicy używając tablicy 'values' i tablicy 'counter'. Na przykład, jeśli:
a=[1,3,2,5]
b=[2,2,1,3]
Chcę wyjście jakiejś funkcji
c=somefunction(a,b)
Być
c=[1,1,3,3,2,5,5,5]
Gdzie a(1) powtarza B (1) wiele razy, a(2) powtarza B(2) razy itd...
Czy jest wbudowana funkcja w MATLAB, która to robi? Chciałbym unikać używania pętli for, jeśli to możliwe. Wypróbowałem wersje 'repmat ()' i 'kron ()' bez skutku.
To w zasadzie Run-length encoding
.
5 answers
Stwierdzenie Problemu
Mamy tablicę wartości, vals
i runlengths, runlens
:
vals = [1,3,2,5]
runlens = [2,2,1,3]
Musimy powtarzać każdy element w vals
razy każdy odpowiadający mu element w runlens
. Tak więc wynik końcowy będzie:
output = [1,1,3,3,2,5,5,5]
Perspektywiczne Podejście
Jednym z najszybszych narzędzi z MATLAB jest cumsum
i jest bardzo przydatny przy problemach wektoryzacji, które działają na nieregularnych wzorcach. W przedstawionym problemie, nieprawidłowość pochodzi z różne elementy w runlens
.
Teraz, aby wykorzystać cumsum
, musimy zrobić dwie rzeczy: zainicjalizować tablicę zeros
i umieścić "odpowiednie" wartości na" kluczowych " pozycjach nad tablicą zer, tak, że po zastosowaniu "cumsum
", skończymy z ostateczną tablicą powtarzanych vals
z runlens
razy.
Liczba kroków: Policzmy wyżej wymienione kroki, aby dać perspektywę perspektywicznego podejścia łatwiejszą:
1) Inicjalizacja tablicy zer: jaka musi być Długość? Ponieważ powtarzamy runlens
razy, długość tablicy zer musi być sumowaniem wszystkich runlens
.
2) Znajdź kluczowe pozycje / indeksy: teraz te kluczowe pozycje są miejscami wzdłuż tablicy zer, gdzie każdy element z vals
zaczyna się powtarzać.
Tak więc dla runlens = [2,2,1,3]
kluczowe pozycje odwzorowane na tablicy zer będą następujące:
[X 0 X 0 X X 0 0] % where X's are those key positions.
3) Znajdź odpowiednie wartości: ostatnim gwoździem, który należy wbić przed użyciem cumsum
, byłoby umieszczenie "odpowiednich" wartości w tych kluczowych pozycjach. Teraz, odkąd będzie robić cumsum
wkrótce potem, jeśli dobrze się zastanowisz, będziesz potrzebował differentiated
wersji values
Z diff
, aby cumsum
na nich przywrócić nasze values
. Ponieważ te zróżnicowane wartości byłyby umieszczone na tablicy zer w miejscach oddzielonych odległościami runlens
, po użyciu cumsum
każdy element vals
powtórzyłby się runlens
razy jako końcowy wynik.
Kod Rozwiązania
Oto implementacja wszystkich powyższych wspomniane kroki -
% 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)
Pre-allocation Hack
Jak widać, powyższy kod używa prealokacji z zerami. Teraz, zgodnie z tym nieudokumentowanym blogiem MATLAB na temat szybszej wstępnej alokacji , można osiągnąć znacznie szybszą wstępną alokację za pomocą-{52]}
array(clens(end)) = 0; % instead of array = zeros(1,(clens(end)))
Wrapping up: Function Code
Aby wszystko zakończyć, mielibyśmy zwarty kod funkcyjny, aby osiągnąć dekodowanie o długości przebiegu w ten sposób -
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;
Benchmarking
Kod Porównawczy
Poniżej znajduje się kod porównujący czasy uruchamiania i prędkości dla podanego podejścia cumsum+diff
w tym poście nad innym podejściem cumsum-only
opartym na na 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')
Skojarzony kod funkcji dla 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;
Działek Runtime i Speedup
Wnioski
The proponowane podejście wydaje się dawać nam zauważalne przyspieszenie nad podejściem cumsum-only
, które wynosi około 3x !
Dlaczego nowe podejście oparte na cumsum+diff
jest lepsze niż poprzednie podejście oparte na cumsum-only
?
Cóż, istota rozumu leży na ostatnim etapie podejścia cumsum-only
, które wymaga mapowania wartości "cumsumed" na vals
. W nowym podejściu cumsum+diff
, robimy diff(vals)
zamiast tego, dla którego MATLAB przetwarza tylko n
elementy (gdzie n jest liczba runLengths) w porównaniu do odwzorowania sum(runLengths)
Liczba elementów dla podejścia cumsum-only
i liczba ta musi być wielokrotnie większa niż {[45] } i dlatego zauważalne przyspieszenie z tym nowym podejściem!
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-08-28 16:35:33
Benchmarki
Aktualizacja dla R2015b: repelem
teraz najszybszy dla wszystkich rozmiarów danych.
Testowane funkcje:
- wbudowany MATLAB
repelem
funkcja dodana w R2015a - rozwiązanie gnovica
cumsum
(rld_cumsum
) - Divakar ' S
cumsum
+diff
rozwiązanie (rld_cumsum_diff
) - knedlsepp ' s
accumarray
rozwiązanie (knedlsepp5cumsumaccumarray
) from this post - naiwna implementacja oparta na pętli (
naive_jit_test.m
) aby przetestować kompilator just-In-time
Wyniki test_rld.m
na R2015b :
Stary wykres czasowy z wykorzystaniem R2015 a tutaj .
Wyniki :
-
repelem
jest zawsze najszybszy przez mniej więcej współczynnik 2. -
{[6] } jest konsekwentnie szybszy niż
rld_cumsum
. -
repelem
jest najszybszy dla małych rozmiarów danych (mniej niż około 300-500 elementy) -
rld_cumsum_diff
staje się znacznie szybszy niżrepelem
około 5 000 elementów -
repelem
staje się wolniejszy niżrld_cumsum
gdzieś pomiędzy 30 000 a 300 000 elementów -
rld_cumsum
ma mniej więcej taką samą wydajność jakknedlsepp5cumsumaccumarray
-
naive_jit_test.m
ma prawie stałą prędkość i na równi zrld_cumsum
iknedlsepp5cumsumaccumarray
dla mniejszych rozmiarów, trochę szybciej dla dużych rozmiarów
Stary Wykres stawki z wykorzystaniem R2015 a tutaj .
Wniosek
Użycie repelem
poniżej około 5 000 elementów i .cumsum
+diff
rozwiązanie powyżej
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:54:28
Nie ma wbudowanej funkcji, o której wiem, ale jest jedno rozwiązanie:
index = zeros(1,sum(b));
index([1 cumsum(b(1:end-1))+1]) = 1;
c = a(cumsum(index));
Wyjaśnienie:
Wektor zera jest najpierw tworzony o tej samej długości co tablica wyjściowa (tzn. suma wszystkich replikacji w b
). Te są następnie umieszczane w pierwszym elemencie i każdy kolejny element reprezentujący, gdzie początek nowej sekwencji wartości będzie w wyjściu. Skumulowana suma wektora index
może być następnie użyta do indeksowania do a
, replikując każdą wartość żądana liczba razy.
Dla jasności, tak wyglądają różne wektory dla wartości a
I b
podanych w pytaniu:
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]
EDIT: Ze względu na kompletność istnieje jest inna alternatywa wykorzystująca ARRAYFUN , ale wydaje się, że trwa to od 20 do 100 razy dłużej niż powyższe rozwiązanie z wektorami o długości do 10 000 elementów:
c = arrayfun(@(x,y) x.*ones(1,y),a,b,'UniformOutput',false);
c = [c{:}];
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-10-12 17:52:16
Jest wreszcie (od R2015a) wbudowana i udokumentowana funkcja, aby to zrobić, repelem
. Znaczenie ma następująca składnia, gdzie drugi argument jest wektorem:
W = repelem(V,N)
, z wektoremV
i wektoremN
tworzy wektorW
, w którym elementV(i)
jest powtarzanyN(i)
razy.
Lub inaczej mówiąc, " każdy element N
określa liczbę razy, aby powtórzyć odpowiedni element V
."
Przykład:
>> 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
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-03-06 19:06:43
Problemy z wydajnością wbudowanego w MATLAB repelem
zostały naprawione od R2015b. uruchomiłem program {[1] } z posta chappjc w R2015b, i repelem
jest teraz szybszy od innych algorytmów o około czynnik 2:
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-02 20:48:57