ttsesm
ttsesm

Reputation: 947

How to efficiently get a multivalue diagonal of a non-square matrix?

Imaging that I have the following matrix A(5x20):

    A = [6.4475 3.3487 Inf 6.4940 6.5480 3.4857 Inf 6.5030 6.5035 3.5732 Inf 6.4951 6.6372 3.3639 Inf 6.5371 Inf 4.4444 Inf Inf;
         6.6761 2.3999 Inf 6.8620 6.5776 2.1963 Inf 6.8535 6.8733 3.1184 Inf 6.8688 6.5918 2.2900 Inf 6.8656 Inf 4.4667 Inf Inf;
         5.9808 4.1965 Inf 5.8950 6.2467 4.5779 Inf 5.9116 5.9198 4.0071 Inf 5.8938 6.3981 4.3019 Inf 5.9570 Inf 4.3433 Inf Inf;
         5.7438 1.5369 Inf 5.9868 5.5404 1.6374 Inf 5.9537 6.0332 2.1704 Inf 6.0024 5.5235 1.5035 Inf 5.9397 Inf 3.5727 Inf Inf;
         4.0544 3.8845 Inf 4.1466 4.6749 4.6560 Inf 4.3833 3.7550 3.0591 Inf 4.0693 4.9586 4.1296 Inf 4.6826 Inf 2.5593 Inf Inf];

then I would like to extract the values in the following way:

enter image description here

so my output matrix is:

B = [6.4475 3.3487 Inf 6.4940;
     6.5776 2.1963 Inf 6.8535;
     5.9198 4.0071 Inf 5.8938;
     5.5235 1.5035 Inf 5.9397;
     Inf 2.5593 Inf Inf]

Upvotes: 0

Views: 60

Answers (2)

Tommaso Belluzzo
Tommaso Belluzzo

Reputation: 23675

This should work fine:

% Define the data sample...
A = [
  6.4475 3.3487 Inf 6.4940 6.5480 3.4857 Inf 6.5030 6.5035 3.5732 Inf 6.4951 6.6372 3.3639 Inf 6.5371 Inf 4.4444 Inf Inf;
  6.6761 2.3999 Inf 6.8620 6.5776 2.1963 Inf 6.8535 6.8733 3.1184 Inf 6.8688 6.5918 2.2900 Inf 6.8656 Inf 4.4667 Inf Inf;
  5.9808 4.1965 Inf 5.8950 6.2467 4.5779 Inf 5.9116 5.9198 4.0071 Inf 5.8938 6.3981 4.3019 Inf 5.9570 Inf 4.3433 Inf Inf;
  5.7438 1.5369 Inf 5.9868 5.5404 1.6374 Inf 5.9537 6.0332 2.1704 Inf 6.0024 5.5235 1.5035 Inf 5.9397 Inf 3.5727 Inf Inf;
  4.0544 3.8845 Inf 4.1466 4.6749 4.6560 Inf 4.3833 3.7550 3.0591 Inf 4.0693 4.9586 4.1296 Inf 4.6826 Inf 2.5593 Inf Inf
];

% Define the script parameters...
k = 4;
n = size(A,2) / k;

% Create the auxiliary matrix for indexing...
aux = repmat({ones(1,k)},1,n);
idx = blkdiag(aux{:});

% Extract the elements using the indexing and properly reshape the result...
B = reshape(A(logical(idx)),k,size(A,1)).'

The final output is:

B =
    6.4475  3.3487  Inf  6.4940
    6.5776  2.1963  Inf  6.8535
    5.9198  4.0071  Inf  5.8938
    5.5235  1.5035  Inf  5.9397
       Inf  2.5593  Inf     Inf

Upvotes: 1

Bentoy13
Bentoy13

Reputation: 4956

I assume that the number of elements per line is a parameter k.

In one line, you can retrieve the elements like this:

B = A(sub2ind(size(A), repmat((1:size(A,1)).', 1, k), (1:k)+((1:k:size(A,2))-1).'));

Let's explain a little. You create the indexes of the elements you want to retrieve and convert them into linear indexing with sub2ind. Line indexes are easy to create with repmat.

For column indexing, one solution is to create a matrix with elements being the column indexes. Here I choose a long version:

(1:k)+((1:k:size(A,2))-1).'

It enables you to skip or duplicate elements (depending on size(A,2) being a multiple of k or not). Note that this construct works in recent Matlab versions, in old versions, you nned to call bsxfun(@plus, 1:k, ((1:k:size(A,2))-1).')

If you are sure to take exactly one element per column in the result, let's do it more efficiently: reshape(1:size(A,2), k,[]).'

Note that it does not take care of odd sizes of input matrix (if you have more rows for example).

Upvotes: 1

Related Questions