如何根据每一行将矩阵转换为一堆对角矩阵?

时间:2015-08-18 16:59:57

标签: matlab matrix vectorization

我有一个矩阵:

A = [1 1 1
     2 2 2
     3 3 3]

是否有矢量化的获取方式:

B = [1 0 0 
     0 1 0
     0 0 1
     2 0 0 
     0 2 0
     0 0 2
     3 0 0 
     0 3 0
     0 0 3]

5 个答案:

答案 0 :(得分:5)

这是一个使用modsub2ind的解决方案:

%// example data
data = reshape(1:9,3,3).' %'
n = 3;  %// assumed to be known

data =

     1     2     3
     4     5     6
     7     8     9
%// row indices
rows = 1:numel(data);
%// column indices
cols = mod(rows-1,n) + 1;
%// pre-allocation
out = zeros(n*n,n);
%// linear indices
linIdx = sub2ind(size(out),rows,cols);
%// assigning
out(linIdx) = data.'
out =

     1     0     0
     0     2     0
     0     0     3
     4     0     0
     0     5     0
     0     0     6
     7     0     0
     0     8     0
     0     0     9

或者如果您更喜欢保存代码行,而不是可读性:

out = zeros(n*n,n);
out(sub2ind(size(out),1:numel(data),mod((1:numel(data))-1,n) + 1)) = data.'

另外两种快速解决方案,但速度不快于其他解决方案:

%// #1
Z = blockproc(A,[1 size(A,2)],@(x) diag(x.data));

%// #2
n = size(A,2);
Z = zeros(n*n,n);
Z( repmat(logical(eye(n)),n,1) ) = A;

为了竞争 - 基准

function [t] = bench()
    A = magic(200);

    % functions to compare
    fcns = {
        @() thewaywewalk(A);
        @() lhcgeneva(A);
        @() rayryeng(A);
        @() rlbond(A);
    };

    % timeit
    t = zeros(4,1);
    for ii = 1:10;
        t = t + cellfun(@timeit, fcns);
    end
    format long
end

function Z = thewaywewalk(A) 
    n = size(A,2);
    rows = 1:numel(A);
    cols = mod(rows-1,n) + 1;
    Z = zeros(n*n,n);
    linIdx = sub2ind(size(Z),rows,cols);
    Z(linIdx) = A.';
end
function Z = lhcgeneva(A) 
    sz = size(A);
    Z = zeros(sz(1)*sz(2), sz(2));
    for i = 1 : sz(1)
        Z((i-1)*sz(2)+1:i*sz(2), :) = diag(A(i, :));
    end
end
function Z = rayryeng(A)  
    A = A.';
    Z = full(sparse(1:numel(A), repmat(1:size(A,2),1,size(A,1)), A(:)));
end
function Z = rlbond(A)  
    D = cellfun(@diag,mat2cell(A, ones(size(A,1), 1), size(A,2)), 'UniformOutput', false);
    Z = vertcat(D{:});
end
ans =

   0.322633905428601  %// thewaywewalk
   0.550931853207228  %// lhcgeneva
   0.254718792359946  %// rayryeng - Winner!
   0.898236688657039  %// rlbond

答案 1 :(得分:5)

以下是使用sparserepmat的另一种方式:

A = [1 2 3; 4 5 6; 7 8 9];
A = A.';
B = full(sparse(1:numel(A), repmat(1:size(A,1),1,size(A,2)), A(:)));

原始矩阵在A中,我将其转置,以便我可以正确地展开每个矩阵的行以进行下一步。我使用sparse来声明矩阵中的非零值。具体来说,我们看到每行只有一个条目,因此行索引的范围应为1到A中的条目数。列从1到最后一列波动并重复。 mod肯定是通过thewaywewalk解决方案的方式,但我想使用repmat,这是他的方法的独立解决方案。因此,我们创建了一个用于访问列的向量,这些列从1到尽可能多的列,并且我们重复这个行以获得尽可能多的行。这些行和列索引向量将决定非零位置的出现位置。最后,进入每个非零位置的是A以行主要顺序展开的元素,遵循行和列索引向量指示的顺序。

请注意,在repmat调用中,由于转置操作,调用size时的行和列会被反转。

结果如下:我们得到:

>> B

B =

     1     0     0
     0     2     0
     0     0     3
     4     0     0
     0     5     0
     0     0     6
     7     0     0
     0     8     0
     0     0     9

鉴于上述问题的稀疏性,将矩阵保留为sparse形式可能会更快,并且只有在必要时才使用full进行转换。将花费时间在两种格式之间进行转换,因此如果您决定进行基准测试,请考虑这一点。

答案 2 :(得分:2)

编辑:我想thewaywewalk的基准测试只留下了一个可读性参数;)

使用Beaker的建议进行编辑:

data = [1 1 1
     2 2 2
     3 3 3];
sz = size(data);
z = zeros(sz(1)*sz(2), sz(2));
for i = 1 : sz(1)
    z((i-1)*sz(2)+1:i*sz(2), :) = diag(data(i, :));
end

答案 3 :(得分:1)

另一种选择,(对我来说)比上面的任何基准测试方法(对于大型和小型矩阵)都快

[m,n] = size(A);
Z(n,m*n) = 0;
for idx = 1:n
     Z(idx,((idx-1)*m+1):(idx*m)) = A(:,idx);
end
Z = reshape(Z,m*n,n);

答案 4 :(得分:0)

此代码将A转换为行向量的单元格数组,将diag函数应用于每个,然后将它们堆叠起来:

D = cellfun(@diag,mat2cell(A, ones(size(A,1), 1), size(A,2)), 'UniformOutput', false);
B = vertcat(D{:});

结果:

B =

     1     0     0
     0     1     0
     0     0     1
     2     0     0
     0     2     0
     0     0     2
     3     0     0
     0     3     0
     0     0     3