Arrays 用以前的非零值替换向量中的所有零

Arrays 用以前的非零值替换向量中的所有零,arrays,matlab,loops,octave,vectorization,Arrays,Matlab,Loops,Octave,Vectorization,Matlab/倍频程算法示例: 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 ] 该算法非常简单:它遍历向量并用最后一个非零值替换所有零。当使用慢速for(i=1:length)循环并能够引用前面的元素(i-1)时,它看起来很简单,但看起来不可能以快速矢量化的形式表达出来。 我尝试了merge()和shift(),但它只适用于第一次出现的零,而不是任意数量的零 它可

Matlab/倍频程算法示例:

 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 ]
该算法非常简单:它遍历向量并用最后一个非零值替换所有零。当使用慢速for(i=1:length)循环并能够引用前面的元素(i-1)时,它看起来很简单,但看起来不可能以快速矢量化的形式表达出来。 我尝试了merge()和shift(),但它只适用于第一次出现的零,而不是任意数量的零

它可以在倍频程/Matlab中以矢量形式完成,还是必须使用C才能在大量数据上具有足够的性能


我有,而且似乎一般不可能以矢量化形式引用以前的值,比如SQL
lag()
group by
循环(I-1)
很容易做到。但是倍频程/Matlab循环非常慢

有没有人找到了这个一般问题的解决方案,或者出于基本的倍频程/Matlab设计原因,这是徒劳的


业绩基准:

解决方案1(慢循环) 运行时间为15.047秒

Dan提供的解决方案2(大约快80倍) 运行时间为0.188167秒

15.047/0.188167=79.97倍改善

按行计算的解决方案3(大约快115倍) 运行时间为0.130558秒

15.047/0.130558=115.25倍改善

魔法路易斯·门多的解决方案4(大约快250倍) 运行时间为0.0597501秒

15.047/0.0597501=251.83倍改善


(更新2019/03/13)使用MATLAB R2017a的计时:
慢循环:0.010862秒。
丹:0.072561秒。
游戏时间:0.066282秒。
路易斯·门多:0.032257秒。
填充缺失:0.053366秒。
因此,我们再次得出同样的结论:MATLAB中的循环不再缓慢


另见:

向量运算通常假定独立于单个项。如果您依赖以前的项目,那么循环是最好的方法


< Matlab中的一些额外背景:在Matlab中,操作通常不是因为向量操作而更快,而是因为向量操作只在本地C++代码中完成循环,而不是通过解释程序

,我认为这是可能的,让我们从基本知识开始,您要捕获的数字大于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's 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
 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
 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.

我认为这是一个矢量化的解决方案。以您的示例为例:

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)

以下简单方法可以满足您的需要,而且可能非常快:

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

下面是另一个解决方案,使用

我认为它也相当快,因为只有查找和索引,没有计算:

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)
是零数据的索引。(~mask)中的查询数据
然后通过下一个上一个相邻插值进行“计算”,因此它基本上会在网格中查找上一个网格点的值。正是你想要的。不幸的是,对于所有可想象的情况,所涉及的函数都有巨大的开销,这就是为什么它仍然比Luis Mendo的答案慢,尽管没有涉及算术计算


此外,还可以稍微减少
interp1
的开销:

F = griddedInterpolant(idx(mask),in(mask),'previous');
in(~mask) = F(idx(~mask));
但效果不太明显



基准 代码

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
MATLAB R2016b中新增了:它的功能与问题中的描述完全相同:

in = [ 1 0 2 0 7 7 7 0 5 0 0 0 9 ];
in(in==0) = NaN;
out = fillmissing(in,'previous');

[此新功能在中发现]。

但输入向量在开始时给出,每次更新不依赖于以前的更新。所以从技术上讲,你应该能够做到这一点,而不是一个循环。这更多的是一个评论,而不是一个答案。同样,这个问题在MATLAB意义上是可垂直化的。@gamerows是的,我想是的。刚刚加上我的attempt@Pawel你能在两个答案之间做一个
timeit
比较,并为我们提供一个简单的
八度循环解决方案吗?看看这些选项是否能提供任何性能,这将是一件有趣的事情improvements@Dan当然在工作it@Pawel可能比
[in(1:20);out(1:20)]更容易使用
isequal
。#测试并排显示是否ok
@Pawel你也可以试试Luis Mendo的解决方案吗,我怀疑它应该是最快的,而且也是最干净的。就性能时间而言,这与简单的
for
循环相比如何
arrayfun
实际上不算作矢量化。比较起来会很有趣,因为我不知道它是否比循环有优势,因为OP使用的是倍频程,没有JIT的好处。不过,比较必须以八度音阶进行。我认为可以将
accumarray
cumsum
组合起来进行。让我来更新一下,我在V的末尾加了一个非零的数字,如果你不加,当V以0结尾时它就不起作用了;我认为这也是事实。在这种情况下,这将是错误的
find(diff([0,~V])=-1)-find(diff([0,~V])==1)
Luis,自R2016b以来,有一个新函数
fillmissing
,您的解决方案比这个更快当然,普通的旧循环现在比您的解决方案快3倍左右……:)
in =   %// = out

     1     1     2     2     7     7     7     7     5     5     5     5     9
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
in = [ 1 0 2 0 7 7 7 0 5 0 0 0 9 ];
in(in==0) = NaN;
out = fillmissing(in,'previous');