Arrays 用以前的非零值替换向量中的所有零
Matlab/倍频程算法示例: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(),但它只适用于第一次出现的零,而不是任意数量的零 它可
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');