MATLAB基于长度向量重复数字

时间:2014-05-15 06:18:36

标签: matlab octave vectorization run-length-encoding

是否有矢量化方式来执行以下操作? (以示例显示):

input_lengths = [ 1 1 1 4       3     2   1 ]
result =        [ 1 2 3 4 4 4 4 5 5 5 6 6 7 ]

我已经间隔了input_lengths,因此很容易理解如何获得结果

结果向量的长度为sum(lengths)。我目前使用以下循环计算result

result = ones(1, sum(input_lengths ));
counter = 1;
for i = 1:length(input_lengths)
    start_index = counter;
    end_index = counter + input_lengths (i) - 1;

    result(start_index:end_index) = i;
    counter = end_index + 1;
end

编辑:

我也可以使用arrayfun执行此操作(尽管这不是矢量化函数)

cell_result = arrayfun(@(x) repmat(x, 1, input_lengths(x)), 1:length(input_lengths), 'UniformOutput', false);
cell_result : {[1], [2], [3], [4 4 4 4], [5 5 5], [6 6], [7]}

result = [cell_result{:}];
result : [ 1 2 3 4 4 4 4 5 5 5 6 6 7 ]

6 个答案:

答案 0 :(得分:11)

完全矢量化版本:

selector=bsxfun(@le,[1:max(input_lengths)]',input_lengths);
V=repmat([1:size(selector,2)],size(selector,1),1);
result=V(selector);

下行是,内存使用率是O(numel(input_lengths)* max(input_lengths))

答案 1 :(得分:11)

所有解决方案的基准

previous benchmark之后,我将脚本中给出的所有解决方案分组并运行几个小时以获得基准测试。我之所以这样做,是因为我觉得很高兴看到每个提出的解决方案的性能与输入长度作为参数 - 我的意图不是要降低前一个解决方案的质量,这给出了有关JIT效果的其他信息。而且,每个参与者似乎都同意这一点,在所有答案中都做了相当不错的工作,所以这个伟大的帖子应该得到一个结论。

我不会在这里发布脚本代码,这很长很无趣。基准测试的过程是针对一组不同长度的输入向量运行每个解决方案:10,20,50,100,200,500,1000,2000,5000,10000,20000,50000,100000,200000,500000, 1000000.对于每个输入长度,我生成了一个基于泊松定律的随机输入向量,参数为0.8(以避免大值):

input_lengths = round(-log(1-rand(1,ILen(i)))/poisson_alpha)+1;

最后,我将每个输入长度的计算时间平均超过100次。

我使用Matlab R2013b在笔记本电脑(核心I7)上运行脚本; JIT已激活。

以下是绘制结果(对不起,颜色线),以对数对数刻度(x轴:输入长度; y轴:以秒为单位的计算时间):

Benchmark 100 trials, all solutions

所以Luis Mendo是明显的赢家,恭喜!

对于想要数字结果和/或想要重新绘制它们的人,这里它们是(将表格切成2个部分并近似为3位数,以便更好地显示):

N                   10          20          50          100         200         500         1e+03       2e+03
-------------------------------------------------------------------------------------------------------------
OP's for-loop       8.02e-05    0.000133    0.00029     0.00036     0.000581    0.00137     0.00248     0.00542 
OP's arrayfun       0.00072     0.00117     0.00255     0.00326     0.00514     0.0124      0.0222      0.047
Daniel              0.000132    0.000132    0.000148    0.000118    0.000126    0.000325    0.000397    0.000651
Divakar             0.00012     0.000114    0.000132    0.000106    0.000115    0.000292    0.000367    0.000641
David's for-loop    9.15e-05    0.000149    0.000322    0.00041     0.000654    0.00157     0.00275     0.00622
David's arrayfun    0.00052     0.000761    0.00152     0.00188     0.0029      0.00689     0.0122      0.0272
Luis Mendo          4.15e-05    4.37e-05    4.66e-05    3.49e-05    3.36e-05    4.37e-05    5.87e-05    0.000108
Bentoy13's cumsum   0.000104    0.000107    0.000111    7.9e-05     7.19e-05    8.69e-05    0.000102    0.000165
Bentoy13's sparse   8.9e-05     8.82e-05    9.23e-05    6.78e-05    6.44e-05    8.61e-05    0.000114    0.0002
Luis Mendo's optim. 3.99e-05    3.96e-05    4.08e-05    4.3e-05     4.61e-05    5.86e-05    7.66e-05    0.000111

N                   5e+03       1e+04       2e+04       5e+04       1e+05       2e+05       5e+05       1e+06
-------------------------------------------------------------------------------------------------------------
OP's for-loop       0.0138      0.0278      0.0588      0.16        0.264       0.525       1.35        2.73
OP's arrayfun       0.118       0.239       0.533       1.46        2.42        4.83        12.2        24.8
Daniel              0.00105     0.0021      0.00461     0.0138      0.0242      0.0504      0.126       0.264
Divakar             0.00127     0.00284     0.00655     0.0203      0.0335      0.0684      0.185       0.396
David's for-loop    0.015       0.0286      0.065       0.175       0.3         0.605       1.56        3.16
David's arrayfun    0.0668      0.129       0.299       0.803       1.33        2.64        6.76        13.6
Luis Mendo          0.000236    0.000446    0.000863    0.00221     0.0049      0.0118      0.0299      0.0637
Bentoy13's cumsum   0.000318    0.000638    0.00107     0.00261     0.00498     0.0114      0.0283      0.0526
Bentoy13's sparse   0.000414    0.000774    0.00148     0.00451     0.00814     0.0191      0.0441      0.0877
Luis Mendo's optim. 0.000224    0.000413    0.000754    0.00207     0.00353     0.00832     0.0216      0.0441

好的,我已经在列表中添加了另一个解决方案......我无法阻止自己优化Luis Mendo的最佳解决方案。没有信用,它只是Luis Mendo的一个变种,我稍后会解释。

显然,使用arrayfun的解决方案非常耗时。与其他解决方案相比,使用显式for循环的解决方案更快,但仍然很慢。所以,是的,矢量化仍然是优化Matlab脚本的主要选择。

由于我发现最快解决方案的计算时间有很大差异,特别是输入长度在100到10000之间,我决定更准确地进行基准测试。所以我把最慢的分开(抱歉),并重新运行其他6个运行速度更快的解决方案。除了我平均超过1000次运行之外,这个减少的解决方案列表的第二个基准是相同的。

Benchmark 1000 trials, best solutions

(这里没有表格,除非你真的想要,它和以前的数字完全相同)

正如有人所说,Daniel的解决方案比Divakar的解决方案快一点,因为似乎{@ 1}}与@times的使用比使用bsxfun慢。尽管如此,它们比for循环解决方案快10倍:显然,在Matlab中进行矢量化是一件好事。

