Extract matrix elements - matlab

If I have a matrix:
0 0 3 4
4 3 2 0
2 0 2 0
I want to extract the non-zero elements into some batches, with respect to a rule: if an element is already taken, the other element in the same row/column is not allowed. So the extracted matrix will be:
1st batch:
0 0 3 0
4 0 0 0
0 0 0 0
2nd batch:
0 0 0 4
0 3 0 0
0 0 2 0
3rd batch:
0 0 0 0
0 0 2 0
2 0 0 0
Any other combination of batches is also accepted, as long as all non-zero elements are covered, and the rule is conformed. How would you do that in MATLAB/Octave?

For just the rows, I'd do it as follows:
A = [ 0 0 3 4 ;
4 3 2 0 ;
2 0 2 0 ];
Checking if numbers are nonzero:
Anonzero=A~=0;
>> Anonzero
0 0 1 1
1 1 1 0
1 0 1 0
Take cumsum along the rows of Anonzero:
Aidx=cumsum(A,[],2);
>> Aidx
0 0 1 2
1 2 3 3
1 1 2 2
numbatches=max(Aidx(:,end));
Set indices of zero values back to zero, so they won't get selected
A(~Anonzero)=0;
Extract batches:
batch=cell(numbatches,1);
for ii=1:numbatches
batch{ii}=A.*(Aidx==ii);
end
resulting in:
>>batch{1}
0 0 3 0
4 0 0 0
2 0 0 0
>>batch{2}
0 0 0 4
0 3 0 0
0 0 2 0
>>batch{3}
0 0 0 0
0 0 2 0
0 0 0 0
I assume there can be done something similar for a row and column rule, but I don't see it right away.. I'll think about it ;)

Gunther was already on the right track. You want to select an element, if
the row cumsum of the non-zeros is 1 AND
the column cumsum of the non-zeros is 1 AND
the element itself is non-zero.
The following code solves the problem:
A = [0, 0, 3, 4;
4, 3, 2, 0;
2, 0, 2, 0];
batches = cell(0);
while any(A(:)~=0)
selector = cumsum(A~=0, 1) .* cumsum(A~=0, 2) .* (A~=0) == 1;
batches{end+1} = A .* selector;
A(selector) = 0;
end
Note however that the returned solution is not optimal because its 2nd batch is
0 0 0 4
0 3 0 0
2 0 0 0
which means that the remaining matrix elements are from the same column:
0 0 0 0
0 0 2 0
0 0 2 0
Unfortunately, you cannot draw them in the same batch. So you end up with four batches instead of just three.
Edit: Probably, it is a good idea, to select first those elements, which appear in rows/columns with a lot of non-zeros. For example, one could use these weights
weight = repmat(sum(A~=0, 1), size(A, 1), 1) ...
.* repmat(sum(A~=0, 2), 1, size(A, 2)) .* (A~=0)
weight =
0 0 6 2
6 3 9 0
4 0 6 0
The following algorithm
batches = cell(0);
while any(A(:)~=0)
batch = zeros(size(A));
weight = repmat(sum(A~=0, 1), size(A, 1), 1) ...
.* repmat(sum(A~=0, 2), 1, size(A, 2)) .* (A~=0);
while any(weight(:)~=0)
[r,c] = find(weight == max(weight(:)), 1);
batch(r,c) = A(r,c);
A(r,c) = 0;
weight(r,:) = 0;
weight(:,c) = 0;
end
batches{end+1} = batch;
end
returns those batches.
batches{:}
ans =
0 0 0 4
0 0 2 0
2 0 0 0
ans =
0 0 3 0
4 0 0 0
0 0 0 0
ans =
0 0 0 0
0 3 0 0
0 0 2 0
So it worked at least for this small test case.

