2
votes

I have a cell array, where each cell is a matrix of different size. I want to concatenate every elements of all the matrices into one column vector. So

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};

I can do this with a loop, but curious if there is a faster way. I looked into cell2mat and reshape, but can't get them to do this (dimension mismatch errors). Searches on web don't seem to help.

This is my solution with a for loop:

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

Edit 2: Thanks for the answers. I learned something new about perf. I benchmarked the 3 solutions by @HansHirse, @lucien-xhh, and @wolfie. A bit surprising results. Note I am actually running Octave (version 5.2.0.).

So the solution without cell2fun was fastest. Other 2 solutions both use cellfun, but was surprisingly close to the fastest, while the other was double of the fastest. Code and results follow.

Code


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(:).', X, 'UniformOutput', 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

Results:

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

Surprised to see cellfun2 is almost same as the fastest solution, and that cellfun takes 2x time even it's almost same as cellfun2.

4
I suspect that the two cellfun answers might be giving different results, one interleaving the values. This would be the cause of the slowdown. Did you double-check the results? The solution interleaving the values wouldn’t work if the cell array had different-sized arrays in it. - Cris Luengo
Note that you X in the tests is a column array, whereas in Hans’ and Lucien’s answers it is a row array. - Cris Luengo
Also, did you “warm up” before doing the test? The first call to a function tends to be a lot slower than subsequent ones. - Cris Luengo
I just ran your benchmark in MATLAB R2017a, "cellfun2" is almost 2x slower than "cellfun", and "vertcat" is more than 2x faster. In MATLAB the looping is optimized, and so the call to cellfun has a significant overhead. I guess the added .' operation adds to the time? -- But I don't see why "cellfun" would be slower than "cellfun2" in Octave. It makes no sense. - Cris Luengo
OK, I broke out Octave (v.5.2.0 on Linux), and ran your benchmark there. I see "cellfun" taking 2.28 s, "cellfun2" taking 2.07, and "vertcat" 2.04. "cellfun" is indeed slower, maybe caused by the transpose on the cell array? But it's not 2x, more like 1.1x. Also, you must have a really fast computer... - Cris Luengo

4 Answers

6
votes

Preallocating your loop will be more performant and better practise

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

Any shorthand like cellfun is basically this loop in disguise, and cell2mat uses a loop for concatenation under the hood but has additional checks so may actually cause a minor slow-down.

4
votes

You could use cellfun to flatten all matrices using an anonymous function. Then, feed the modified cell array as "column vector" to cell2mat, i.e. transposing the modified cell arry beforehand.

Here's an example, in which all that becomes a one-liner (tested with 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).')

Some examplary output (generated with 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

In fact, using cellfun with an anonymous function is something like a loop in disguise, but it should be still more efficient than your loop, since you're appending to an array.

3
votes

One Solution: Try X = {[X1(:); X2(:)]} , then use cell2mat

Two Solution:

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

If you are using Octave you can use cellindexmat:

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