Bentoy13和Luis Mendo的解决方案非常接近;第一个使用更多指令,但第二个指令在将1连接到repmat时使用额外的分配。这就是为什么我们看到Bentoy13的解决方案在输入长度较大(大于5.10 ^ 5)时会更快一些,因为没有额外的分配。从这个考虑,我做了一个优化的解决方案,没有额外的分配;这是代码(路易斯·门多可以把这一个放在他的答案中,如果他想:) :):

cumsum(input_lengths(1:end-1))

欢迎任何改进意见。

答案 2 :(得分:10)

更多的评论比什么,但我做了一些测试。我尝试了for循环和arrayfun,并测试了您的for循环和arrayfun版本。你的for循环是最快的。我认为这是因为它很简单,并允许JIT编译进行最优化。我使用的是Matlab,octave可能会有所不同。

时机:

Solution:     With JIT   Without JIT  
Sam for       0.74       1.22    
Sam arrayfun  2.85       2.85    
My for        0.62       2.57    
My arrayfun   1.27       3.81    
Divakar       0.26       0.28    
Bentoy        0.07       0.06    
Daniel        0.15       0.16
Luis Mendo    0.07       0.06

所以Bentoy的代码非常快,而Luis Mendo的速度几乎完全相同。我过分依赖JIT了!


