【问题标题】:Counting runs in columns of a matrix计算矩阵列中的运行
【发布时间】:2015-06-07 21:08:07
【问题描述】:

我有一个由1s-1s 组成的矩阵,其中随机穿插0s

%// create matrix of 1s and -1s

hypwayt = randn(10,5);
hypwayt(hypwayt > 0) =  1;
hypwayt(hypwayt < 0) = -1;

%// create numz random indices at which to insert 0s (pairs of indices may  
%// repeat, so final number of inserted zeros may be < numz)

numz = 15;
a = 1;
b = 10;
r = round((b-a).*rand(numz,1) + a);
s = round((5-1).*rand(numz,1) + a);

for nx = 1:numz
    hypwayt(r(nx),s(nx)) = 0
end

输入:

hypwayt =

-1     1     1     1     1
 1    -1     1     1     1
 1    -1     1     0     0
-1     1     0    -1     1
 1    -1     0     0     0
-1     1    -1    -1    -1
 1     1     0     1    -1
 0     1    -1     1    -1
-1     0     1     1     0
 1    -1     0    -1    -1

我想计算nonzero 元素在一列中重复了多少次,以产生如下内容:

基本思想是(由@rayryeng 提供)对于独立的每一列,每次你点击一个唯一的数字时,你就开始增加一个累积的运行计数器,并且每次你点击与前一个相同的数字时它都会增加。一旦你击中一个新数字,它就会重置为 1,除了你击中 0 的情况,那就是 0

预期输出:

hypwayt_runs =

 1     1     1     1     1
 1     1     2     2     2
 2     2     3     0     0
 1     1     0     1     1
 1     1     0     0     0
 1     1     1     1     1
 1     2     0     1     2
 0     3     1     2     3
 1     0     1     3     0
 1     1     0     1     1

实现此目的最干净的方法是什么?

【问题讨论】:

  • 您能否进一步解释hypwayt_runs 是如何计算的?我没有看到模式(很遗憾)。
  • 看了30分钟,现在明白了。对于每一列独立地,每次你点击一个唯一的数字时,你就开始增加一个累积的运行计数器,每次你点击与前一个相同的数字时它都会增加。一旦你击中一个新数字,它就会重置为 1……除了你击中 0 的情况,那就是 0。

标签: matlab run-length-encoding


【解决方案1】:

作为 Dev-IL 的动机,这里有一个使用循环的解决方案。尽管代码是可读的,但我认为它很慢,因为您必须单独遍历每个元素。

hypwayt = [-1     1     1     1     1;
 1    -1     1     1     1;
 1    -1     1     0     0;
-1     1     0    -1     1;
 1    -1     0     0     0;
-1     1    -1    -1    -1;
 1     1     0     1    -1;
 0     1    -1     1    -1;
-1     0     1     1     0;
 1    -1     0    -1    -1];

%// Initialize output array
out = ones(size(hypwayt));

%// For each column
for idx = 1 : size(hypwayt, 2)
    %// Previous value initialized as the first row
    prev = hypwayt(1,idx);
    %// For each row after this point...
    for idx2 = 2 : size(hypwayt,1)        
        % // If the current value isn't equal to the previous value...
        if hypwayt(idx2,idx) ~= prev
            %// Set the new previous value
            prev = hypwayt(idx2,idx);
            %// Case for 0
            if hypwayt(idx2,idx) == 0
                out(idx2,idx) = 0;            
            end
         %// Else, reset the value to 1 
         %// Already done by initialization

        %// If equal, increment
        %// Must also check for 0
        else
            if hypwayt(idx2,idx) ~= 0
               out(idx2,idx) = out(idx2-1,idx) + 1;
            else
               out(idx2,idx) = 0;
            end
        end
    end
end

输出

>> out

out =

     1     1     1     1     1
     1     1     2     2     2
     2     2     3     0     0
     1     1     0     1     1
     1     1     0     0     0
     1     1     1     1     1
     1     2     0     1     2
     0     3     1     2     3
     1     0     1     3     0
     1     1     0     1     1

