Collapse/Compress a matrix, moving non-zero elements to the left - arrays

I am trying to collapse an array such that the follow examples are met
v = [ 1 2 3; 0 0 1; 0 1 0]
will push up all non-zero elements to the left such that I would be left with
v = [1 2 3; 0 1 1;]
Another case that I would need to satisfy is for example
v2 = [1 2 3; 1 0 0; 1 0 0; 0 1 0]
becomes
v2 = [1 2 3; 1 1 0; 1 0 0]
So basically I am squeezing the matrix up by pushing all non-zero values to the left in their corresponding column. Any help appreciated.

Here's a function which can reproduce your examples. See the comments for details
function A = pushmatrixup(A)
% Loop over each column of matrix A
for col = 1:size(A,2)
% Rearrange the values in column 'col', with the logic:
% [Values in column 'col' which don't equal 0;
% Values in column 'col' which do equal 0]
A(:,col) = [A(A(:,col) ~= 0, col); A(A(:,col) == 0, col)];
end
% All columns now have their 0s 'sunk' to the bottom of the column.
end
Checks:
pushmatrixup(v)
ans =
1 2 3
0 1 1
0 0 0
pushmatrixup(v2)
ans =
1 2 3
1 1 0
1 0 0
0 0 0
Edit: If the non-zero values are always greater than zero too, you could simply use sort...
sort(v2, 'descend')
The second part to this is removing the all-zero rows at the bottom. Extending the pushmatrixup function, or doing this after a sort...
function A = pushmatrixup(A)
% Loop over each column of matrix A
for col = 1:size(A,2)
% Rearrange the values in column 'col', with the logic:
% [Values in column 'col' which don't equal 0;
% Values in column 'col' which do equal 0]
A(:,col) = [A(A(:,col) ~= 0, col); A(A(:,col) == 0, col)];
end
% All columns now have their 0s 'sunk' to the bottom of the column.
% Remove the all-zero rows
% Loop from bottom to top on rows until one has a non-zero value
for rw = size(A,1):-1:1
if any(A(rw,:) ~= 0)
% Break if non-zero element found in row
break
end
end
% rw is row number of row with all zero elements, as loop was
% broken when this condition was broken. Cut A off at row rw.
A = A(1:rw,:);
end

Related

MATLAB : How can I generate a random binary matrix with a specific number of 1s in each row and in each column?

I would like to generate a binary matrix A of dimension m * n with a unique number J of 1s on each column of the matrix and a unique number K of 1s on each row of the matrix.
For example, for a binary matrix A of dimension m * n = 5 * 10 with J = 3 and K = 6 we may obtain the following matrix:
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
This answer by Luis Mendo, gives just the specific number of 1s on each column. In my case I'm trying to add the option of the specific factor of 1s on each column and on each row, which can be a different number.
How can I construct this matrix?
I made this just for fun. It can get stuck, so it doesn't work all the time. For the values in your example, the function found a matrix in about 40 % of the runs. You might want to tinker a bit with max_iterations. If it's too small, the probability of finding a matrix goes down, but if it's too large it's really time consuming.
Note: I wrote this in python and haven't run it in matlab, so there might be typos.
function A = generate_matrix(m,n,J,K, max_iterations)
if nargin < 5
max_iterations = m*K*4;
end
% Check if it's possible to create matrix:
if (m*K ~= n*J)
error('Matrix not possible')
end
A = falses(m,n);
allowed_rows = 1:m;
allowed_cols = 1:n;
too_many_iterations = false;
counter = 0;
while ~isempty(allowed_rows) && ~too_many_iterations
% pick random row and column from the ones that don't have enough ones
row_index = allowed_rows(randi(length(allowed_rows)));
col_index = allowed_cols(randi(length(allowed_cols)));
A(row_index, col_index) = true;
% Update allowed_rows and allowed_cols
allowed_rows = find(sum(A,2) < K);
allowed_cols = find(sum(A,1) < J);
% Keep track of number of iterations
counter = counter + 1;
too_many_iterations = counter > max_iterations;
end
if ~isempty(allowed_rows)
warning('Matrix not found!')
A = [];
end
end
The function will start with a matrix of zeros and randomly pick a row and column that doesn't have enough ones and place a one at that location, then pick a new row and column and so on until there are no rows left. The reason it doesn't work all the time, is that it can reach a dead end. For example, with m=3, n=6, J=2 and K=4, the following matrix is a dead end, since the only row without four ones is row 1, the only column without two ones is column 3, but A[1,3] already is one.
[1, 0, 1, 1, 0, 0;
1, 1, 0, 0, 1, 1;
0, 1, 0, 1, 1, 1]

Find the middle value in array that meets condition

