1

I have a matrix containing group identifiers:

A = [ 1 ; 1 ; 2;2;2;3;3]

and I would like to enhance this by a running sequence by group to obtain a matrix like this:

B = [ 1,1 ; 1,2 ; 2,1;2,2;2,3;3,1;3,2]

B =

 1     1
 1     2
 2     1
 2     2
 2     3
 3     1
 3     2

How can I do this without using the dreaded for-loop? Thank you!

Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
bonifaz
  • 588
  • 3
  • 16
  • Is the last one `3,2`? – OmG Oct 14 '19 at 09:34
  • @OmG yes indeed, thank you! – bonifaz Oct 14 '19 at 09:40
  • What if there's a new `2` after the run of `3`'2? That is, what's the output for `A = [ 1 ; 1 ; 2;2;2;3;3;2]`? – Luis Mendo Oct 14 '19 at 09:40
  • Even if the above is not possible, would an input `A = [2;2; 1;1;1; 3;3]` be possible? Or `A=[1;1; 4;4; 5]`? Some approaches could benefit from the input being sorted, or not having gaps, or not having more than one run of each value. Please specify if any of those restrictions applies in your problem – Luis Mendo Oct 14 '19 at 10:05
  • Possible duplicate of [Count repeating integers in an array](https://stackoverflow.com/questions/54079558/count-repeating-integers-in-an-array) – obchardon Oct 14 '19 at 11:39
  • For small vector you can use: `B = [A,sum(triu(A==A.'),1).']` – obchardon Oct 14 '19 at 11:44

2 Answers2

4

Input contains positive integers, not necessarily consecutive but sorted

[B, ~] = find(sort(sparse(1:numel(A), A, true), 1, 'descend'));
B = [A B];

This works as follows:

  1. Create an intermediate logical sparse matrix, with column positions determined by A and consecutive row positions: sparse(1:numel(A), A, true). Although this matrix may be large, creating it as sparse makes the approach memory-efficient.
  2. Move the true entries in each column to the upper part of the matrix: sort(..., 1, , 'descend').
  3. The row indices contain the desired result: [B, ~] = find(...).

Input contains positive integers, not necessarily consecutive, not necessarily sorted

t = sparse(1:numel(A), A, true);
t = t.*cumsum(t, 1);
B = [A nonzeros(t.')];

This works as follows:

  1. Create an intermediate logical sparse matrix as before.
  2. In each column, replace nonzeros by consecutive values 1, 2, ...
  3. Transpose the matrix. The nonzeros in linear order are the desired result.
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
  • Your approach seems to require, contrary to your stated assumptions, that the input matrix A is sorted. E.g. here it fails: A = [ 2 ; 1; 2; 3 ; 3;1;3]. But that's ok, I can sort the matrix. – bonifaz Oct 14 '19 at 11:01
  • 1
    I have also included a slightly different approach that works for the unsorted case – Luis Mendo Oct 14 '19 at 11:30
  • Seems to me it also works without creating a full matrix, quite a bit faster for large input matrix.. – bonifaz Oct 14 '19 at 11:52
  • 1
    @bonifaz Thank you for the correction (I used `full` for debugging and forgot to remove it). I have edited that and added an explanation – Luis Mendo Oct 14 '19 at 12:36
0

This works for nonintegers.

[s, is] = sort(A)
[~, d] = cummax(s) ;
C(is, :) = (2:numel(A) + 1).'  -  d;
B = [A C] ;

If input is sorted:

[~, d] = cummax(A) ;
C = (2:numel(A) + 1).'  -  d;
B = [A C] 
rahnema1
  • 15,264
  • 3
  • 15
  • 27