我尝试的代码

clc,clear
input_lengths = randi(20,[1 10000]);

% My for loop
tic()
C=cumsum(input_lengths);
D=diff(C);
results=zeros(1,C(end));
results(1,1:C(1))=1;
for i=2:length(input_lengths)
    results(1,C(i-1)+1:C(i))=i*ones(1,D(i-1));
end
toc()

tic()
A=arrayfun(@(i) i*ones(1,input_lengths(i)),1:length(input_lengths),'UniformOutput',false);
R=[A{:}];
toc()

答案 3 :(得分:9)

result = zeros(1,sum(input_lengths));
result(cumsum([1 input_lengths(1:end-1)])) = 1;
result = cumsum(result);

这应该很快。并且内存使用量是最小的。

上述代码的优化版本,由于Bentoy13(见他的very detailed benchmarking):

result = zeros(1,sum(input_lengths));
result(1) = 1;
result(1+cumsum(input_lengths(1:end-1))) = 1;
result = cumsum(result);

答案 4 :(得分:7)

这是@ Daniel' s answer的略微变体。该解决方案的关键在于该解决方案。现在这个避免了repmat,所以这样就可以了解更多"矢量化"也许。这是代码 -

selector=bsxfun(@le,[1:max(input_lengths)]',input_lengths); %//'
V = bsxfun(@times,selector,1:numel(input_lengths)); 
result = V(V~=0)

对于所有绝望的单行搜索人 -

result = nonzeros(bsxfun(@times,bsxfun(@le,[1:max(input_lengths)]',input_lengths),1:numel(input_lengths)))

答案 5 :(得分:6)

我搜索一个优雅的解决方案,我认为David's solution是一个好的开始。我的想法是,可以生成索引,从上一个元素添加一个索引。

为此,如果我们计算输入向量的cumsum,我们得到:

cumsum(input_lengths)
ans = 1     2     3     7    10    12    13

这是相同数字序列末尾的索引。这不是我们想要的,所以我们将矢量翻转两次以获得开头:

fliplr(sum(input_lengths)+1-cumsum(fliplr(input_lengths)))
ans = 1     2     3     4     8    11    13

这是诀窍。你翻转矢量,收集它以得到翻转矢量的末端,然后翻转;但是你必须从输出向量的总长度中减去向量(+1因为索引从1开始)因为cumsum适用于翻转的向量。

一旦你完成了这个,它非常简单,你只需要在计算索引处加1,在其他地方加0,然后收集它:

idx_begs = fliplr(sum(input_lengths)+1-cumsum(fliplr(input_lengths)));
result = zeros(1,sum(input_lengths));
result(idx_begs) = 1;
result = cumsum(result);

修改

首先,请看Luis Mendo's solution,它非常接近我的但是更简单,更快一点(即使它非常接近,我也不会编辑我的)。我想在这个日期,这是所有人中最快的解决方案。

其次,在查看其他解决方案时,我已经制作了另一个单行,与我的初始解决方案和other one-liner略有不同。好吧,这不会是非常易读的,所以请一试:

result = cumsum( full(sparse(cumsum([1,input_lengths(1:end-1)]), ...
ones(1,length(input_lengths)), 1, sum(input_lengths),1)) );

我把它切成两行。好的,现在让我们来解释一下。

类似的部分是构建索引数组,以增加当前元素的值。我使用Luis Mendo的解决方案。为了在一行中构建解决方案向量,我在这里使用的事实是它实际上是二进制向量的稀疏表示,我们将在最后得到的那个。使用我们的计算索引向量作为x位置构建该稀疏向量,将1作为y位置的向量构建,并将1作为要放置在这些位置的值。第四个参数是精确的向量的总大小(如果input_lengths的最后一个元素不是1则很重要)。然后我们得到这个稀疏向量的完整表示(否则结果是一个没有空元素的稀疏向量),我们可以得到cumsum。

除了给出这个问题的另一种解决方案之外,没有使用这种解决方案。基准测试可以显示它比我原来的解决方案慢,因为内存负载较重。