Search code examples
matlaboctave

How to unroll a cell array into a column vector?


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.


Solution

  • 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.