Подтвердить что ты не робот

Замените все нули в векторе предыдущим ненулевым значением

Пример алгоритма Matlab/Octave:

 input vector: [ 1 0 2 0 7 7 7 0 5 0 0 0 9 ]
output vector: [ 1 1 2 2 7 7 7 7 5 5 5 5 9 ]

Алгоритм очень прост: он проходит через вектор и заменяет все нули последним ненулевым значением. Это кажется тривиальным, и это происходит, когда выполняется с медленным циклом (i = 1: длина) и может ссылаться на предыдущий элемент (i-1), но выглядит невозможен в быстрой векторизованной форме. Я попытался слить() и shift(), но он работает только для первого вхождения нуля, а не из произвольного числа из них.

Можно ли это сделать в векторизованной форме в Octave/Matlab или использовать C для достижения достаточной производительности при большом количестве данных?

Спасибо, Pawel

PS: У меня есть другой подобный медленный алгоритм для цикла для ускорения, и, как правило, невозможно ссылаться на предыдущие значения в векторизованной форме, например, на отставание SQL() или группой по циклу (i-1). Но петли Octave/Matlab ужасно медленны.

Кто-нибудь нашел решение этой общей проблемы или это бесполезно для основных причин дизайна Octave/Matlab?

========== РЕДАКТИРОВАНИЕ ===============

Тест производительности:

==== РЕШЕНИЕ 1 (медленный цикл)

in = out = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] ,1 ,100000);
tic; for i=2:length(out) if (out(i)==0) out(i)=out(i-1); endif; endfor; toc;
[in(1:20); out(1:20)] # test to show side by side if ok

Elapsed time is 15.047 seconds.

==== РЕШЕНИЕ 2 Дэн (~ 80 раз быстрее)

in = V = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] ,1 ,100000);
tic;
d = double(diff([0,V])>0);
d(find(d(2:end))+1) = find(diff([0,~V])==-1) - find(diff([0,~V])==1);
out = V(cumsum(~~V+d)-1);
toc;
[in(1:20); out(1:20)] # shows it works ok
Elapsed time is 0.188167 seconds.
# 15.047 / 0.188167 = 79.97 times improvement

==== РЕШЕНИЕ 3 от GameOfThrows (~ 115 раз быстрее)

in = a = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] ,1 ,100000);
tic;
pada = [a,888];
b = pada(find(pada >0));
bb = b(:,1:end-1);
c = find (pada==0);
d = find(pada>0);
length = d(2:end) - (d(1:end-1));
t = accumarray(cumsum([1,length])',1);
out = R = bb(cumsum(t(1:end-1)));
toc;
Elapsed time is 0.130558 seconds.
# 15.047 / 0.130558 = 115.25 times improvement

==== Магический РЕШЕНИЕ 4 Луиса Мендо (~ 250 раз быстрее)

Обновлен до аккуратного однострочного

in = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] , 1, 100000);
tic;
out = nonzeros(in).'(cumsum(in~=0));
toc;
Elapsed time is 0.0597501 seconds.
# 15.047 / 0.0597501 = 251.83 times improvement

Дэн, GameOfThrows и Luis - я очень ценю вашу быструю, острую и эффективную помощь в этом деле. Это отличные решения с отличным ускорением. Я удивлен, что такое улучшение возможно, и сейчас я отправлю второй вызов. Сначала я решил пропустить его, потому что я считал его более трудным и недоступным, но что показывают эти данные - я надеюсь, что я снова не прав.

См. также: Тривиальная/невозможная задача алгоритма в Octave/Matlab Часть II: память итераций

4b9b3361

Ответ 1

Следующий простой подход делает то, что вы хотите, и, вероятно, очень быстро:

in = [1 0 2 0 7 7 7 0 5 0 0 0 9];
t = cumsum(in~=0);
u = nonzeros(in);
out = u(t).';

Ответ 2

Я думаю, что это векторизованное решение. Работает на вашем примере:

V = [1 0 2 0 7 7 7 0 5 0 0 0 9]
%// This is where the numbers you will repeat lie. You have to cast to a double otherwise later when you try assign numbers to it it caps them at logical 1s
d = double(diff([0,V])>0)
%// find(diff([0,~V])==-1) - find(diff([0,~V])==1) is the length of each zero cluster
d(find(d(2:end))+1) = find(diff([0,~V])==-1) - find(diff([0,~V])==1)
%// ~~V is the same as V ~= 0
V(cumsum(~~V+d)-1)

Ответ 3

