我正在尝试使用'values'数组和'counter'数组将多个值插入到数组中。例如,如果:
a=[1,3,2,5]
b=[2,2,1,3]
我想要一些功能的输出
c=somefunction(a,b)
是
c=[1,1,3,3,2,5,5,5]
其中a(1)重复b(1)次,a(2)重复b(2)次等...
MATLAB中是否有内置函数可以执行此操作?如果可能的话,我想避免使用for循环。我尝试过'repmat()'和'kron()'的变体无济于事。
这基本上是Run-length encoding
。
答案 0 :(得分:21)
我们有一系列值,vals
和游程长度,runlens
:
vals = [1,3,2,5]
runlens = [2,2,1,3]
我们需要在vals
中重复runlens
中每个对应元素的每个元素。因此,最终的输出将是:
output = [1,1,3,3,2,5,5,5]
MATLAB最快的工具之一是cumsum
,在处理不规则模式的矢量化问题时非常有用。在所述问题中,不规则性伴随着runlens
中的不同元素。
现在,为了利用cumsum
,我们需要在这里做两件事:初始化一个zeros
数组,并将“适当的”值放在零数组上的“关键”位置,这样“ cumsum
“已应用,我们最终将重复vals
次runlens
次。
步骤:让我们对上述步骤进行编号,使前瞻性方法更容易理解:
1)初始化零数组:长度必须是多少?由于我们重复runlens
次,因此零数组的长度必须是所有runlens
的总和。
2)找到关键位置/索引:现在这些关键位置是沿零点阵列的位置,其中vals
的每个元素开始重复。
因此,对于runlens = [2,2,1,3]
,映射到零数组的关键位置将是:
[X 0 X 0 X X 0 0] % where X's are those key positions.
3)找到合适的值:在使用cumsum
之前要敲打的最后一根钉子就是将“适当的”值放入这些关键位置。现在,由于我们很快会做cumsum
,如果你仔细思考,你需要一个differentiated
values
版cumsum
diff
,values
在那些带回我们的runlens
。由于这些差异值将放置在由cumsum
距离分隔的地方的零数组上,因此在使用vals
后,我们会将每个runlens
元素重复% 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)
次作为最终输出
解决方案代码
这是实施上述所有步骤的实现 -
array(clens(end)) = 0; % instead of array = zeros(1,(clens(end)))
预先分配黑客
可以看出,上面列出的代码使用预分配零。现在,根据这个UNDOCUMENTED MATLAB blog on faster pre-allocation,可以使用 -
实现更快的预分配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;
结束:功能代码
为了包装所有内容,我们将有一个紧凑的函数代码来实现这样的行程长度解码 -
cumsum+diff
基准代码
下面列出的是基准代码,用于比较此帖中所述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')
上的other cumsum-only
based approach -
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;
的相关功能代码:
cumsum-only
运行时和加速图
建议的方法似乎给我们一个明显的加速cumsum+diff
方法,大约 3x !
为什么这种基于cumsum-only
的新方法比之前的cumsum-only
方法更好?
嗯,原因的本质在于vals
方法的最后一步,需要将“cumsumed”值映射到cumsum+diff
。在基于diff(vals)
的新方法中,我们正在进行n
而不是MATLAB仅处理sum(runLengths)
元素(其中n是runLengths的数量)与{{1}的映射相比较} cumsum-only
方法的元素数量,这个数字必须比n
多很多倍,因此采用这种新方法可以显着加快速度!
答案 1 :(得分:20)
基准
针对R2015b进行了更新:repelem
现在对所有数据大小都是最快的。
经测试的功能:
repelem
功能cumsum
解决方案(rld_cumsum
)cumsum
+ diff
解决方案(rld_cumsum_diff
)knedlsepp5cumsumaccumarray
的accumarray
解决方案(this post)
naive_jit_test.m
)来测试即时编译器 2015年R2015上test_rld.m
的结果 b :
旧时序图使用R2015 a here。
<强>首饰强>:
repelem
总是最快的大约2倍。rld_cumsum_diff
始终比rld_cumsum
快。repelem
对于小数据量(小于约300-500个元素)来说速度最快rld_cumsum_diff
明显快于repelem
约5 000个元素repelem
变得慢于rld_cumsum
介于30 000到300 000个元素之间rld_cumsum
与knedlsepp5cumsumaccumarray
naive_jit_test.m
速度几乎保持恒定,与rld_cumsum
和knedlsepp5cumsumaccumarray
相比,尺寸更小,大尺寸更快一点使用R2015 a here的旧费率图。
<强>结论强>
使用repelem
下面的约5 000个元素以及上面的。cumsum
+ diff
解决方案
答案 2 :(得分:16)
我知道没有内置功能,但这是一个解决方案:
index = zeros(1,sum(b));
index([1 cumsum(b(1:end-1))+1]) = 1;
c = a(cumsum(index));
首先创建与输出数组长度相同的零向量(即b
中所有复制的总和)。然后将其中的一个放在第一个元素中,每个后续元素表示新值序列的开始位于输出中的位置。然后可以使用向量index
的累积和来索引a
,将每个值复制所需的次数。
为了清楚起见,这就是问题中给出的a
和b
值的各种向量的样子:
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]
编辑:为了完整起见,是使用ARRAYFUN的另一种选择,但这似乎需要20到100倍的时间才能运行比上述解决方案的载体长达10,000个元素:
c = arrayfun(@(x,y) x.*ones(1,y),a,b,'UniformOutput',false);
c = [c{:}];
答案 3 :(得分:12)
最后(从 R2015a 开始)有一个内置且记录在案的函数,repelem
。以下语法(其中第二个参数是向量)与此相关:
带有向量W = repelem(V,N)
和向量V
的
N
会创建一个向量W
,其中元素V(i)
重复N(i)
次。
或换句话说,“N
的每个元素都指定重复V
的相应元素的次数。”
示例:
>> 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
答案 4 :(得分:3)
自R2015b起,MATLAB内置repelem
的性能问题已得到修复。我在R2015b的chappjc帖子中运行了test_rld.m
程序,repelem
现在比其他算法快了大约2倍: