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.

Author: Divakar, 2009-12-29

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

Tutaj wpisz opis obrazka

Tutaj wpisz opis obrazka

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!

 21
Author: Divakar,
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:

  1. wbudowany MATLAB repelem funkcja dodana w R2015a
  2. rozwiązanie gnovica cumsum (rld_cumsum)
  3. Divakar ' S cumsum+diff rozwiązanie (rld_cumsum_diff)
  4. knedlsepp ' s accumarray rozwiązanie (knedlsepp5cumsumaccumarray) from this post
  5. naiwna implementacja oparta na pętli (naive_jit_test.m) aby przetestować kompilator just-In-time

Wyniki test_rld.m na R2015b :

repelem time

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ść jak knedlsepp5cumsumaccumarray
  • naive_jit_test.m ma prawie stałą prędkość i na równi z rld_cumsum i knedlsepp5cumsumaccumarray dla mniejszych rozmiarów, trochę szybciej dla dużych rozmiarów

Tutaj wpisz opis obrazka

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 .

 20
Author: chappjc,
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{:}];
 15
Author: gnovice,
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 wektorem V i wektorem N tworzy wektor W, w którym element V(i) jest powtarzany N(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
 12
Author: chappjc,
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:

Zaktualizowany wykres porównujący czas wykonania repelem różnych metodPrzyspieszenie repelem nad cumsum+diff (o czynnik 2)

 3
Author: Christine Tobler,
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