Я думаю, что возможно, пусть начнется с основ, вы хотите захватить, где число больше 0:

 a = [ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] %//Load in Vector
 pada = [a,888];  %//Pad A with a random number at the end to help in case the vector ends with a 0
 b = pada(find(pada >0)); %//Find where number if bigger than 0
 bb = b(:,1:end-1);     %//numbers that are bigger than 0
 c = find (pada==0);   %//Index where numbers are 0
 d = find(pada>0);     %//Index where numbers are greater than 0
 length = d(2:end) - (d(1:end-1));  %//calculate number of repeats needed for each 0 trailing gap.
 %//R = [cell2mat(arrayfun(@(x,nx) repmat(x,1,nx), bb, length,'uniformoutput',0))]; %//Repeat the value

 ----------EDIT--------- 
 %// Accumarray and cumsum method, although not as nice as Dan 1 liner
 t = accumarray(cumsum([1,length])',1);
 R = bb(cumsum(t(1:end-1)));

ПРИМЕЧАНИЕ. Я использовал arrayfun, но вы также можете использовать accumarray. Я думаю, это демонстрирует, что это можно сделать параллельно?

R =

Столбцы с 1 по 10

 1     1     2     2     7     7     7     7     5     5

Столбцы с 11 по 13

 5     5     9

ИСПЫТАНИЙ:

a = [ 1 0 2 0 7 7 7 0 5 0 0 0 9 0 0 0 ]

R =

Столбцы с 1 по 10

 1     1     2     2     7     7     7     7     5     5

Столбцы с 11 по 16

 5     5     9     9     9     9

ИСПОЛНЕНИЯ:

a = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] ,1,10000); %//Double of 130,000
Arrayfun Method : Elapsed time is 6.840973 seconds.
AccumArray Method : Elapsed time is 2.097432 seconds.

Ответ 4

Вот еще одно решение, используя линейную интерполяцию с предыдущим поиском соседа.

Я предполагаю, что он будет довольно быстрым, так как есть только взгляды, индексирование и отсутствие вычислений:

in = [1 0 2 0 7 7 7 0 5 0 0 0 9]
mask = logical(in);
idx = 1:numel(in);
in(~mask) = interp1(idx(mask),in(mask),idx(~mask),'previous');
%// out = in

Описание

Вам нужно создать вектор-указатель:

idx = 1:numel(in)  $// = 1 2 3 4 5 ...

И логическая маска, маскирующая все ненулевые значения:

mask = logical(in);

Таким образом вы получите точки сетки idx(mask) и данные сетки in(mask) для интерполяции. Точками запроса idx(~mask) являются индексы нулевых данных. Данные запроса in(~mask) затем "вычисляются" следующей соседней интерполяцией, поэтому она в основном смотрит в сетку, что является значением для предыдущей точки сетки. Именно то, что вы хотите. К сожалению, задействованные функции имеют огромные накладные расходы для всех мыслимых случаев, поэтому он все еще медленнее, чем Luis Mendo Answer, хотя нет арифметических расчетов.


Кроме того, можно немного уменьшить накладные расходы interp1:

F = griddedInterpolant(idx(mask),in(mask),'previous');
in(~mask) = F(idx(~mask));

Но эффекта слишком мало.


in =   %// = out

     1     1     2     2     7     7     7     7     5     5     5     5     9

Benchmark

0.699347403200000 %// thewaywewalk
1.329058123200000 %// GameOfThrows
0.408333643200000 %// LuisMendo
1.585014923200000 %// Dan

код

function [t] = bench()
    in = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] ,1 ,100000);

    % functions to compare
    fcns = {
        @() thewaywewalk(in);
        @() GameOfThrows(in);
        @() LuisMendo(in);
        @() Dan(in);
    }; 

    % timeit
    t = zeros(4,1);
    for ii = 1:10;
        t = t + cellfun(@timeit, fcns);
    end
    format long
end

function in = thewaywewalk(in) 
    mask = logical(in);
    idx = 1:numel(in);
    in(~mask) = interp1(idx(mask),in(mask),idx(~mask),'previous');
end
function out = GameOfThrows(a) 
    pada = [a,888];
    b = pada(find(pada >0));
    bb = b(:,1:end-1);
    c = find (pada==0);
    d = find(pada>0);
    length = d(2:end) - (d(1:end-1));
    t = accumarray(cumsum([1,length])',1);
    out = bb(cumsum(t(1:end-1)));
end
function out = LuisMendo(in) 
    t = cumsum(in~=0);
    u = nonzeros(in);
    out = u(t).';
end
function out = Dan(V) 
    d = double(diff([0,V])>0);
    d(find(d(2:end))+1) = find(diff([0,~V])==-1) - find(diff([0,~V])==1);
    out = V(cumsum(~~V+d)-1);
end

Ответ 5

Операции с вектором обычно предполагают независимость отдельных элементов. Если у вас есть зависимость от более раннего элемента, то цикл - лучший способ сделать это.

Некоторый дополнительный фон на matlab: в Matlab операции, как правило, быстрее не из-за векторных операций, а потому, что векторная операция просто делает цикл в нативном С++-коде вместо интерпретатора