【问题标题】:Index an array with a set of start and end indices使用一组开始和结束索引对数组进行索引
【发布时间】:2020-01-07 07:30:03
【问题描述】:

我有两个数组:

timesteps = [1,3;5,7;9,10];
data = [1,2,3,4,5,6,7,8,9,10];

timesteps 数组中的值描述了我想要的data 的哪些值。第一列开始,第二列结束。

例如这里我想得到[1,2,3,5,6,7,9,10]

所以这段代码对我来说工作得很好,但是由于 for 循环它非常慢...... Matlab 中是否有一个衬线,以便我可以摆脱 for 循环?

newData=[];
for ind=1:size(timesteps,1)
  newData=cat(2,newData,data(timesteps(ind,1):timesteps(ind,2)));
end



编辑: 使用Wolfie 的解决方案,我得到了以下(非常好的)结果。 (我只使用了一个小数据集,通常是 50 倍大。)

(Mine)    Elapsed time is 48.579997 seconds.
(Wolfies) Elapsed time is 0.058733 seconds.

【问题讨论】:

  • 循环在 MATLAB 中不再变慢了。这是一个普遍的误解。由于 R2016b 循环中的 JIT 大修几乎不比矢量化解决方案慢,而且通常可读性更好(因此可调试)。关于您想要的结果:8 在那里做什么?
  • 此外,在您的代码 sn-p 中,变量 newData 未初始化,您编写了 ts 而不是时间步长,如果您想要更好的比较,您应该更正它
  • 您的示例根本不使用data 变量...我怀疑您的意思是data(timesteps(ind,1):timesteps(ind,2))cat

标签: matlab for-loop optimization indexing vectorization


【解决方案1】:

Irreducible's answer 使用 str2numsprintf 在数字和字符数据之间翻转以创建索引...这在我的测试中的性能较低(在我的测试中),就像您已经为小型数组所做的那样,但对于大型数组来说更快,因为内存分配处理得更好。

您可以通过预先分配输出并将其编入索引以避免循环中的串联来提高性能。对于大型阵列,这可以大大提高速度。

N = [0; cumsum( diff( timesteps, [], 2 ) + 1 )];
newData = NaN( 1, max(N) );
for ind = 1:size(timesteps,1)
    newData(N(ind)+1:N(ind+1)) = data(timesteps(ind,1):timesteps(ind,2));
end

下面的基准显示了它是如何始终更快的。

  • x 轴:data 中的元素数
  • y 轴:以秒为单位的时间
  • 假设:选择随机索引子集,其中 index 的行数比 data 少 4 倍。

基准图

注意,这取决于所使用的索引是可变的。在下面的代码中,我在每次运行时随机生成索引,因此您可能会看到情节有点跳跃。

但是,具有预分配的循环始终更快,而没有预分配的循环始终呈指数级增长。


基准代码

T = [];
p = 4:12;
for ii = p
    n = 2^ii;
    k = 2^(ii-2);

    timesteps = reshape( sort( randperm( n, k*2 ) ).', 2, [] ).';
    data = 1:n;

    f_Playergod = @() f1(timesteps, data);
    f_Irreducible = @() f2(timesteps, data);
    f_Wolfie = @() f3(timesteps, data);

    T = [T; [timeit( f_Playergod ), timeit( f_Irreducible ), timeit( f_Wolfie )]];
end

figure(1); clf; 
plot( T, 'LineWidth', 1.5 );
legend( {'Loop, no preallocation', 'str2num indexing', 'loop, with preallocation'}, 'location', 'best' );
xticklabels( 2.^p ); grid on;

function newData = f1( timesteps, data )
    newData=[];
    for ind=1:size(timesteps,1)
      newData=cat(2,newData,data(timesteps(ind,1):timesteps(ind,2)));
    end
end
function newData = f2( timesteps, data )
    newData = data( str2num(sprintf('%d:%d ',timesteps')) );
end
function newData = f3( timesteps, data )
    N = [0; cumsum( diff( timesteps, [], 2 ) + 1 )];
    newData = NaN( 1, max(N) );
    for ind = 1:size(timesteps,1)
        newData(N(ind)+1:N(ind+1)) = data(timesteps(ind,1):timesteps(ind,2));
    end
end

【讨论】:

  • 不错!我喜欢代码中提到用户名的方式=)但是我的丢失了,可能会更多
  • @user2305193 我今天可能没有时间包括你的,随意在你的机器上运行提供的基准测试代码来确认你“可能”的怀疑
【解决方案2】:

为了摆脱 for 循环,您可以执行以下操作:

timesteps = [1,3;5,7;9,10];
data = [1,2,3,4,5,6,7,8,9,10];
%create a index vector of the indices you want to extract
idx=str2num(sprintf('%d:%d ',timesteps'));
%done
res=data(idx)

res =

 1     2     3     5     6     7     9    10

但是,关于运行时间,如 cmets 中所述,我尚未对其进行测试,但我怀疑它会更快。这里唯一的优点是结果数组不必在每次迭代时更新......

【讨论】:

【解决方案3】:

我通常会去循环,但你可以这样做

%take every 1st column element and 2nd column elemeent, use the range of numbers to index data
a=arrayfun(@(x,y) data(x:y),timesteps(:,1),timesteps(:,2),'UniformOutput',0) 
%convert cell array to vector
a=[a{:}]

我应该提到这比循环慢得多

【讨论】: