10

Suppose that C is a cell array with shape M × 1 (i.e., size(C) returns [M 1]), and that each element of C is in turn a cell array with shape 1 × N.

I often want to convert such a cell array to a new cell array D having shape 1 × N, with elements being cell arrays with shape M × 1, and such that C{i}{j} equals D{j}{i} for all 0 < i &leq; M, and 0 < j &leq; N.

I use the following monstrosity for this

D = arrayfun(@(j) arrayfun(@(i) C{i}{j}, (1:M)', 'un', 0), 1:N, 'un', 0);

but the need for this operation arises often enough (after all, it's sort of a "cell array transpose") that I thought I'd ask:

is there a more standard way to do this operation?

Note that the desired D is different from

E = cat(2, C{:});

or, equivalently,

E = cat(1, D{:});

The E above is a two-dimensional (M × N) cell array, whereas both C and D are one-dimensional cell arrays of one-dimensional cell arrays. Of course, conversion of E back to either C or D is also another often needed operation (this sort of thing is never-ending with MATLAB), but I'll leave it for another post.


The motivation behind this question goes far beyond the problem described above. It turns out that a huge fraction of my MATLAB code, and an even larger fraction of my MATLAB programming time and effort, are devoted to this essentially unproductive chore of converting data from one format to another. Of course, format conversion is unavoidable when doing any kind of computational work, but with MATLAB I find myself doing it a lot more, or at least having to work a lot harder at it, than when I work in other systems (e.g., Mathematica or Python/NumPy). My hope is that by building up my repertoire of MATLAB "format conversion tricks" I will be able to bring down to a more reasonable level the fraction of my MATLAB programming time that I have to devote to format conversion.


P.S. The following code constructs a C like the one described above, for M = 5 and N = 2.

uc = ['A':'Z'];
randstr = @() uc(randi(26, [1 4]));
M = 5;
rng(0);  % keep example reproducible
C = arrayfun(@(i) {randstr() i}, 1:M, 'un', 0)';

% C = 
%     {1x2 cell}
%     {1x2 cell}
%     {1x2 cell}
%     {1x2 cell}
%     {1x2 cell}
% >> cat(1, C{:});
% ans = 
%     'VXDX'    [1]
%     'QCHO'    [2]
%     'YZEZ'    [3]
%     'YMUD'    [4]
%     'KXUY'    [5]
%     

N = 2;
D = arrayfun(@(j) arrayfun(@(i) C{i}{j}, (1:M)', 'un', 0), 1:N, 'un', 0);

% D = 
%     {5x1 cell}    {5x1 cell}
horchler
  • 18,384
  • 4
  • 37
  • 73
kjo
  • 33,683
  • 52
  • 148
  • 265
  • +1 for well formulated answer and reproducible code. I don't think the double-`arrayfun` approach is so much of a monstrosity – Luis Mendo Feb 17 '14 at 17:22
  • @LuisMendo: imagine if you had to write something like that every time you needed a matrix transpose. It's a monstrosity for me, because I need this operation so often. Of course, I may be able to write a function to facilitate things, but before doing this, I thought I'd ask to see if there's already a suitable built-in alternative. – kjo Feb 17 '14 at 17:26
  • @kjo: Is this just an intermediate step and your actual problem is how to transpose your cell array `C` to a 1-by-5 cell containing five 2-by-1 cells? Also perhaps a different data structure is called for. Maybe a `struct`? – horchler Feb 17 '14 at 17:43
  • @horchler: as I said in my small-print, this is a recurring situation; it arises in many diverse contexts. At some earlier time the transposition problem you mention may have been one of such contexts, but not at the moment. – kjo Feb 17 '14 at 18:01
  • 1
    @kjo I agree with you on that. But a cell array of cell arrays is a slightly convoluted structure, so it seems normal to me that you need such a complicate expression to "transpose" it. It's not transpose, it's more to turn it inside out, so to speak. If you had a 2D cell array instead of nested cell arrays you could simply use tranpose – Luis Mendo Feb 17 '14 at 20:12

1 Answers1

3

Here's a little trick using num2cell, which actually works with cell array inputs – the key is to first expand C into a 5-by-2 cell array (equivalent to cell(5,2)).

% Your example to produce C
uc = ['A':'Z'];
randstr = @() uc(randi(26, [1 4]));
M = 5;
rng(0);  % Keep example reproducible
C = arrayfun(@(i) {randstr() i}, 1:M, 'un', 0)';

% D = num2cell(reshape([C{:}],[N M]).',[1 M])
D = num2cell(reshape([C{:}],[size(C{1},2) size(C,1)]).',[1 size(C,1)])

or more simply

D = num2cell(cat(1,C{:}),1)

where D{:} returns:

ans = 

    'VXDX'
    'QCHO'
    'YZEZ'
    'YMUD'
    'KXUY'


ans = 

    [1]
    [2]
    [3]
    [4]
    [5]

The inverse operation to go from D back to C can be accomplished via:

% C = num2cell(reshape([D{:}],[N M]),[M N])
C = num2cell(reshape([D{:}],[size(D{1},1) size(D,2)]),[size(D,2) size(D{1},1)])

or

C = num2cell(cat(2,D{:}),2)

Thus you might be able to create a function like the following that would work in either direction:

transpose_cells = @(C)num2cell(cat(isrow(C)+1,C{:}),isrow(C)+1);
isequal(transpose_cells(transpose_cells(C)),C)
horchler
  • 18,384
  • 4
  • 37
  • 73