arraysstringmatlabcharcell-array

MATLAB Search Within a Cell Array of Cells


Setup:

I have a 21 x 3 cell array.

The first 2 columns are USUALLY strings or char arrays, but could be 1xn cells of strings or char arrays (if there are multiple alternate strings that mean the same thing in the context of my script). The 3rd element is a number.

I'm looking to return the index of any EXACT match of with a string or char array (but type doesn't have to match) contained in this cell array in column 1, and if column 1 doesn't match, then column 2.

I can use the following:

find(strcmp( 'example', celllist(:,1) ))

find(strcmp( 'example', celllist(:,2) ))

And these will match the corresponding indices with any strings / char arrays in the top level cell array. This won't, of course, match any strings that are inside of cells of strings inside the top level cell array.

Is there an elegant way to match those strings (that is, without using a for, while, or similar loop)? I want it to return the index of the main cell array (1 through 21) if the cells contains the match OR the cell within the cell contains an exact match in ANY of its cells.


Solution

  • The cellstr function is your friend, since it converts all of the following to a cell array of chars:

    Then you don't have to care about variable types, and can just do an ismember check on every element, which we can assume is a cell of chars.

    We can set up a test:

    testStr = 'example';
    arr = { 'abc', 'def', {'example','ghi'}, "jkl", "example" };
    % Expected output is [0,0,1,0,1]
    

    Doing this with a loop to better understand the logic would look like this:

    isMatch = false(1,numel(arr)); % initialise output
    for ii = 1:numel(arr)          % loop over main array
        x = cellstr(arr{ii});      % convert to cellstr
        isMatch(ii) = any( ismember( testStr, x ) ); % check if any sub-element is match
    end
    

    If you want to avoid loops* then you can do this one-liner instead using cellfun

    isMatch = cellfun( @(x) any( ismember( testStr, cellstr(x) ) ), arr );
    % >> isMatch = [0 0 1 0 1]
    

    So for your case, you could run this on both columns and apply some simple logic to select the one you want

    isMatchCol1 = cellfun( @(x) any( ismember( testStr, cellstr(x) ) ), arr(:,1) );
    isMatchCol2 = cellfun( @(x) any( ismember( testStr, cellstr(x) ) ), arr(:,2) );
    

    If you want the row index instead of a logical array, you can wrap the output with the find function, i.e. isMatchIdx = find(isMatch);.


    *This only avoids loops visually, cellfun is basically a looping device in disguise, but it does save us initialising the output at least.