How can I vectorise this loop in MATLAB - matlab

I have a loop that iterates over a matrix and sets all rows and columns with only one non-zero element to all zeroes.
so for example, it will transform this matrix:
A = [ 1 0 1 1
0 0 1 0
1 1 1 1
1 0 1 1 ]
to the matrix:
A' = [ 1 0 1 1
0 0 0 0
1 0 1 1
1 0 1 1 ]
row/column 2 of A only has 1 non zero element in it, so every element in row/column 2 is set to 0 in A'
(it is assumed that the matrices will always be diagonally symmetrical)
here is my non-vectorised code:
for ii = 1:length(A)
if nnz(A(ii,:)) == 1
A(ii,:) = 0;
A(:,ii) = 0;
end
end
Is there a more efficient way of writing this code in MATLAB?
EDIT:
I have been asked in the comments for some clarification, so I will oblige.
The purpose of this code is to remove edges from a graph that lead to a vertex of degree 1.
if A is the adjacency matrix representing a undirected graph G, then a row or column of that matrix which only has one non-zero element indicates that row/column represents a vertex of degree one, as it only has one edge incident to it.
My objective is to remove such edges from the graph, as these vertices will never be visited in a solution to the problem I am trying to solve, and reducing the graph will also reduce the size of the input to my search algorithm.
#TimeString, i understand that in the example you gave, recursively applying the algorithm to your matrix will result in a zero matrix, however the matrices that I am applying it to represent large, connected graphs, so there will never be a case like that. In response to your question as to why I only check for how many elements in a row, but the clear both columns and rows; this is because the matrix is always diagonally symmetrical, so i know that if something is true for a row, so it will be for the corresponding column..
so, just to clarify using another example:
I want to turn this graph G:
represented by matrix:
A = [ 0 1 1 0
1 0 1 0
1 1 0 1
0 0 1 0 ]
to this graph G':
represented by this matrix:
A' = [ 0 1 1 0
1 0 1 0
1 1 0 0
0 0 0 0 ]
(i realise that this matrix should actually be a 3x3 matrix because point D has been removed, but i already know how to shrink the matrix in this instance, my question is about efficiently setting columns/rows with only 1 non-zero element all to 0)
i hope that is a good enough clarification..

Not sure if it's really faster (depends on Matlab's JIT) but you can try the following:
To find out which columns (equivalently, rows, since the matrix is symmetric) have more than one non zero element use:
sum(A ~= 0) > 1
The ~= 0 is probably not needed in your case since the matrix consists of 1/0 elements only (graph edges if I understand correctly).
Transform the above into a diagonal matrix in order to eliminate unwanted columns:
D = diag(sum(A~=0) > 1)
And multiply with A from left to zero rows and from right to zero columns:
res = D * A * D

Thanks to nimrodm's suggestion of using sum(A ~= 0) instead of nnz, i managed to find a better solution than my original one
to clear the rows with one element i use:
A(sum(A ~= 0) == 1,:) = 0;
and then to clear columns with one element:
A(:,sum(A ~= 0) == 1) = 0;
for those of you who are interested, i did a 'tic-toc' comparison on a 1000 x 1000 matrix:
% establish matrix
A = magic(1000);
rem_rows = [200,555,950];
A(rem_rows,:) = 0;
A(:,rem_rows) = 0;
% insert single element into empty rows/columns
A(rem_rows,500) = 5;
A(500,rem_rows) = 5;
% testing original version
A_temp = A;
for test = 1
tic
for ii = 1:length(A_temp)
if nnz(A_temp(ii,:)) == 1
A_temp(ii,:) = 0;
A_temp(:,ii) = 0;
end
end
toc
end
Elapsed time is 0.041104 seconds.
% testing new version
A_temp = A;
for test = 1
tic
A_temp(sum(A_temp ~= 0) == 1,:) = 0;
A_temp(:,sum(A_temp ~= 0) == 1) = 0;
toc
end
Elapsed time is 0.010378 seconds
% testing matrix operations based solution suggested by nimrodm
A_temp = A;
for test = 1
tic
B = diag(sum(A_temp ~= 0) > 1);
res = B * A_temp * B;
toc
end
Elapsed time is 0.258799 seconds
so it appears that the single line version that I came up with, inspired by nimrodm's suggestion, is the fastest
thanks for all your help!

