如何将元胞数组展开为列向量?

问题描述

我有一个单元格数组,其中每个单元格都是一个不同大小的矩阵。我想将所有矩阵的每个元素连接成一个列向量。 所以

X1=rand(2,3);  % Total 6 elements.
X2=rand(3,4);  % Total 12 elements.
X = {X1,X2};  % Total 18 elements in a 2-cell array.

% How to unroll everything from X into one giant column vector of size 18x1 ?

% Edit: The above example only shows two matrices,X1 and X2,but there Could be n such matrices in the cell array.
X = {X1,X2,...,Xn};

我可以用循环来做到这一点,但很好奇是否有更快的方法。我查看了 cell2mat 并重塑,但无法让他们这样做(尺寸不匹配错误)。在网络上搜索似乎没有帮助。

这是我的 for 循环解决方案:

unrolled_X=[];
for i=1:length(X)
  unrolled_X = [unrolled_X; X{i}(:)];
end

编辑 2:感谢您的回答。我学到了一些关于性能的新东西。我对@HansHirse、@lucien-xhh 和@wolfie 的 3 个解决方案进行了基准测试。结果有点出人意料。注意我实际上运行的是 Octave(版本 5.2.0。)。

所以没有 cell2fun 的解决方案是最快的。其他 2 个解决方案都使用 cellfun,但出人意料地接近最快,而另一个是最快的两倍。代码和结果如下。

代码


function run_benchmarks()
  X={};
  for i=1:5
    X{i}=rand(1000,1000);
  end
  
  fprintf("unroll_with_cellfun: %f\n",benchmark(@()unroll_with_cellfun(X),100));
  fprintf("unroll_with_cellfun2: %f\n",benchmark(@()unroll_with_cellfun2(X),100));
  fprintf("unroll_with_vertcat: %f\n",benchmark(@()unroll_with_vertcat(X),100));
  
end

function unrolled_X = unroll_with_cellfun(X)
  unrolled_X = cell2mat(cellfun(@(x) x(:),X,'UniformOutput',false).');
end

function unrolled_X = unroll_with_cellfun2(X)
  unrolled_X = cell2mat(cellfun(@(x) x(:).',false)).';
end

function unrolled_X = unroll_with_vertcat(X)
  unrolled_X = cell(length(X),1);
  for ii = 1:length(X)
    unrolled_X{ii} = X{ii}(:);
  end
  unrolled_X = vertcat( unrolled_X{:} );
end


function elapsed_time_in_seconds = benchmark(f,N)
  % benchmark runs the function 'f' N times and returns the elapsed time in seconds.

  timeid = tic;
  for i=1:N
    output = f();
  end
  elapsed_time_in_seconds = toc(timeid);
end

结果:

octave:161> run_benchmarks
unroll_with_cellfun: 1.240324
unroll_with_cellfun2: 0.606957   <-- Close to fastest.
unroll_with_vertcat: 0.597657    <-- FASTEST

惊讶地发现 cellfun2 几乎与最快的解决方案相同,而且即使它与 cellfun2 几乎相同,cellfun 也需要 2 倍的时间。

解决方法

预分配循环将提高性能和更好的实践

unrolled_X = cell(length(X),1);
for ii = 1:length(X)
  unrolled_X{ii} = X{ii}(:);
end
unrolled_X = vertcat( unrolled_X{:} );

任何像 cellfun 这样的简写基本上都是这个变相的循环,而 cell2mat 使用循环在幕后进行连接,但有额外的检查,因此实际上可能会导致轻微的减速。

,

您可以使用 cellfun 使用 anonymous function 来展平所有矩阵。然后,将修改后的元胞数组作为“列向量”提供给cell2mat,即预先转置修改后的元胞数组。

这是一个示例,其中所有内容都变成了单行(使用 MATLAB Online 测试):

X1 = rand(2,3);
X2 = rand(3,4);
X3 = rand(1,5);
X = {X1,X2,X3}

unrolled_X = cell2mat(cellfun(@(x) x(:),X,'UniformOutput',false).')

一些示例输出(使用 Octave 6.1.0 生成):

X =
{
  [1,1] =
     0.2781   0.3303   0.7424
     0.3314   0.4878   0.6254

  [1,2] =
     0.567344   0.848374   0.035421   0.171656
     0.359233   0.482265   0.327617   0.188834
     0.088272   0.771683   0.763845   0.181979

  [1,3] =
     0.9843   0.7817   0.9399   0.5453   0.3310
}

unrolled_X =

   0.278085
   0.331438
   0.330314
   0.487774
   0.742395
   0.625360
   0.567344
   0.359233
   0.088272
   0.848374
   0.482265
   0.771683
   0.035421
   0.327617
   0.763845
   0.171656
   0.188834
   0.181979
   0.984326
   0.781678
   0.939857
   0.545296
   0.331043

实际上,将 cellfun 与匿名函数一起使用有点像伪装的循环,但它应该仍然比循环更有效,因为您要附加到数组。

,

一种解决方案:尝试 X = {[X1(:); X2(:)]} ,然后使用 cell2mat

两种解决方案:

clear
X1 = rand(2,3);  
X2 = rand(3,4);
X3 = rand(4,X3};
XX = cellfun(@(x) x(:)',false);
cell2mat(XX)
,

如果您使用的是 Octave,则可以使用 cellindexmat

unrolled_X = vertcat(cellindexmat(X,':'){:});