How to find the number of times a group of a specific value is present in an array? - arrays

I have a 1 by 1000 (1 row by 1000 columns) matrix that contain only 0 and 1 as their elements. How can I find how many times 1 is repeated 3 times consecutively.
If there are more than 3 ones then it is necessary to reset the counting. So 4 would be 3+1 and it counts as only one instance of 3 consecutive 1s but 6 would be 3+3 so it counts as two instances of having 3 consecutive 1s.

This approach finds the differences between when A goes from 0 to 1 (rising edge) and from 1 to 0 (falling edge). This gives the lengths of consecutive 1s in each block. Then divide these numbers by 3 and round down to get the number of runs of 3.
Padding A with a 0 at the start and end just ensures we have a rising edge at the start if A starts with a 1, and we have a falling edge at the end if A ends with a 1.
A = round(rand(1,1000));
% padding with a 0 at the start and end will make this simpler
B = [0,A,0];
rising_edges = ~B(1:end-1) & B(2:end);
falling_edges = B(1:end-1) & ~B(2:end);
lengths_of_ones = find(falling_edges) - find(rising_edges);
N = sum(floor(lengths_of_ones / 3));
Or in a much less readable 2 lines:
A = round(rand(1,1000));
B = [0,A,0];
N = sum(floor((find(B(1:end-1) & ~B(2:end)) - find(~B(1:end-1) & B(2:end))) / 3));