Bsxfuning it -
A(bsxfun(#or,(sum(A~=0,2)==1),(sum(A~=0,1)==1))) = 0
Sample run -
>> A
A =
1 0 1 1
0 0 1 0
1 1 1 1
1 0 1 1
>> A(bsxfun(#or,(sum(A~=0,2)==1),(sum(A~=0,1)==1))) = 0
A =
1 0 1 1
0 0 0 0
1 0 1 1
1 0 1 1

Related

Replace repeated value based on sequence size - Matlab

I have a 2D matrix composed of ones and zeros.
mat = [0 0 0 0 1 1 1 0 0
1 1 1 1 1 0 0 1 0
0 0 1 0 1 1 0 0 1];
I need to find all consecutive repetitions of ones in each row and replace all ones with zeros only when the sequence size is smaller than 5 (5 consecutive ones):
mat = [0 0 0 0 0 0 0 0 0
1 1 1 1 1 0 0 0 0
0 0 0 0 0 0 0 0 0];
Any suggestion on how to approach this problem would be very welcome.
You can use diff to find the start and end points of the runs of 1, and some logic based on that to zero out the runs which are too short. Please see the below code with associated comments
% Input matrix of 0s and 1s
mat = [0 0 0 0 1 1 1 0 0
1 1 1 1 1 0 0 1 0
0 0 1 0 1 1 0 0 1];
% Minimum run length of 1s to keep
N = 5;
% Get the start and end points of the runs of 1. Add in values from the
% original matrix to ensure that start and end points are always paired
d = [mat(:,1),diff(mat,1,2),-mat(:,end)];
% Find those start and end points. Use the transpose during the find to
% flip rows/cols and search row-wise relative to input matrix.
[cs,r] = find(d.'>0.5); % Start points
[ce,~] = find(d.'<-0.5); % End points
c = [cs, ce]; % Column number array for start/end
idx = diff(c,1,2) < N; % From column number, check run length vs N
% Loop over the runs which didn't satisfy the threshold and zero them
for ii = find(idx.')
mat(r(ii),c(ii,1):c(ii,2)-1) = 0;
end
If you want to throw legibility out of the window, this can be condensed for a slightly faster and denser version, based on the exact same logic:
[c,r] = find([mat(:,1),diff(mat,1,2),-mat(:,end)].'); % find run start/end points
for ii = 1:2:numel(c) % Loop over runs
if c(ii+1)-c(ii) < N % Check if run exceeds threshold length
mat(r(ii),c(ii):c(ii+1)-1) = 0; % Zero the run if not
end
end
The vectorized solution by #Wolfie is nice and concise, but a bit hard to understand and far from the wording of the problem. Here is a direct translation of the problem using loops. It has the advantage of being easier to understand and is slightly faster with less memory allocations, which means it will work for huge inputs.
[m,n] = size(mat);
for i = 1:m
j = 1;
while j <= n
seqSum = 1;
if mat(i,j) == 1
for k = j+1:n
if mat(i,k) == 1
seqSum = seqSum + 1;
else
break
end
end
if seqSum < 5
mat(i,j:j+seqSum-1) = 0;
end
end
j = j + seqSum;
end
end

Check neighbor values in a matrix, Matlab

my problem is: I have to make a code that takes a matrix 6x6 of 0's and 1's and check certain conditions in each position of the matrix to iterate through it, and create a matrix with the new values based on the conditions, that are based in the neighbor elements of the center value. The conditions are the following:
if the center value is 1 it has 3 options:
-If in the neighbor cells there is only one cell containing a number 1, or cero cells containing a number 1 the center value converts from 1 to 0.
-If the neighbor cells of the center value contains in total 4 or more 1's, it converts from 1 to 0.
-And if the neighbor cells contain in total 2 or 3 number 1's, it keeps being 1.
Now, if the center value is 0, it has 1 option:
-If the neighbor cells contain 3 1's in total (tops), it converts from 0 to 1.
-Else, it keeps being 0
The matrix is the following:
A = [0 1 0 1 1 0; 1 0 1 0 0 1; 1 1 0 1 1 1; 0 0 1 0 0 0; 0 1 0 0 0 1; 1 1 1 0 0 1]
So for example, the number 1 in the position A(5,2) should become 0, because it has 4 1's surrounding it, and another example, the zero in A(4,6) should become 1, because it has 3 1's surrounding it.
I made 2 loops using 1 for the rows and another for the columns to iterate through each value of the matrix, but in the conditional I don't know how to check the values of the surrounding cells or neighbor cells
I'm new to Matlab, if you can help me it would be great! Thanks.
Edit: added code the code I've made until now.
[f,c]=size(A)
for i=1:f
for j=1:c
if A(i,:)==1
if A(i+1,j+1)==1 && A(i+1,j-1)==1 && A(i-1,j-1)==1
A(i,:)=1;
end
elseif A(i,:)==0
if A(i+1,j+1)==1 && A(i+1,j-1)==1 && A(i-1,j-1)==1
A(i,:)=1;
end
end
end
end
I tried to set the conditions to the (i-1,j-1),(i+1,j+1),(i+1,j),(i,j+1),(i-1,j),(i,j-1) but I think that will not work because I'm checking coditions as a group not counting the total numbers of 1 in the neighbors
Edit 2: been thinking how to solve it and I thought that I could make a variable that counts the quantity of 1's in the surroundings of the center value, but it gives me an error that it exceeds the dimensions of the matrix in line 50, even after I made considerations for the first column, row and last column, row.
This is the complete code:
[f,c] = size(gen0);
nrogen = input('Introduzca el número de generaciones de bacterias que quiera crecer: ');
if isempty(nrogen) || ~isnumeric(nrogen) || ~isscalar(nrogen) || round(nrogen)~=nrogen || nrogen < 0
disp('Número no pertinente, intente de nuevo');
return
end
nac = 0;
mue = 0;
neigh = 0;
for k=1:nrogen
for i=1:f
for j=1:c
if i>1 && i<6
if gen0(i+1,j+1)==1
neigh=neigh+1;
end
if gen0(i,j+1)==1
neigh=neigh+1;
end
if gen0(i+1,j)==1
neigh=neigh+1;
end
if gen0(i-1,j-1)==1
neigh=neigh+1;
end
if gen0(i,j-1)==1
neigh=neigh+1;
end
if gen0(i-1,j)==1
neigh=neigh+1;
end
elseif i==1
if gen0(i+1,j+1)==1
neigh=neigh+1;
end
if gen0(i,j+1)==1
neigh=neigh+1;
end
if gen0(i+1,j)==1
neigh=neigh+1;
end
elseif i==6
if gen0(i-1,j-1)==1
neigh=neigh+1;
end
if gen0(i,j-1)==1
neigh=neigh+1;
end
if gen0(i-1,j)==1
neigh=neigh+1;
end
if gen0(i,:)==1
if neigh==2 || neigh==3
gen0(i,:)=1;
elseif neigh==1 || neigh==0
gen0(i,:)=0;
end
end
end
end
disp(gen0);
end
if gen0(i,:)==1
if neigh==2 || neigh==3
gen0(i,:)=1;
elseif neigh==1 || neigh==0 || neigh>3
gen0(i,:)=0;
end
end
end
Nested loops is a sensible option. However, what you are doing is basically an image filter, something that is already implemented by the function imfilter, so Why not to take advantage of that and simplify your life? What you want to do can be accomplished with:
%Creating the filter that adds up all neighbors
filter=ones(3,3);
filter(2,2)=0;
%Apply the filter to compute the sum of all neighboring elements
onesCount=imfilter(A,filter);
%Creating the rules in arrays that contains result for a given count of ones (+1)
%ones count 0 1 2 3 4 5 6 7 8 9
onesRule = [0 0 1 1 0 0 0 0 0 0];
zeroRule = [0 0 0 1 0 0 0 0 0 0];
%Initializing output matrix
out=zeros(size(A));
%Assigning values to the cells with ones
outForOnes=onesRule(onesCount+1);
out(A==1)=outForOnes(A==1);
%Assigning values to the cells with zeros
%(if you want you can skip this step initializing out=outForZeros)
outForZeros=zeroRule(onesCount+1);
out(A==0)=outForZeros(A==0);
Otherwise, if you want to keep the nested loops, instead of dealing with the exceptions of index out of bounds, I would suggest to pad A with zeros all around. So if A size is n by m (6 by 6 in this case) you do:
B=zeros(n+2,m+2);
B(2:end-1,2:end-1)=A;
A=B;
And then you loop i and j between 2:n+1 and 2:m+1
In my opinion, using two nested loops in this case is a good approach. Retrieving the surrounding values of a matrix element is always tricky, but it can be accomplished with some efforts. Here is the solution I propose you:
A = [
0 1 0 1 1 0;
1 0 1 0 0 1;
1 1 0 1 1 1;
0 0 1 0 0 0;
0 1 0 0 0 1;
1 1 1 0 0 1
];
A_rows = size(A,1);
A_cols = size(A,2);
for i = 1:A_rows
for j = 1:A_cols
% Retrieve the current center...
value = A(i,j);
% Retrieve the neighboring column and row offsets...
c = bsxfun(#plus,j,[-1 0 1 -1 1 -1 0 1]);
r = bsxfun(#plus,i,[-1 -1 -1 0 0 1 1 1]);
% Filter the invalid positions...
idx = (c > 0) & (c <= A_cols) & (r > 0) & (r <= A_rows);
% Transform the valid positions into linear indices...
idx = (((idx .* c) - 1) .* A_rows) + (idx .* r);
idx = reshape(idx.',1,numel(idx));
% Filter the invalid linear indices...
idx = idx(idx > 0);
% Find the center neighbors and their sum...
neighbors = A(idx);
neighbors_sum = sum(neighbors);
% Apply the transformation criterions to A...
if (value == 0)
if (neighbors_sum == 3)
A(i,j) = 1;
end
else
if (neighbors_sum <= 1) || (neighbors_sum >= 3)
A(i,j) = 0;
end
end
end
end
The final output for the given example is:
A =
0 1 1 0 0 0
0 0 0 1 0 1
0 0 1 0 0 0
0 1 0 0 0 0
0 0 1 0 0 0
0 1 1 0 0 0
I just have a few doubts about the whole process you described.
The first one concerns the criterions to apply when the center value is equal to 1. Two of them seem to be contradictory:
-If the neighbor cells of the center value contains in total 3 or more 1's, it converts from 1 to 0.
-And if the neighbor cells contain in total 2 or 3 number 1's, it keeps being 1.
When the neighbors sum is equal to 3... which condition should be applied? The first one or the second one?
The second one concerns the original matrix A. Should it be updated inside the loop? I mean, in the current code, the values of A change when certain conditions are met... but this also means that the conditions are influenced by the outcome of the previous iterations. Maybe your goal is to keep A static while updating a clone of it instead?
Anyway, both issues are easy to deal with and you should be able to adapt my code to your needs without any problem.

converting elements in a vector in matlab

For a project, I'm trying to find the first 1 of a series of ones in a vector. For example, I have as input:
x1=[1 0 0 1 1 1 0 1 0 1 0 0 1 1]
and I need as output:
Y1=[1 0 0 1 0 0 0 1 0 1 0 0 1 0]
So every time there is a 1 in the vector, all consequent ones need to be turned into zeroes.
I have the following code, but for some reason it just returns Y1 with exactly the same values as x1.
n=numel(x1);
Y1=zeros(n,1);
for i = 1:n
if x1(i) == 1
Y1(i)= 1;
for j = (i+1): n
if x1(j)== 1
Y1(j)=0;
elseif x1(j) == 0
Y1(j)=0;
i=j+1;
break
end
end
elseif x1(i) == 0
Y1(i)= 0;
end
end
Any help would be greatly appreciated.
Easy with diff. No loops needed.
Y1 = [ x1(1) diff(x1)==1 ];
or equivalently
Y1 = diff([0 x1])==1;
How this works: diff computes the difference of an element with respect to the preceding element. When that difference is 1, a new run of ones has begun. The first element requires special treatment.
A generalization of the answer by #Luis for the case where your vectors don't just contain zeros and ones:
Y1 = diff([0 x1]) & x1 == 1
This checks whether the value is one, and whether it is different from the previous value.

How to replace non-zero elements randomly with zero?

I have a matrix including 1 and 0 elements like below which is used as a network adjacency matrix.
A =
0 1 1 1
1 1 0 1
1 1 0 1
1 1 1 0
I want to simulate an attack on the network, so I must replace some specific percent of 1 elements randomly with 0. How can I do this in MATLAB?
I know how to replace a percentage of elements randomly with zeros, but I must be sure that the element that is replaced randomly, is one of the 1 elements of matrix not zeros.
If you want to change each 1 with a certain probability:
p = 0.1%; % desired probability of change
A_ones = find(A); % linear index of ones in A
A_ones_change = A_ones(rand(size(A_ones))<=p); % entries to be changed
A(A_ones_change) = 0; % apply changes in those entries
If you want to randomly change a fixed fraction of the 1 entries:
f = 0.1; % desired fraction
A_ones = find(A);
n = round(f*length(A_ones));
A_ones_change = randsample(A_ones,n);
A(A_ones_change) = 0;
Note that in this case the resulting fraction may be different to that intended, because of the need to round to an integer number of entries.
#horchler's point is a good one. However, if we keep it simple, then you can just multiple your input matrix to a mask matrix.
>> a1=randint(5,5,[0 1]) #before replacing 1->0
a1 =
1 1 1 0 1
0 1 1 1 0
0 1 0 0 1
0 0 1 0 1
1 0 1 0 1
>> a2=random('unif',0,1,5,5) #Assuming frequency distribution is uniform ('unif')
a2 =
0.7889 0.3200 0.2679 0.8392 0.6299
0.4387 0.9601 0.4399 0.6288 0.3705
0.4983 0.7266 0.9334 0.1338 0.5751
0.2140 0.4120 0.6833 0.2071 0.4514
0.6435 0.7446 0.2126 0.6072 0.0439
>> a1.*(a2>0.1) #And the replacement prob. is 0.1
ans =
1 1 1 0 1
0 1 1 1 0
0 1 0 0 1
0 0 1 0 1
1 0 1 0 0
And other trick can be added to the mask matrix (a2). Such as a different freq. distribution, or a structure (e.g. once a cell is replaced, the adjacent cells become less likely to be replaced and so on.)
Cheers.
The function find is your friend:
indices = find(A);
This will return an array of the indices of 1 elements in your matrix A and you can use your method of replacing a percent of elements with zero on a subset of this array. Then,
A(subsetIndices) = 0;
will replace the remaining indices of A with zero.

analyzing sequences matlab

I am trying to write a short matlab function that will recieve a vector and will return me the index of the first element of the longest sequence of 1s (I can assume that the sequence consists of 1s and 0s). for example:
IndexLargeSeq([110001111100000000001111111111110000000000000000000000000000000])
will return 21 - which is the index of the first 1 of the longest sequence of 1s.
thank you
ariel
There you go:
% input:
A = [0 0 0 0 0 0 0 0 0 0 1 1 1 1 1 0 0 1 1 0 0 0 0 0 0 0 0 0 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 1 1 1]';
% replace 0 with 2 because the next command doesn't work with '0' as values
A(A == 0) = 2;
% accumulate data sets
B = [A(diff([A; 0]) ~= 0), diff(find(diff([0; A; 0])))];
% maximize second column where first column == 1
maxSeq = max(B(B(:, 1) == 1, 2));
% get row of B where first column == 1 && second column == maxSeq
row = find(B(:,1) == 1 & B(:,2) == maxSeq, 1);
% calculate the index of the first 1s of this longest sequence:
idx = sum(B(1:(row-1),2)) + 1
idx than is the value (the index) you are looking for, maxSeq is the length of this sewuence of 1s. A has to be a row-vector.
If you want to understand how the datasets are accumulated (the command B = ...), look here: How to accumulate data-sets?.
Here is another option measuring distances between indices of 0s. The code takes into account situations if there are no 1s at all (returns empty vector), or if there are multiple sequences with the longest length. x is an input row vector.
idx = find([1 ~x 1]); %# indices of 0s +1
idxdiff = diff(idx); %# lengths of sequences (+1)
maxdiff = max(idxdiff);
if maxdiff == 1
maxseqidx = []; %# no 1s at all
else
%# find all longest sequences, may be more then one
maxidx = find(idxdiff == maxdiff);
maxseqidx = idx(maxidx);
end
disp(maxseqidx)
EDIT: If x can be either row or column vector, you can change the first line to
idx = find([1; ~x(:); 1]);
The output will be a column vector in this case.