I've got logical array(zeros and ones) 1500x700
I want to find "1" in every column and when there are more than one "1" in column i should choose the middle one.
Is that possible to do it? I know how to find "1", but don't know how to extract the middle "1" if there's couple of "1" in one column.
The find function returns the indices of your ones.
>> example=[1,0,0,1,0,1,1];
>> indices=find(example)
indices =
1 4 6 7
>> indices(floor(numel(indices)/2))
ans =
4
Do this for each column and you have a solution.
You can
Get the row and column indices of ones with find;
Apply accumarray with a custom function to get the middle row index for each column.
x = [1 0 0 0 0; 0 0 1 0 0; 1 0 1 0 0; 1 0 0 1 0]; % example
[ii, jj] = find(x); % step 1
result = accumarray(jj, ii, [size(x,2) 1], #(x) x(ceil(end/2)), NaN); % step 2
Note that:
For an even number of ones this gives the first of the two middle indices. If you prefer the average of the two middle indices replace #(x) x(ceil(end/2)) by #median.
For a column without ones this gives NaN as result. If you prefer a different value, replace the input fifth argument of accumarray by that.
Example:
x =
1 0 0 0 0
0 0 1 0 0
1 0 1 0 0
1 0 0 1 0
result =
3
NaN
2
4
NaN

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

Matlab - removing rows and columns from a matrix that contain 0's

I'm working on a problem involving beam deflections (it's not too fun :P)
I need to reduce the global stiffness matrix into the structure stiffness matrix, I do this by removing any rows and columns from the original matrix that contain a 0.
So if I have a matrix like so (let's call it K):
0 0 5 3 0 0
0 0 7 8 0 0
7 1 2 6 2 1
3 8 6 9 5 3
0 0 4 5 0 0
0 0 1 8 0 0
The reduced matrix (let's call it S) would be just
2 6
6 9
Here's what I have written so far to reduce global matrix K to stiffness matrix S
S = K;
for i = 1:length(S(:,1))
for j = 1:length(S(1,:))
if S(i,j) == 0
S(i,:) = [];
S(:,j) = [];
break;
end
end
end
However I get "Index exceeds matrix dimensions" on the line containing the "if" statement, and I'm not sure my thinking is correct on the best way to remove all rows and columns. Appreciate any feedback!
Easy:
S = K(all(K,2), all(K,1));
For nxn matrix, alternatively you can try out matrix multiplication based approach -
K=[
0 0 5 3 2 0
0 0 7 8 7 0
7 1 6 6 2 1
3 8 6 8 5 3
0 0 4 5 5 0
5 3 7 8 1 6] %// Slightly different than the one in question
K1 = double(K~=0)
K2 = K1*K1==size(K,1)
K3 = K(K2)
S = reshape(K3,max(sum(K2,1)),max(sum(K2,2)))
Output -
S =
6 6 2
6 8 5
7 8 1
The problem is when you remove some row or column you should not increase i or j but MATLAB's for loop automatically updates them. Also your algorithm cannot handle the cases like:
0 1 0
1 1 1
1 1 1
It will only remove the first column due to break condition so you need to remove it but handle indexes properly somehow. Another approach may be firstly taking product of rows and columns then checking those products and removing the corresponding rows and columns when an element of a product is zero. An example implementation in MATLAB might be like:
function [S] = stiff(K)
S = K;
% product of each row, rows(k) == 0 if there is a 0 in row k
rows = prod(S,2);
% product of each column, cols(k) == 0 if there is a 0 in column k
cols = prod(S,1);
Here we compute the product of each row and each column
% firstly eliminate the rows
% row numbers in the new matrix
ii=1;
for i = 1:size(S,1),
if rows(i) == 0,
S(ii, :) = []; % delete the row
else
ii = ii + 1; % skip the row
end
end
Here we remove rows that contain zeros by updating the index manually (notice ii).
% handle the columns now
ii = 1;
for i = 1:size(S,2),
if cols(i) == 0,
S(:, ii) = []; % delete the row
else
ii = ii + 1; % skip the row
end
end
end
Here we apply same operation to remaining columns.
Another method I can suggest is by converting the matrix K into a logical matrix where anything that is non-zero is 1 and 0 otherwise. You would then do a column sum on this matrix then check to see if any columns don't sum to the number of rows you have. You remove these columns, then do a row sum on the intermediate matrix and check if any rows don't sum to the number of columns you have. You remove these rows to be left with your final matrix. As such:
Kbool = K ~= 0;
colsToRemove = sum(Kbool,1) ~= size(Kbool,1);
K(colsToRemove,:) = [];
rowsToRemove = sum(Kbool,2) ~= size(Kbool,2);
K(:,rowsToRemove) = [];

Effective picking of surrounded element

If I have sequence 1 0 0 0 1 0 1 0 1 1 1
how to effectively locate zero which has from both sides 1.
In this sequence it means zero on position 6 and 8. The ones in bold.
1 0 0 0 1 0 1 0 1 1 1
I can imagine algorithm that would loop through the array and look one in back and one in front I guess that means O(n) so probably there is not any more smooth one.
If you can find another way, I am interested.
Use strfind:
pos = strfind(X(:)', [1 0 1]) + 1
Note that this will work only when X is a vector.
Example
X = [1 0 0 0 1 0 1 0 1 1 1 ];
pos = strfind(X(:)', [1 0 1]) + 1
The result:
pos =
6 8
The strfind method that #EitanT suggested is quite nice. Another way to do this is to use find and element-wise bit operations:
% let A be a logical ROW array
B = ~A & [A(2:end),false] & [false,A(1:end-1)];
elements = find(B);
This assumes, based on your example, that you want to exclude boundary elements. The concatenations [A(2:end),false] and [false,A(1:end-1)] are required to keep the array length the same. If memory is a concern, these can be eliminated:
% NB: this will work for both ROW and COLUMN vectors
B = ~A(2:end-1) & A(3:end) & A(1:end-2);
elements = 1 + find(B); % need the 1+ because we cut off the first element above
...and to elaborate on #Eitan T 's answer, you can use strfind for an array if you loop by row
% let x = some matrix of 1's and 0's (any size)
[m n] = size(x);
for r = 1:m;
pos(r,:) = strfind(x(r,:)',[1 0 1]) + 1;
end
pos would be a m x ? matrix with m rows and any returned positions. If there were no zeros in the proper positions though, you might get a NaN ... or an error. Didn't get a chance to test.

Resources