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.
CodePudding user response:
The cellstr
function is your friend, since it converts all of the following to a cell array of chars:
- chars e.g.
cellstr( 'abc' ) => {'abc'}
- cells of chars e.g.
cellstr( {'abc','def'} ) => {'abc','def'}
- strings e.g.
cellstr( "abc" ) => {'abc'}
- string arrays e.g.
cellstr( ["abc", "def"] ) => {'abc','def'}
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.