You can define your custom functions like below
v = randi([0,1],1,1000);
% get runs in cell array
function C = runs(v)
C{1} = v(1);
for k = 2:length(v)
if v(k) == C{end}(end)
C{end} = [C{end},v(k)];
else
C{end+1} = v(k);
end
end
end
% count times of 3 consecutive 1s
function y = count(x)
if all(x)
y = floor(length(x)/3);
else
y = 0;
end
end
sum(cellfun(#count,runs(v)))

Here is another vectorized way:
% input
n = 3;
a = [1 1 1 1 0 0 1 1 1 0 0 0 1 1 1 1 1 0 1 1 1 1 1 1 1]
% x x x x x = 5
% output
a0 = [a 0];
b = cumsum( a0 ) % cumsum
c = diff( [0 b( ~( diff(a0) + 1 ) ) ] ) % number of ones within group
countsOf3 = sum( floor( c/n ) ) % groups of 3
You like it messy? Here is a one-liner:
countsOf3 = sum(floor(diff([0 getfield(cumsum([a 0]),{~(diff([a 0])+1)})])/n))

Related

How to unfold a Matrix on Matlab?

I have a given matrix H and I would like to unfold (expand) it to find a matrix B by following the method below :
Let H be a matrix of dimension m × n. Let x = gcd (m,n)
The matrix H is cut in two parts.
The cutting pattern being such that :
The "diagonal cut" is made by alternately moving c = n/x units to the right (we move c units to the right several times).
We alternately move c-b = m/x units down (i.e. b = (n-m)/x) (we move b units down several times).
After applying this "diagonal cut" of the matrix, we copy and paste the two parts repeatedly to obtain the matrix B.
Exemple : Let the matrix H of dimension m × n = 5 × 10 defined by :
1 0 1 1 1 0 1 1 0 0
0 1 1 0 0 1 1 0 1 1
1 1 0 1 1 1 0 1 0 0
0 1 1 0 1 0 1 0 1 1
1 0 0 1 0 1 0 1 1 1
Let's calculate x = gcd (m,n) = gcd (5,10) = 5,
Alternatively move to the right : c = n/x = 10/5 = 2,
Alternatively move down : b = (n-m)/x = (10-5)/5 = 1.
Diagonal cutting diagram : The matrix H is cut in two parts.
The cutting pattern is such that :
We move c = 2 units to the right several times c = 2 units to the right,
We repeatedly move c - b = 1 unit downwards.
We get :
After applying this "diagonal cut" of the matrix, we copy and paste the two parts repeatedly to obtain the matrix :
Remark : In the matrices X, X1 and X2 the dashes are zeros.
The resulting matrix B is (L is factor) :
Any suggestions?
This can be done by creating a logical mask with the cutting pattern, and then element-wise multiplying the input by the mask and by its negation. Repeating by L can be done with blkdiag.
H = [1 0 1 1 1 0 1 1 0 0
0 1 1 0 0 1 1 0 1 1
1 1 0 1 1 1 0 1 0 0
0 1 1 0 1 0 1 0 1 1
1 0 0 1 0 1 0 1 1 1];
L = 2;
[m, n] = size(H);
x = gcd(m, n);
c = n / x;
b = (n-m)/x;
mask = repelem(tril(true(m/b)), b, c);
A = [H.*mask; H.*~mask];
A = repmat({A}, L, 1);
B = blkdiag(A{:});

Add values in array and compare with threshold within loop in Matlab

I am stuck trying to figure this out. I have an array:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1]
I want to add the values in the array so that it equal to 10. Once the added value reaches 10, I want the array to start adding the value again until it reaches 10. There is two problem that I face here,
1) How can I add the array so that the sum = 10 everytime. Notice that in the array, there is 3. If I add all the value before 3, I get 8 and I only need 2 from 3. I need to make sure that the remainder, which is 1 is added to the next array to get the sum 10.
2) How do I break the loop once it reaches 10 and ask it continue the summation to next value to get another 10?
I created a loop but it only works for the first part of the array. I have no idea how to make it continue. The code is as follow:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
c = 0;
for i = 1:length(a)
while c < 10
c = c + a(i);
break
end
end
Please help. Thank you
This can be done using cumsum, mod, diff and find as follows:
temp = cumsum(a);
required = find([0 diff(mod(temp,10))] <0)
cumsum returns the cumulative sum which then is rescaled using mod. diff determines where the sum gets greater than or equal to 10 and finally find determines those indexes.
Edit: Above solution works if a doesn't have negative elements. If a can have negative elements then:
temp1=cumsum(a); %Commulative Sum
temp2=[0 diff(mod(temp1,10))];%Indexes where sum >=10 (indicated by negative values)
temp2(temp1<0)=0; %Removing false indexes which may come if `a` has -ve values
required = find(temp2 <0) %Required indexes
This should do what you are trying. It displays the index at which each time the sum equals 10. Check this with your testcases. rem stores the residual sum in each iteration which is carried forward in the next iteration. The rest of the code is similar to what you were doing.
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
c = 0;
rem = 0;
i = 1;
length(a);
while(i <= length(a))
c = rem;
while (c < 10 && i <= length(a))
c = c + a(i);
i = i + 1;
if(c >= 10)
rem = c - 10;
break
end
end
if(c >= 10)
disp(i-1)
end
use cumsum instead of your while loop:
a = [ 1 1 1 2 1 1 1 3 2 1 1 2 1 1 1];
a_ = a;
endidxlist = false(size(a));
startidxlist = false(size(a));
startidxlist(1) = true;
while any(a_) && (sum(a_) >= 10)
b = cumsum(a_);
idx = find(b >= 10,1);
endidxlist(idx) = true;
% move residual to the next sequence
a_(idx) = b(idx) - 10;
if a_(idx) > 0
startidxlist(idx) = idx;
elseif (idx+1) <= numel(a)
startidxlist(idx+1) = true;
end
a_(1:idx-1) = 0;
end
if (idx+1) <= numel(a)
startidxlist(idx+1) = false;
end
endidxlist gives you the end-indexes of each sequence and startidxlist the start-indexes

Find where condition is true n times consecutively