【讨论】:

  • 您的代码比我的矢量化代码更易读、更干净、更易理解(优雅)。 TBH,它甚至比我的解决方案快 15 倍。我的猜测是,即使这个是元素方面的,每次迭代都没有开销,因为你没有在循环中使用任何内置函数。循环并不慢,循环内部函数的开销使得当我们按元素执行它们时它会变慢。你避免了那个。 +1 来自我 :)
  • 不错的工作顺便说一句,但是 2 个 for 循环,真可惜...我必须为我的答案的第一行道歉 :)
【解决方案2】:

我想应该有更好的方法,但这应该可行

使用cumsum,diff,accumarray & bsxfun

%// doing the 'diff' along default dim to get the adjacent equality
out = [ones(1,size(A,2));diff(A)];

%// Putting all other elements other than zero to 1 
out(find(out)) = 1;

%// getting all the indexes of 0 elements
ind = find(out == 0);

%// doing 'diff' on indices to find adjacent indices
out1 = [0;diff(ind)];

%// Putting all those elements which are 1 to zero and rest to 1
out1 = 0.*(out1 == 1) + out1 ~= 1;

%// counting each unique group's number of elements
out1 = accumarray(cumsum(out1),1);

%// Creating a mask for next operation
mask = bsxfun(@le, (1:max(out1)).',out1.');

%// Doing colon operation from 2 to maxsize
out1 = bsxfun(@times,mask,(2:size(mask,1)+1).');    %'

%// Assign the values from the out1 to corresponding indices of out
out(ind) = out1(mask);

%// finally replace all elements of A which were zero to zero
out(A==0) = 0

结果:

输入:

>> A

A =

-1     1     1     1     1
 1    -1     1     1     1
 1    -1     1     0     0
-1     1     0    -1     1
 1    -1     0     0     0
-1     1    -1    -1    -1
 1     1     0     1    -1
 0     1    -1     1    -1
-1     0     1     1     0
 1    -1     0    -1    -1

输出:

>> out

out =

 1     1     1     1     1
 1     1     2     2     2
 2     2     3     0     0
 1     1     0     1     1
 1     1     0     0     0
 1     1     1     1     1
 1     2     0     1     2
 0     3     1     2     3
 1     0     1     3     0
 1     1     0     1     1

【讨论】:

  • 我不知道如何优雅地做到这一点。 +1
  • @rayryeng 谢谢。我认为我的方法很长,应该有更好的方法用很少的代码优雅地做到这一点:)
  • @rayryeng - “优雅”何时成为“不使用循环”的同义词?似乎这个问题可以通过嵌套循环和一些if 语句(递增、重置或计数器)轻松解决。由于 OP 没有提到性能是一个问题 - 我认为基于循环的解决方案可能更具可读性......它基本上是将您对问题的第二条评论放入代码中。平心而论——我并没有试图解决这个问题,它可能比我描述的更难......
  • @Dev-iL - 我用循环写了一个答案,但我认为它不优雅。如果您想尝试使用循环解决问题,请成为我的客人,但是当我尝试使用循环时,我不喜欢我编写它的方式。有了它,玩得开心!顺便说一句,如果你愿意,我可以发布我所做的,但这可能是我写过的最丑陋的代码。大声笑
  • @Dev-iL - 我用循环发布了一个答案。即使它是可读的,我也不喜欢这个解决方案。您必须单独遍历每个元素。
【解决方案3】:

rayryeng's answer 为基础,以下是我对基于循环的解决方案的看法。

输入:

hypwayt = [
    -1     1     1     1     1
     1    -1     1     1     1
     1    -1     1     0     0
    -1     1     0    -1     1
     1    -1     0     0     0
    -1     1    -1    -1    -1
     1     1     0     1    -1
     0     1    -1     1    -1
    -1     0     1     1     0
     1    -1     0    -1    -1 ];

