Search code examples
matlabcell

Merging elements of different cells


Suppose, we have a cell array consisting of ids and one attribute, e.g.

A{1,1}=[1 2;2 4]
A{1,2}=[2 3 5;8 5 6] 

Now, I'd like to have a final output consisting of unique ids of two cells (first row values) and corresponding columns have attribute value of each cell separately. i.e.

C = 
[1]    [         2]
[2]    [1x2 double]  % 4 in first cell and 8 in second cell
[3]    [         5]
[5]    [         6]

it seems that it's not possible to use something like C=[unique(A{1,:}(1,:)')]. Any help is greatly appreciated.


Solution

  • Assuming that each cell has two rows and a variable amount of columns where the first row is the ID and the second row is an attribute, I'd consolidate all of the cells into a single 2D matrix and use accumarray. accumarray is very suitable here because you want to group values that belong to the same ID together and apply a function to it. In our case, our function will simply place the values in a cell array and we'll make sure that the values are sorted because the values that are grouped by accumarray per ID come into the function in random order.

    Use cell2mat to convert the cells into a 2D matrix, transpose it so that it's compatible for accumarray, and use it. One thing I'll need to note is that should any IDs be missing, accumarray will make this slot empty. What I meant by missing is that in your example, the ID 4 is missing as there is a gap between 3 and 5 and also the ID 6 between 5 and 7 (I added the example in your comment to me). Because the largest ID in your data is 7, accumarray works by assigning outputs from ID 1 up to ID 7 in increments of 1. The last thing we would need to tackle is to eliminate any empty cells from the output of accumarray to complete the grouping.

    BTW, I'm going to assume that your cell array consists of a single row of cells like your example.... so:

    %// Setup
    A{1,1}=[1 2;2 4];
    A{1,2}=[2 3 5;8 5 6];
    A{1,3}=[7;8];
    
    %// Convert row of cell arrays to a single 2D matrix, then transpose for accumarray
    B = cell2mat(A).';
    
    %// Group IDs together and ensure they're sorted
    out = accumarray(B(:,1), B(:,2), [], @(x) {sort(x)});
    
    %// Add a column of IDs and concatenate with the previous output
    IDs = num2cell((1:numel(out)).');
    out = [IDs out];
    
    %// Any cells from the grouping that are empty, eliminate
    ind = cellfun(@isempty, out(:,2));
    out(ind,:) = [];
    

    We get:

    out = 
    
        [1]    [         2]
        [2]    [2x1 double]
        [3]    [         5]
        [5]    [         6]
        [7]    [         8]
    
    >> celldisp(out(2,:))
    
    ans{1} =
    
         2         
    
    ans{2} =
    
         4
         8
    

    If you'd like this done on a 2D cell array, where each row of this cell array represents a separate instance of the same problem, one suggestion I have is to perhaps loop over each row. Something like this, given your example in the comments:

    %// Setup
    A{1,1}=[1 2;2 4];
    A{1,2}=[2 3 5;8 5 6];
    A{1,3}=[7;8];
    A{2,1}=[1 2;2 4]; 
    A{2,2}=[1;7];
    
    %// Make a cell array that will contain the output per row
    out = cell(size(A,1),1);
    
    for idx = 1 : size(A,1)
         %// Convert row of cell arrays to a single 2D matrix, then transpose for accumarray
          B = cell2mat(A(idx,:)).';
    
          %// Group IDs together and ensure they're sorted
          out{idx} = accumarray(B(:,1), B(:,2), [], @(x) {sort(x)});
    
          %// Add a column of IDs and concatenate with the previous output
          IDs = num2cell((1:numel(out{idx})).');
          out{idx} = [IDs out{idx}];
    
          %// Any cells from the grouping that are empty, eliminate
          ind = cellfun(@isempty, out{idx}(:,2));
          out{idx}(ind,:) = [];
    end
    

    We get:

    >> out{1}
    
    ans = 
    
        [1]    [         2]
        [2]    [2x1 double]
        [3]    [         5]
        [5]    [         6]
        [7]    [         8]
    
    >> out{2}
    
    ans = 
    
        [1]    [2x1 double]
        [2]    [         4]
    
    >> celldisp(out{1}(2,:))
    
    ans{1} =
    
         2
    
    ans{2} =
    
         4
         8
    
    >> celldisp(out{2}(1,:))
    
    ans{1} =
    
         1
    
    ans{2} =
    
         2
         7