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(),但它只适用于第一个零的出现,而不是任意数量的零。
Octave/Matlab是否可以以矢量化形式完成此操作,还是必须使用C以在大量数据上具有足够的性能?
我有另一个类似的缓慢循环算法以加速,但通常无法以向量化的形式引用先前的值,就像SQL中的
lag()
或group by
或loop (i-1)
那样容易。但Octave/Matlab循环非常缓慢。有人找到了这个一般问题的解决方案吗?还是由于Octave/Matlab的基本设计原因而徒劳无功?
性能基准:
解决方案1(慢循环)
in = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] ,1 ,100000);
out = in;
tic
for i=2:length(out)
if (out(i)==0)
out(i)=out(i-1);
end
end
toc
[in(1:20); out(1:20)] % test to show side by side if ok
已经过去了15.047秒。
Dan的解决方案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
经过了0.188167秒,时间已过去。
15.047 / 0.188167 = 改进了79.97倍
GameOfThrows提出的解决方案3(速度提高了约115倍)
in = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] ,1 ,100000);
a = in;
tic;
pada = [a,888];
b = pada(pada >0);
bb = b(:,1:end-1);
c = find (pada==0);
d = find(pada>0);
len = d(2:end) - (d(1:end-1));
t = accumarray(cumsum([1,len])',1);
out = bb(cumsum(t(1:end-1)));
toc;
经过0.130558秒的时间。
15.047 / 0.130558 = 改进了115.25倍
Luis Mendo的神奇解决方案4(快约250倍)
in = repmat([ 1 0 2 0 7 7 7 0 5 0 0 0 9 ] , 1, 100000);
tic;
u = nonzeros(in);
out = u(cumsum(in~=0)).';
toc;
经过的时间为0.0597501秒。
15.047 / 0.0597501 = 改进了251.83倍
(更新于2019/03/13)使用MATLAB R2017a的时间:
Slow loop: 0.010862 seconds.
Dan: 0.072561 seconds.
GameOfThrows: 0.066282 seconds.
Luis Mendo: 0.032257 seconds.
fillmissing: 0.053366 seconds.
因此,我们再次得出相同的结论:MATLAB中的循环不再缓慢!
另请参阅: 在Octave/Matlab中的微不足道/不可能的算法挑战 第二部分:迭代记忆
timeit
比较,比较这两个答案和一个朴素的for
循环解决方案?看看这些选项是否提供了任何性能改进,这将是有趣的。 - Danisequal
可能更容易,而不是使用[in(1:20); out(1:20)]#test to show side by side if ok
。 - Dan