expected_out = [
     1     1     1     1     1
     1     1     2     2     2
     2     2     3     0     0
     1     1     0     1     1
     1     1     0     0     0
     1     1     1     1     1
     1     2     0     1     2
     0     3     1     2     3
     1     0     1     3     0
     1     1     0     1     1 ];

实际代码:

CNT_INIT = 2;             %// a constant representing an initialized counter
out = hypwayt;            %// "preallocation"
out(2:end,:) = diff(out); %// ...we'll deal with the top row later
hyp_nnz = hypwayt~=0;     %// nonzero mask for later brevity
cnt = CNT_INIT;           %// first initialization of the counter

for ind1 = 2:numel(out)
    switch abs(out(ind1))
        case 2 %// switch from -1 to 1 and vice versa:
            out(ind1) = 1;
            cnt = CNT_INIT;
        case 0 %// means we have the same number again:
            out(ind1) = cnt*hyp_nnz(ind1); %//put cnt unless we're zero
            cnt = cnt+1;
        case 1 %// means we transitioned to/from zero:
            out(ind1) = hyp_nnz(ind1); %// was it a nonzero element?
            cnt = CNT_INIT;            
    end
end

%// Finally, take care of the top row:
out(1,:) = hyp_nnz(1,:);

正确性测试:

assert(isequal(out,expected_out))

我想它可以通过使用一些“复杂”的 MATLAB 函数来进一步简化,但恕我直言,它看起来确实足够优雅 :)

注意:out 的第一行被计算了两次(一次在循环中,一次在末尾),因此两次计算值存在微小的低效率。但是,它允许将整个逻辑放入在 numel() 上运行的单个循环中,我认为这证明了这一点额外计算的合理性。

【讨论】:

    【解决方案4】:

    这是一个很好的问题,由于@rayryeng 没有提出矢量化解决方案,这里是我的几行——好吧,这不公平,我花了半天的时间才得到这个。基本思路是使用cumsum作为最终函数。

    p = size(hypwayt,2);  % keep nb of columns in mind
    % H1 is the mask of consecutive identical values, but kept as an array of double (it will be incremented later)
    H1 = [zeros(1,p);diff(hypwayt)==0];
    
    % H2 is the mask of elements where a consecutive sequence of identical values ends. Note the first line of trues.
    H2 = [true(1,p);diff(~H1)>0];
    
    % 1st trick: compute the vectorized cumsum of H1
    H3 = cumsum(H1(:));
    
    % 2nd trick: take the diff of H3(H2).
    % it results in a vector of the lengths of consecutive sequences of identical values, interleaved with some zeros.
    % substract it to H1 at the same locations
    H1(H2) = H1(H2)-[0;diff(H3(H2))];
    
    % H1 is ready to be cumsummed! Add one to the array, all lengths are decreased by one.
    Output = cumsum(H1)+1;
    
    % last force input zeros to be zero
    Output(hypwayt==0) = 0;
    

    以及预期的输出:

    Output =
    
          1     1     1     1     1
          1     1     2     2     2
          2     2     3     0     0
          1     1     0     1     1
          1     1     0     0     0
          1     1     1     1     1
          1     2     0     1     2
          0     3     1     2     3
          1     0     1     3     0
          1     1     0     1     1
    

    让我补充一些解释。大技巧当然是第二个,我花了一段时间才弄清楚如何快速计算连续相同值的长度。第一个只是一个小技巧,无需任何 for 循环即可计算整个事物。如果你 cumsum H1 直接,你会得到一些偏移量的结果。这些偏移量以符合 cumsum 的方式删除,方法是获取一些键值的局部差异并在这些序列结束后立即删除它们。这些特殊值被排除在外,我也取第一行(H2 的第一行):每个第一列元素都被视为与前一列的最后一个元素不同。

    我希望现在更清楚一点(并且某些特殊情况没有缺陷......)。

    【讨论】:

      猜你喜欢
      • 2013-05-12
      • 1970-01-01
      • 1970-01-01
      • 2018-05-11
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-01-16
      • 2011-02-24
      相关资源
      最近更新 更多