Hi i would like to create a function which rotates all elements counter clockwise by one place of a square matrix.
[ 1 2 3 4;
5 6 7 8;
9 10 11 12;
13 14 15 16]
shall become:
[ 2 3 4 8;
1 7 11 12;
5 6 10 16;
9 13 14 15]
This is my attempt:
size_mat = size(A,1)
B=zeros(size_mat);
loops = fix(size_mat/2)
if mod(size_mat,2) ~= 0
B(loops+1,loops+1)= A(loops+1,loops+1)
end
for i=1:loops,
B(i+1,i)=A(i,i);
for j=i:(size_mat-i)
B(i,j)=A(i,j+1);
B(j,size_mat+1-i)=A(j+1,size_mat+1-i);
B(size_mat+1-i,size_mat+1-j)=A(size_mat+1-i,size_mat-j);
if((j+2)<=size_mat)
B(size_mat+1-j,i)=A(size_mat-j,i);
end
end
end
Can I do better?
Code
%%// A is the input matrix
N = size(A,1)
sz = ceil(N/2)
quad4 = ones(sz)
quad1 = quad4
quad1(logical(fliplr(triu(ones(sz),1))))=N
quad4(logical(tril(quad4)))=-N
quad14 = [quad1;quad4]
quad = [-1*rot90(quad14,2) quad14]
if rem(N,2)==1
quad(:,sz)=[]
quad(sz,:)=[]
quad(sz,sz)=0
end
rotatedA = A(reshape(1:numel(A),size(A))+quad) %%// rotated output matrix
Related
Consider a matrix X. I have to update a submatrix of X, X(row1:row2, col1:col2), with a matrix Z (of size row2-row1+1, col2-col1+1) but only on those positions where a logical matrix L (of size row2-row1+1, col2-col1+1) is true.
E.g. if
X=[ 1 2 3 4 5 6
11 12 13 14 15 16
21 22 23 24 25 26
31 32 33 34 34 36]
Z=[31 41
32 42]
L=[ 1 0
0 1]
row1 = 2; row2 = 3; col1 = 3; col2 = 4
then after the update I should get:
X=[ 1 2 3 4 5 6
11 12 31 14 15 16
21 22 23 42 25 26
31 32 33 34 34 36]
Currently I do the following:
Y = X(row1:row2, col1:col2);
Y(L) = Z(L);
X(row1:row2, col1:col2) = Y;
This code is in a tight loop and according to Matlab's (v2019a) profiler is the main bottleneck of my program. In the real code X is a 2000x1500x3 cube; row1, row2, col1, col2, Z and L change in the loop.
The question is whether it can be rewritten into a single / faster assignment.
Thanks.
Honestly, without seeing your actual code, I get the sense that your solution may be as fast as you can get. The reason I say that is because I tested a few different solutions by creating some random sample data closer to your actual problem. I assumed X is an image of type uint8 with size 2000-by-1500-by-3, Z is size N-by-N (i.e. we will only be modifying the first page of X), L is an N-by-N logical array, and the row and column indices are randomly chosen:
X = randi([0 255], 2000, 1500, 3, 'uint8');
N = 20; % Submatrix size
Z = randi([0 255], N, N, 'uint8');
L = (rand(N, N) > 0.5);
row1 = randi([1 2000-N]);
row2 = row1+N-1
col1 = randi([1 1500-N]);
col2 = col1+N-1;
I then tested 3 different solutions: your original solution, a solution using find and sub2ind to create a linear index for X, and a solution that creates a logical index for X:
% Original solution:
Y = X(row1:row2, col1:col2, 1);
Y(L) = Z(L);
X(row1:row2, col1:col2, 1) = Y;
% Linear index solution:
[rIndex, cIndex] = find(L);
X(sub2ind(size(X), rIndex+row1-1, cIndex+col1-1)) = Z(L);
% Logical index solution
[R, C, ~] = size(X);
fullL = false(R, C);
fullL(row1:row2, col1:col2) = L;
X(fullL) = Z(L);
I tested these repeatedly with randomly-generated sample data using timeit and found that your original solution is consistently the fastest. The linear index solution is very close, but slightly slower. The logical index solution takes more than twice as long.
Let's define some example data:
X = randi(9,5,6);
Y = 10+X;
row1 = 2;
row2 = 4;
col1 = 3;
col2 = 4;
L = logical([0 1; 0 0; 1 1]);
Then:
ind_subm = bsxfun(#plus, (row1:row2).',size(X,1)*((col1:col2)-1));
% linear index for submatrix
ind_subm_masked = ind_subm(L);
% linear index for masked submatrix
X(ind_subm_masked) = Y(ind_subm_masked);
Example results:
X before:
X =
6 2 1 7 9 6
3 3 3 5 5 7
6 3 8 6 5 4
7 4 1 3 3 4
2 5 9 5 5 9
L:
L =
3×2 logical array
0 1
0 0
1 1
X after:
X =
6 2 1 7 9 6
3 3 3 15 5 7
6 3 8 6 5 4
7 4 11 13 3 4
2 5 9 5 5 9
I want to create matrix B from matrix A, with the following rules:
Non-diagonal elements A are like non-diagonal elements of B.
The main diagonal of A is the antidiagonal of B
The antidiagonal of A is the main diagonal of B.
For example:
A = [ 1 2 3 4;
7 8 9 10;
13 14 15 16;
19 20 21 22 ];
B = [ 4 2 3 1;
7 9 8 10;
13 15 14 16;
22 20 21 19 ];
How can I create B given A?
You can create all of the indices, then it's a single assignment.
% Get size of square matrix A
n = size(A,1);
% Indicies are 1:n^2 by default
idx = 1:n^2;
% Swap diagonal and antidiagonal indices
idx( [1:(n+1):n^2, n^2-n+1:1-n:n] ) = [n^2-n+1:1-n:n, 1:(n+1):n^2];
% Use the indexing array to create B from A, reshape to be n*n
B = reshape( A( idx ), n, n );
Output for your example A:
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
There are so many ways to reach that results, it is just an indexing exercise. Here is one (of the many) way to reach that result for any square matrix of size n:
%% input
A=[ 1 2 3 4 ;
7 8 9 10 ;
13 14 15 16 ;
19 20 21 22 ];
%% Calculate linear indices for the diagonal and antidiagonal
n=size(A,1) ;
idxdiag = 1:(n+1):n^2 ; % => idxdiag = [1 6 11 16]
idxantidiag = n:(n-1):n^2-1 ; % => idxantidiag = [4 7 10 13]
%% Generate B
B = A ; % start with a simple copy (for the non-diagonal elements)
% Method 1: direct indice assignment
B(idxdiag) = diag(fliplr(A)) ; % Assign diagonal elements of B
B(idxantidiag) = flipud(diag(A)) ; % Assign antidiagonal elements of B
% Method 2: summation
B([idxdiag idxantidiag]) = 0 ;
B = B + diag(diag(fliplr(A))) + fliplr(diag(diag(A))) ;
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
Both methods return exactly the same matrix B.
I suggest you familiarise yourself with the MATLAB function used to understand what is going on behind the scene:
fliplr
flipud
diag
and may be have a read at:
Matrix Indexing in MATLAB
I thought a little differently and came to a conclusion
A=[1 2 3 4;7 8 9 10;13 14 15 16; 19 20 21 22];;
n=size(A,1) ;
B=zeros(n,n) ;
for i=1:n
for j=1:n
if i==j
B(i,j)=A(i,n-i+1);
elseif j==n-i+1
B(i,j)=A(i,i);
else
B(i,j)=A(i,j);
end
end
end
B
Here's a variant using eye, find, and flip to generate linear indices:
ind1 = find(eye(size(A)));
ind2 = flip(find(flip(eye(size(A)))));
B = A;
B([ind1 ind2]) = B([ind2 ind1]);
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
And here's a variant of the above that uses just eye and flip to generate logical indices:
ind1 = eye(size(A), 'logical');
ind2 = flip(ind1);
B = A;
B(ind1) = flip(A(ind2));
B(ind2) = flip(A(ind1));
B =
4 2 3 1
7 9 8 10
13 15 14 16
22 20 21 19
If I have matrix size(mat)= X*Y*6
let call mat(:,:,1)=A
and mat(:,:,2)=B and so on
how do i rezise mat to X*Y*12
where
mat(:,:,1)=mat(:,:,2)= A
mat(:,:,3)=mat(:,:,4)=B
and so on
You can use the following syntax:
%defines input matrix (in your case it is already defined)
m = 500;
n = 400;
z = 6;
mat = rand(m,n,z);
%initialize output matrix
newMat = zeros(m,n,z*2);
%assign old matrix values into the new matrix
newMat(:,:,1:2:end) = mat;
newMat(:,:,2:2:end) = mat;
If you have Matlab 2015a or newer you can use repelem:
N = 2; %// number of times to repeat
result = repelem(mat, 1, 1, N); %// repeat N times along 3rd dim
For older Matlab versions you can do it manually as follows:
N = 2; %// number of times to repeat
ind = ceil(1/N:1/N:size(mat,3)); %// build index with repetitions
result = mat(:,:,ind); %// apply index along desired dim
Example:
>> %// Data
>> mat = randi(9,2,4,2)
mat(:,:,1) =
5 8 9 2
7 3 1 5
mat(:,:,2) =
5 7 1 1
1 8 8 2
>> %// First approach
>> N = 2;
>> result = repelem(mat, 1, 1, N)
result(:,:,1) =
5 8 9 2
7 3 1 5
result(:,:,2) =
5 8 9 2
7 3 1 5
result(:,:,3) =
5 7 1 1
1 8 8 2
result(:,:,4) =
5 7 1 1
1 8 8 2
>> %// Second approach
>> N = 2;
>> ind = ceil(1/N:1/N:size(mat,3));
>> result = mat(:,:,ind)
result(:,:,1) =
5 8 9 2
7 3 1 5
result(:,:,2) =
5 8 9 2
7 3 1 5
result(:,:,3) =
5 7 1 1
1 8 8 2
result(:,:,4) =
5 7 1 1
1 8 8 2
I have two different matrices A and B:
A =
[7 8 9;
4 5 6]
B =
[22 32 12;
9 8 10]
sortB =
[12 22 32;
8 9 10]
sortindex_B=[3 1 2;2 1 3];, i.e., 12 is in the third position of matrix B, 22 in first, and 32 in second position; similarly for the second row.
Now I want to sort A depending on Sortindex_B (i.e., in matrix A I want 7 as the third element, 8 as the first, and 9 as the second element of the first row; and similarly for the second row: 4 at the second, 5 at the first, and 6 as the third element). Hence the result should look like:
A_final =
[8 9 7;
5 4 6]
How can I achieve this?
You can ask for the sorting index matrix from sort command, when sorting B, and thereafter operate on A as a cell, in so making cellfun available:
A = [7 8 9; 4 5 6];
B = [22 32 12; 9 8 10];
[sortB, I] = sort(B,2);
Icell = mat2cell(I,ones(1, size(I,1)),size(I,2));
Acell = mat2cell(A,ones(1, size(I,1)),size(I,2));
sortA = cell2mat(...
cellfun(#(x,y) y(x), Icell, Acell, 'UniformOutput', false))
Output (you state first row output as 8 9 7, but did you really mean 9 7 8?)
sortA =
9 7 8
5 4 6
For sorting as specifically specified in your question; re-map index matrix I:
A = [7 8 9; 4 5 6];
B = [22 32 12; 9 8 10];
[sortB, I] = sort(B,2);
%// re-map I
for i = 1:size(I,1)
Itmp = I(i,:);
for j = 1:size(I,2)
I(i,Itmp(j)) = j;
end
end
Icell = mat2cell(I,ones(1, size(I,1)),size(I,2));
ImapCell = mat2cell(Imap,ones(1, size(I,1)),size(I,2));
Acell = mat2cell(A,ones(1, size(I,1)),size(I,2));
sortA = cell2mat(...
cellfun(#(x,y) y(x), Icell, Acell, 'UniformOutput', false))
Output
sortA =
8 9 7
5 4 6
Use a combination of sort as in dfri's answer and sub2ind:
A = [7 8 9;
4 5 6];
B = [22 32 12;
9 8 10];
[sortB, sortindex_B] = sort(B,2);
[~, colIdx] = sort(sortindex_B,2);
rowIdx = ndgrid(1:size(B,1),1:size(B,2));
idx = sub2ind(size(B),rowIdx,colIdx);
sortA = A(idx)
ans =
8 9 7
5 4 6
you will have to do this row by row using the index values that the sort function returns.
Something like this should do the trick and is expandable to any number of rows that your matrix A and B may have. This does also validate that A and B are the same size before it continues.
B= [22 32 12; 9 8 10]
A = [7 8 9; 4 5 6];
assert(all(size(A) == size(B)));
sortB = zeros(size(B));
finalA= zeros(size(A));
for i = 1:size(B,1)
[sorted,idx] = sort(B(i,:));
sortB(i,:) = sorted;
tempA = A(i,:);
tempA = tempA(idx);
finalA(i,:) = tempA;
end
There are many clever ways to do this, including this for loop. I hope the comments will explain the logic.
clear; %// input the sample data
A = [7 8 9; 4 5 6];
B = [22 32 12; 9 8 10];
sortB = [12 22 32; 8 9 10];
%// loop through every element in B
[R C]=size(B);
for i=1:C
for j=1:R
%// Where does A(j,i) need to go in Afinal?
%// It needs to go in the j-th row, and in
%// whatever column of B(j,:) equals sortB(j,i).
Afinal( j , find( B(j,:) ==sortB(j,i)) ) = A(j,i);
end
end
And the result:
>> Afinal
Afinal =
8 9 7
5 4 6
Example:
input = [1 255 0 0 0 9 9 9 1 6 6 6 6 6 6 1]; % array of numbers (uint8)
output = [1 255 0 0 0 255 9 9 9 255 1 6 6 6 255 6 6 6 255 1];
% output must have 255 inserted at positions 6, 10, 15, 19
% because 0, 9, 6, 6 have occurred three times respectively
outputIndex = [6 10 15 19];
% outputIndex must indicate the positions where 255 was inserted
This could be one vectorized approach to get things done efficiently -
%// Input
A = [1 255 0 0 0 9 9 9 1 6 6 6 6 6 6 1]
%// Input paramter (how many times a value must be repeated for detection)
search_count = 3;
%// Find difference between consecutive elemnts and set all non zero
%// differences as ones, otherwise as zeros in a binary array
diffA = diff(A)~=0
%// Find start and end indices of "islands" of same value
starts = strfind([1 diffA],[1 zeros(1,search_count-1)])
ends = strfind([diffA 1],[zeros(1,search_count-1) 1])+search_count
%// For each island of same valued elements, find out where first group ends
firstgrp = starts + search_count
%// Find how many times a group of that same value of search_count times repeats
%// within each "island" of same valued elements. Also get the max repeats.
pattern_repeats = floor((ends - starts)./search_count)
max_repeat = max(pattern_repeats)
%// All possible repeat indices within all islands
all_repeats = bsxfun(#plus,firstgrp,[0:max_repeat-1]'*(search_count)) %//'
%// Use a binary mask to select only those repeats allowed with pattern_repeat
out_idx = all_repeats(bsxfun(#lt,[0:max_repeat-1]',pattern_repeats)) %//'
out_idx = out_idx + [0:numel(out_idx)-1]' %//'
%// Create output arary, insert 255 at out_idx locations and put values
%// from input array into rest of the locations
out = zeros(1,numel(A)+numel(out_idx));
out(out_idx) = 255
out(out==0) = A
Code run -
>> A
A =
Columns 1 through 13
1 255 0 0 0 9 9 9 1 6 6 6 6
Columns 14 through 16
6 6 1
>> out_idx
out_idx =
6
10
15
19
>> out
out =
Columns 1 through 13
1 255 0 0 0 255 9 9 9 255 1 6 6
Columns 14 through 20
6 255 6 6 6 255 1
I don't understand the downvotes, it's actually an interesting question.
Here the long answer:
n = 3;
subst = 255;
input = [1 255 0 0 0 9 9 9 1 6 6 6 6 6 6 61];
%// mask
X = NaN(1,numel(input));
%// something complicated (see below)
X(filter(ones(1,n-1),1,~([0. diff(input)])) == n-1) = 1;
%// loop to split multiple occurences of n-tuples
for ii = 1:numel(input)
if X(ii) == 1 && ii < numel(X)-n+1
X(ii+1:ii+n-1) = NaN(1,n-1);
end
end
%// output vector
D = [input; X.*subst];
E = D(:);
output = E(isfinite(E))
%// indices of inserted 255
D = [input.*0; X.*subst];
E = D(:);
outputIndex = find(E(isfinite(E)))
Explanation of the complicated part:
%// finite differences of input
A = [0 diff(input)];
%// conversion to logical
B = ~A;
%// mask consecutive values
mask = filter(ones(1,n-1),1,B) == n-1;
%// set masked values to 1
X(mask) = 1;
If you have the image processing toolbox you can save the loop with this fancy oneliner for getting the mask:
mask = accumarray(bwlabel(filter(ones(1,n-1),1,~([0. diff(input)])) == n-1).'+1,1:numel(input),[],#(x) {getfield(sort(x),{find(mod(cumsum(1:numel(x)),n) == 1)})});
X = NaN(1,numel(input));
X(vertcat(mask{2:end})) = subst;
%// output vector
D = [input; X];
E = D(:);
output = E(isfinite(E))
%// indices of inserted 255
D = [input.*0; X];
E = D(:);
outputIndex = find(E(isfinite(E)))