I have an array (say of 1s and 0s) and I want to find the index, i, for the first location where 1 appears n times in a row.
For example,
x = [0 0 1 0 1 1 1 0 0 0] ;
i = 5, for n = 3, as this is the first time '1' appears three times in a row.
Note: I want to find where 1 appears n times in a row so
i = find(x,n,'first');
is incorrect as this would give me the index of the first n 1s.
It is essentially a string search? eg findstr but with a vector.
You can do it with convolution as follows:
x = [0 0 1 0 1 1 1 0 0 0];
N = 3;
result = find(conv(x, ones(1,N), 'valid')==N, 1)
How it works
Convolve x with a vector of N ones and find the first time the result equals N. Convolution is computed with the 'valid' flag to avoid edge effects and thus obtain the correct value for the index.
Another answer that I have is to generate a buffer matrix where each row of this matrix is a neighbourhood of overlapping n elements of the array. Once you create this, index into your array and find the first row that has all 1s:
x = [0 0 1 0 1 1 1 0 0 0]; %// Example data
n = 3; %// How many times we look for duplication
%// Solution
ind = bsxfun(#plus, (1:numel(x)-n+1).', 0:n-1); %'
out = find(all(x(ind),2), 1);
The first line is a bit tricky. We use bsxfun to generate a matrix of size m x n where m is the total number of overlapping neighbourhoods while n is the size of the window you are searching for. This generates a matrix where the first row is enumerated from 1 to n, the second row is enumerated from 2 to n+1, up until the very end which is from numel(x)-n+1 to numel(x). Given n = 3, we have:
>> ind
ind =
1 2 3
2 3 4
3 4 5
4 5 6
5 6 7
6 7 8
7 8 9
8 9 10
These are indices which we will use to index into our array x, and for your example it generates the following buffer matrix when we directly index into x:
>> x = [0 0 1 0 1 1 1 0 0 0];
>> x(ind)
ans =
0 0 1
0 1 0
1 0 1
0 1 1
1 1 1
1 1 0
1 0 0
0 0 0
Each row is an overlapping neighbourhood of n elements. We finally end by searching for the first row that gives us all 1s. This is done by using all and searching over every row independently with the 2 as the second parameter. all produces true if every element in a row is non-zero, or 1 in our case. We then combine with find to determine the first non-zero location that satisfies this constraint... and so:
>> out = find(all(x(ind), 2), 1)
out =
5
This tells us that the fifth location of x is where the beginning of this duplication occurs n times.
Based on Rayryeng's approach you can loop this as well. This will definitely be slower for short array sizes, but for very large array sizes this doesn't calculate every possibility, but stops as soon as the first match is found and thus will be faster. You could even use an if statement based on the initial array length to choose whether to use the bsxfun or the for loop. Note also that for loops are rather fast since the latest MATLAB engine update.
x = [0 0 1 0 1 1 1 0 0 0]; %// Example data
n = 3; %// How many times we look for duplication
for idx = 1:numel(x)-n
if all(x(idx:idx+n-1))
break
end
end
Additionally, this can be used to find the a first occurrences:
x = [0 0 1 0 1 1 1 0 0 0 0 0 1 0 1 1 1 0 0 0 0 0 1 0 1 1 1 0 0 0]; %// Example data
n = 3; %// How many times we look for duplication
a = 2; %// number of desired matches
collect(1,a)=0; %// initialise output
kk = 1; %// initialise counter
for idx = 1:numel(x)-n
if all(x(idx:idx+n-1))
collect(kk) = idx;
if kk == a
break
end
kk = kk+1;
end
end
Which does the same but shuts down after a matches have been found. Again, this approach is only useful if your array is large.
Seeing you commented whether you can find the last occurrence: yes. Same trick as before, just run the loop backwards:
for idx = numel(x)-n:-1:1
if all(x(idx:idx+n-1))
break
end
end
One possibility with looping:
i = 0;
n = 3;
for idx = n : length(x)
idx_true = 1;
for sub_idx = (idx - n + 1) : idx
idx_true = idx_true & (x(sub_idx));
end
if(idx_true)
i = idx - n + 1;
break
end
end
if (i == 0)
disp('No index found.')
else
disp(i)
end

Find consecutive values in 3D array

Say I have an array the size 100x150x30, a geographical grid 100x150 with 30 values for each grid point, and want to find consecutive elements along the third dimension with a congruous length of minimum 3.
I would like to find the maximum length of consecutive elements blocks, as well as the number of occurrences.
I have tried this on a simple vector:
var=[20 21 50 70 90 91 92 93];
a=diff(var);
q = diff([0 a 0] == 1);
v = find(q == -1) - find(q == 1);
v = v+1;
v2 = v(v>3);
v3 = max(v2); % maximum length: 4
z = numel(v2); % number: 1
Now I'd like to apply this to the 3rd dimension of my array.
With A being my 100x150x30 array, I've come this far:
aa = diff(A, 1, 3);
b1 = diff((aa == 1),1,3);
b2 = zeros(100,150,1);
qq = cat(3,b2,b1,b2);
But I'm stuck on the next step, which would be: find(qq == -1) - find(qq == 1);. I can't make it work.
Is there a way to put it in a loop, or do I have to find the consecutive values another way?
Thanks for any help!
A = randi(25,100,150,30); %// generate random array
tmpsize = size(A); %// get its size
B = diff(A,1,3); %// difference
v3 = zeros(tmpsize([1 2])); %//initialise
z = zeros(tmpsize([1 2]));
for ii = 1:100 %// double loop over all entries
for jj = 1:150
q = diff([0 squeeze(B(ii,jj,:)).' 0] == 1);%'//
v = find(q == -1) - find(q == 1);
v=v+1;
v2=v(v>3);
try %// if v2 is empty, set to nan
v3(ii,jj)=max(v2);
catch
v3(ii,jj)=nan;
end
z(ii,jj)=numel(v2);
end
end
The above seems to work. It just doubly loops over both dimensions you want to get the difference over.
The part where I think you were stuck was using squeeze to get the vector to put in your variable q.
The try/catch is there solely to prevent empty consecutive arrays in v2 throwing an error in the assignment to v3, since that would remove its entry. Now it simply sets it to nan, though you can switch that to 0 of course.
Here's one vectorized approach -
%// Parameters
[m,n,r] = size(var);
max_occ_thresh = 2 %// Threshold for consecutive occurrences
% Get indices of start and stop of consecutive number islands
df = diff(var,[],3)==1;
A = reshape(df,[],size(df,3));
dfA = diff([zeros(size(A,1),1) A zeros(size(A,1),1)],[],2).'; %//'
[R1,C1] = find(dfA==1);
[R2,C2] = find(dfA==-1);
%// Get interval lengths
interval_lens = R2 - R1+1;
%// Get max consecutive occurrences across dim-3
max_len = zeros(m,n);
maxIDs = accumarray(C1,interval_lens,[],#max);
max_len(1:numel(maxIDs)) = maxIDs
%// Get number of consecutive occurrences that are a bove max_occ_thresh
num_occ = zeros(m,n);
counts = accumarray(C1,interval_lens>max_occ_thresh);
num_occ(1:numel(counts)) = counts
Sample run -
var(:,:,1) =
2 3 1 4 1
1 4 1 5 2
var(:,:,2) =
2 2 3 1 2
1 3 5 1 4
var(:,:,3) =
5 2 4 1 2
1 5 1 5 1
var(:,:,4) =
3 5 5 1 5
5 1 3 4 3
var(:,:,5) =
5 5 4 4 4
3 4 5 2 2
var(:,:,6) =
3 4 4 5 3
2 5 4 2 2
max_occ_thresh =
2
max_len =
0 0 3 2 2
0 2 0 0 0
num_occ =
0 0 1 0 0
0 0 0 0 0

matlab: inserting element after element?

Is there a way to insert an element into an array after verifying a certain element value? For example, take
A = [0 0 1 1 0 1 0]
After each 1 in the array, I want to insert another 1 to get
Anew = [0 0 1 1 1 1 0 1 1 0]
However I want a way to code this for a general case (any length 1 row array and the ones might be in any order).
A = [0 0 1 1 0 1 1];
i = (A == 1); % Test for number you want insert after
t = cumsum(i);
idx = [1 (2:numel(A)) + t(1:end-1)];
newSize = numel(A) + sum(i);
N = ones(newSize,1)*5; % Make this number you want to insert
N(idx) = A
Output:
N =
0 0 1 5 1 5 0 1 5 0
I made the inserted number 5 and split things onto multiple lines so it's easy to see what's going on.
If you wanted to do it in a loop (and this is how I would do it in real life where no-one can see me showing off)
A = [0 0 1 1 0 1 0];
idx = (A == 1); % Test for number you want insert after
N = zeros(1, numel(A) + sum(idx));
j = 1;
for i = 1:numel(A)
N(j) = A(i);
if idx(i)
j = j+1;
N(j) = 5; % Test for number you want to insert after
end
j = j+1;
end
N
Output:
N =
0 0 1 5 1 5 0 1 5 0
This code is not the most elegant, but it'll answer your question...
A=[0 0 1 1 0 1 0];
AA=[];
for ii=1:length(A);
AA=[AA A(ii)];
if A(ii)
AA=[AA 1];
end
end
I'm sure there will be also a vectorized way...
This should do the trick:
>> A = [0 0 1 1 0 1 0]
>>
>> sumA = sum(A);
>> Anew = zeros(1, 2*sumA+sum(~A));
>> I = find(A) + (0:sumA-1);
>> Anew(I) = 1;
>> Anew(I+1) = 8.2;
Anew =
0 0 1 8.2 1 8.2 0 1 8.2 0

Resources