1

I want to find the lengths of all series of ones and zeros in a logical array in MATLAB. This is what I did:

A = logical([0 0 0 1 1 1 1 0 1 1 0 0 0 0 0 0 1 1 1 1 1]);
%// Find series of ones:
csA = cumsum(A);
csOnes = csA(diff([A 0]) == -1);
seriesOnes = [csOnes(1) diff(csOnes)];
%// Find series of zeros (same way, using ~A)
csNegA = sumsum(~A);
csZeros = csNegA(diff([~A 0]) == -1);
seriesZeros = [csZeros(1) diff(csZeros)];

This works, and gives seriesOnes = [4 2 5] and seriesZeros = [3 1 6]. However it is rather ugly in my opinion.

I want to know if there is a better way to do this. Performance is not an issue as this is inexpensive (A is no longer than a few thousand elements). I am looking for code clarity and elegance.

If nothing better can be done, I'll just put this in a little helper function so I don't have to look at it.

Robert Seifert
  • 25,078
  • 11
  • 68
  • 113
buzjwa
  • 2,632
  • 2
  • 24
  • 37

3 Answers3

2

You could use an existing code for run-length-encoding, which does the (ugly) work for you and then filter out your vectors yourself. This way your helper function is rather general and its functionality is evident from the name runLengthEncode.

Reusing code from this answer:

function [lengths, values] = runLengthEncode(data)
startPos = find(diff([data(1)-1, data]));
lengths = diff([startPos, numel(data)+1]);
values = data(startPos);

You would then filter out your vectors using:

A = logical([0 0 0 1 1 1 1 0 1 1 0 0 0 0 0 0 1 1 1 1 1]);
[lengths, values] = runLengthEncode(A);
seriesOnes = lengths(values==1);
seriesZeros = lengths(values==0);
Community
  • 1
  • 1
knedlsepp
  • 6,065
  • 3
  • 20
  • 41
  • I like this answer the most for its generality. I know I was originally seeking clarity and elegance, and the other answers have this as well, but I think the extra generality of this answer merits accepting it. Thanks! – buzjwa Mar 01 '15 at 16:57
  • To make the above RLE algorithm compatible both with row and with column vectors, one can change the first line of the function to `startPos = find(diff([data(1)-1; data(:)])).';`. – Dev-iL Oct 15 '17 at 07:45
1

You can try this:

A = logical([0 0 0 1 1 1 1 0 1 1 0 0 0 0 0 0 1 1 1 1 1]);
B = [~A(1) A ~A(end)];                %// Add edges at start/end
edges_indexes = find(diff(B));        %// find edges
lengths = diff(edges_indexes);        %// length between edges

%// Separate zeros and ones, to a cell array
s(1+A(1)) = {lengths(1:2:end)};
s(1+~A(1)) = {lengths(2:2:end)};
nicolas
  • 3,120
  • 2
  • 15
  • 17
0

This strfind (works wonderfully with numeric arrays as well as string arrays) based approach could be easier to follow -

%// Find start and stop indices for ones and zeros with strfind by using
%// "opposite (0 for 1 and 1 for 0) sentients" 
start_ones = strfind([0 A],[0 1]) %// 0 is the sentient here and so on
start_zeros = strfind([1 A],[1 0])
stop_ones = strfind([A 0],[1 0])
stop_zeros = strfind([A 1],[0 1])

%// Get lengths of islands of ones and zeros using those start-stop indices 
length_ones = stop_ones - start_ones + 1
length_zeros = stop_zeros - start_zeros + 1
Divakar
  • 218,885
  • 19
  • 262
  • 358