5

I'd like to assign a cumulative numerical value for sequential runs in a binary vector. What I have is

x = [0 0 0 1 1 0 1 1 1 0 1 0 0 0 0 0 0],

and what I would like is

y = [1 2 3 1 2 1 1 2 3 1 1 1 2 3 4 5 6].

The solution using sum/cumsum/unique/find range of functions alludes me. Any help would be greatly appreciated.

Dan
  • 45,079
  • 17
  • 88
  • 157
J..S
  • 105
  • 4

3 Answers3

6

Here's a way:

a = arrayfun(@(x)(1:x), diff(find([1,diff(x),1])), 'uni', 0);
[a{:}]

The idea is to generate a list of the 'run lengths', i.e. [3,2,1,3,1,1,6] in your case, then just concatenate a bunch of vectors that count to each value in that list, i.e. cat(2, 1:3, 1:2, 1:1, 1:3.... I use arrayfun as a shortcut for reapplying the : operator and then use the comma separated list that {:} returns as a shortcut for the concatenation.

Luis Mendo
  • 110,752
  • 13
  • 76
  • 147
Dan
  • 45,079
  • 17
  • 88
  • 157
  • Well spotted on applying ``@(x) 1:x`` on runlength encoding! – Nras Jan 13 '15 at 11:14
  • wow. great! Your one liners are so cool (and complex) sometimes they give me a headache! Nice job. – Ander Biguri Jan 13 '15 at 11:16
  • 1
    Though (at least for me personally) ``a = arrayfun(@(x) (1:x), diff(find([true, diff(x) ~= 0, true])), 'uni', 0)`` would be slightly more readable. – Nras Jan 13 '15 at 11:22
  • @Nras agreed - but I think Bentoy's answer is better anyways – Dan Jan 13 '15 at 11:31
  • @Dan - nice solution. I wouldn't have considered using arrayfun. Thank you. – J..S Jan 14 '15 at 02:18
  • 1
    @J..S Thanks but I think you should consider the `diff`/`cumsum` approach. I don't know what your goals are and I haven't benched marked but I would suspect that it is faster and in my opinion it is clearer as well – Dan Jan 14 '15 at 05:59
  • 1
    I've borrowed this answer [here](http://stackoverflow.com/a/27948340/2586922) (with due credit) – Luis Mendo Jan 14 '15 at 17:16
6

(Not a one-liner, alas ...):

F = find(diff(x))+1;
y = ones(size(x));
y(F) = y(F)-diff([1,F]);
y = cumsum(y);

First, find all positions in x where there is a change; then build a vector of 1 where you substract the length of each continuous segment. At the end, take the cumsum of it.

Bentoy13
  • 4,886
  • 1
  • 20
  • 33
  • This is a clever approach! – Dan Jan 13 '15 at 10:56
  • I also had the usual suspects ``find``, ``diff`` and especially ``cumsum`` in my mind when reading the question. This would look pretty similar to my (yet non-existent) approach. – Nras Jan 13 '15 at 11:13
  • This is the type of solution I originally had in mind - but couldn't quite get working. Many thanks @Bentoy13. – J..S Jan 14 '15 at 02:14
1

Create a sparse matrix such that each run is on a different column, and then do the cumulative sum:

t = sparse(1:numel(x), cumsum([1 diff(x)~=0]), 1);
y = nonzeros(cumsum(t).*t).';

Use accumarray with a custom function to generate each increasing pattern in a different cell, and then concatenate all cells:

y = accumarray(cumsum([1 diff(x)~=0]).', 1, [], @(x) {1:numel(x).'});
y = [y{:}];
Luis Mendo
  • 110,752
  • 13
  • 76
  • 147