An interesting problem no doubt...My guess is that #GuntherStruyf's method will eventually be the one you should select. However, here's a simplistic solution using loops:
A = [
0 0 3 4
4 3 2 0
2 0 2 0 ];
C = {};
nz = A ~= 0;
while any(nz(:))
tmpNz = nz;
tmpA = A;
newNz = false(size(nz));
while true
[i,j] = find(tmpNz, 1);
if isempty(i) || isempty(j), break; end
tmpNz(i,:) = false;
tmpNz(:,j) = false;
newNz(i,j) = true;
end
tmpA(~newNz) = false;
C{end+1} = tmpA;
nz(newNz) = false;
end
This should be quite fast once you get rid of the growing cell-array, e.g., by pre-allocating it with a large number of initial elements, and then removing unused elements afterwards.
Nevertheless, I'd wait until #GuntherStruyf figures his thing out!

Related

Spread elements of rows to multiple rows

I am working on graph theory using adjacency matrix, I want to split the edges between multiple nodes for instance I have the following initial adjacency matrix :
a= [ 0 2 3;
2 0 1;
3 1 0]
From that matrix its clear that we have 3 nodes, Now I want to split the aforementioned rows (edges) into new random nodes between (1-3) :
split= randi([1 3],1,length(A));
split = [ 2 2 1]
I know now that I need to split the elements of the first row into two rows, the elements of the second rows also into two rows, while the elements of th third row will remain as is, and I'll have new matrix with size 5X5 as following:
A = [0 0 2 0 3;
0 0 0 0 0;
2 0 0 0 1;
0 0 0 0 0;
3 0 1 0 0]
What I want to do is to split the non-zero elements in the first row between this row and the second row, and the third with the fourth, so my matrix will look like:
An = [0 0 2 0 0;
0 0 0 0 3;
2 0 0 0 0;
0 0 0 0 1;
0 3 0 1 0]
It's not fully clear to me what's the initial point, the prerequisites and conditions. I assume that every second row/column is a row/column of zeros. I furthermore assume that the non-zero rows/columns have exactly two non-zero values whereas the second value should be moved to the next row/column. For this, I'd suggest:
A = [0 0 2 0 3 ; 0 0 0 0 0 ; 2 0 0 0 1 ; 0 0 0 0 0 ; 3 0 1 0 0];
for n = 1:2
if n==2
A = A';
end % if
for k = 1:2:size(A,1)-1
m = find(A(k,:));
A(k+(0:1),m(end)) = flipud(A(k+(0:1),m(end)));
end % for
if n==2
A = A';
end % if
end % for
A
A =
0 0 2 0 0
0 0 0 0 3
2 0 0 0 0
0 0 0 0 1
0 3 0 1 0
Here An is directly generated from a without creating A:
a = [ 0 2 3;
2 0 1;
3 1 0];
split = [ 2 2 1];
L = length(a);
cum = cumsum([1 split(1:end-1)]);
%ro = rot90(split - (0:L-1).' + cum-1, -1); %MATLAB R2016b
ro = rot90(bsxfun(#minus,split + cum-1 , (0:L-1).') , -1);
co = repmat(cum, L, 1);
idx = triu(true(L), 1);
N = sum(split);
An = zeros(N);
sub = sub2ind([N,N], ro(idx), co(idx));
An(sub) = a(idx);
An = An + An.'
An =
0 0 2 0 0
0 0 0 0 3
2 0 0 0 0
0 0 0 0 1
0 3 0 1 0

insert a row and a line in a matrix

I create a matrix b from a matrix a in the following way:
a=[1 2 ; 3 4];
b= [a zeros(2); zeros(2) a]
b =
1 2 0 0
3 4 0 0
0 0 1 2
0 0 3 4
Successively, I want to insert a line and a column of zeros at a certain point of the matrix. Let's say at middle way:
idx=2;
c=[b(1:idx,:); zeros(1,4); b(idx+1:end,:)]
c =
1 2 0 0
3 4 0 0
0 0 0 0
0 0 1 2
0 0 3 4
c=[c(:,1:idx) zeros(5,1) c(:,idx+1:end)]
c =
1 2 0 0 0
3 4 0 0 0
0 0 0 0 0
0 0 0 1 2
0 0 0 3 4
Is there a more intelligent way of doing this?
Here is another way(I don't know if it is a more intelligent way).
Assuming that you have the row index as row and the column index as col:
sc = size(b) + 1;
c = zeros(sc);
ROW = true(sc(1), 1);
ROW(row) = false;
COL = true(1, sc(2));
COL(col) = false;
Then in MATLAB r2016b /Octave you can write
c(ROW & COL)=b;
In pre 2016b you can use bsxfun
c(bsxfun(#and, ROW , COL))=b;

Putting 1's in certain places

I have 2 matrices
Matrix A = [7 3 5 2 8 4 1 6 9;
5 2 6 1 4 3 9 7 8;
9 1 4 5 2 6 3 6 7;
4 8 1 6 3 7 2 9 5;
6 1 7 2 8 4 5 9 3]
Matrix B = [1 0 0 0 0 0 0 0 0;
0 1 1 0 0 0 0 0 0;
0 0 0 0 0 0 0 1 0;
0 0 0 1 0 1 0 0 0;
0 0 0 0 0 0 0 0 1]
Matrix A and B are already defined.
Here each column can't have more than 1 what i want to do is that if when i do sum for Matrix B if i found 0 in it i have to add 1's in the places of the zero's but in certain places. In each row the 1's have to be placed in certain groups. For example if a 1 is placed in column 1, then it can be placed as well in column 2 or 3 only. It can't be placed anywhere else. If in another row it is placed in column 5, then it can be placed in column 4 or 6 only and so on. It's like group of 3. Each 3 columns are together.
To be more clear:
Here the sum of matrix B is [1 1 1 1 0 1 0 1 1]. The zeros here are placed in column 5 and 7 and i want to add 1 putting in mind where the 1 is going to be placed in the matrix. So in this example the 1 of column 5 can only be placed in row 4 as the 1's in this row are placed in column 4 and 6. The 1 of column 7 can be placed in row 5 or row 3. If we have choice between 2 rows then the 1 will be placed in the placed of the higher number of Matrix A.
The 1's have to be placed in groups; columns 1, 2 and 3 are together, columns 4,5 and 6 are together and columns 7, 8 and 9 are together. so if the 1 is placed in 1 column of the group then it can't be placed in any other place.
Let me simplify it if we have an array like this [0 0 0 0 0 0 0 1 1] This array has 3 categories, columns 1,2 and 3 are 1st category, columns 4,5 and 6 are 2nd category and so on. here i want to place a 1 so that the 3rd category won't have a zero element. This is what i want to do briefly but with a whole matrix with all the categories.
so here the output will be =
[1 0 0 0 0 0 0 0 0;
0 1 1 0 0 0 0 0 0;
0 0 0 0 0 0 0 1 0;
0 0 0 1 1 1 0 0 0;
0 0 0 0 0 0 1 0 1]
This code was tried but it doesn't give the required output as the 1 was placed in the 1st row not in the place where it has to be (the category that it should be in).
sum_cols_B = sum(B); % Sum of Matrix B (dim 1)
[~, idx] = find(sum_cols_B == 0); % Get indices where sum == 0
% Using loop to go through the indices (where sum = 0)
for ii = idx
B(1,ii) = 1; % Insert 1 in the first position of that
end % column in Matrix B
Ask me if the question is still not clear.!
Here's an updated loop that will add the missing 1's:
sum_cols_B = sum(B);
[~, idx] = find(sum_cols_B == 0);
group_size = 3;
for ii = idx
% Calculate the starting column of the group for column ii
% There are (ii-1)/group_size groups
% Add 1 for 1-based indexing
group_start = floor((ii-1)/group_size)*group_size + 1;
% Determine which rows in the current group have nonzero values
group_mask = sum(B(:,group_start:group_start+group_size-1), 2) > 0;
% Find the row number of the max in A column ii corresponding to mask
[~,rownum] = max(A(:,ii).*group_mask);
% The value in column ii of B should have a 1 inserted
% at the row containing the max in A
B(rownum,ii) = 1;
end
Results for B above are:
B =
1 0 0 0 0 0 0 0 0
0 1 1 0 0 0 0 0 0
0 0 0 0 0 0 0 1 0
0 0 0 1 1 1 0 0 0
0 0 0 0 0 0 1 0 1
B = [1 0 0 0 0 0 0 0 0;...
0 1 1 0 0 0 0 0 0;...
0 0 0 0 0 0 0 1 0;...
0 0 0 1 0 1 0 0 0;...
0 0 0 0 0 0 0 0 1]; % Matrix - using this as an example
sum_cols_B = sum(B); % Sum of Matrix B (dim 1)
[~, idx] = find(sum_cols_B == 0); % Get indices where sum == 0
% Using loop to go through the indices (where sum = 0)
for ii = idx
B(1,ii) = 1; % Insert 1 in the first position of that
end % column in Matrix B

Create N copies of a vector based on number of nonzero values in that vector

I have a 64-by-1 vector which contains 27 non-zero values. I want to create N copies from that vector such that each copy contains only 4 non-zero values (in that case the first 6 copies will have 4 non-zero values and the last copy will contain only 3 non-zero values) using MATLAB.
For example:
orig_vector = [0 0 0 0 1 0 0 0 0 5 0 0 0 2 0 1 0 2 3 1 1 ];
first_copy = [0 0 0 0 1 0 0 0 0 5 0 0 0 2 0 1 0 0 0 0 0 ];
second_copy = [0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 2 3 1 1 ];
How can this be done?
Perhaps something like:
non_zero_indices = find(orig_vector); % get array indices of non-zero elements
n_non_zero = length(non_zero_indices);
n_copies = ceil(n_non_zero / 4); % eg. with 6 non-zero elements we will have 2 copies
new_vectors = zeros(n_copies, length(orig_vector)); % matrix of new vectors where vectors go in rows
for i=0:n_copies - 2
idx = non_zero_indices(1+i*4:4+i*4);
new_vectors(i+1, idx) = orig_vector(idx);
end
idx = non_zero_indices(1+(n_copies-1)*4:end); % handle end which may have fewer than 4 elements
new_vectors(n_copies, idx) = orig_vector(idx);

In matlab, increment different column element for every row in without using a loop

Assume you have an 4x4 matrix A of zeros:
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
And an 4x1 vector B that represents column indices for matrix A (so values [1:4])
4
2
3
1
Now I want to increment those columnpositions in matrix A on the index on every row from vector B.
I have tried a couple of constructions myself but can't quite manage to do this.
For example I tried:
A(:, B) = A(:, B)+1
Which just increment every element in A.
This is how I want the operation to act:
>> A(somethting(B)) = A(somethting(B)) + 1
0 0 0 1
0 1 0 0
0 0 1 0
1 0 0 0
You can do this by using the linear index to each of the elements you want to address. Compute this using sub2ind:
>> A = zeros(4)
A =
0 0 0 0
0 0 0 0
0 0 0 0
0 0 0 0
>> B = [4 2 3 1]
B =
4 2 3 1
>> i=sub2ind(size(A),B,1:4)
i =
4 6 11 13
>> A(i) = A(i)+1
A =
0 0 0 1
0 1 0 0
0 0 1 0
1 0 0 0
Well just in case you want a looped version :p
A = zeros(4,4);
B = [4, 2, 3, 1];
for i = 1:length(B)
A(i, B(i) ) = A(i, B(i) ) + 1;
end
A = zeros(4);
B = [4 2 3 1];
A(repmat([1:4]',1,4) == repmat(B,4,1)) = 1
A =
0 0 0 1
0 1 0 0
0 0 1 0
1 0 0 0