Search code examples
stringmatlabstructmatlab-struct

How to remove structs from Matlab array for specific struct.field values


Everytime I try editing arrays of structs by field, I discover I really need to take a few weeks and try to really learn Matlab. Right now, I have an array of structs, where each struct has fields along the line of:

x.fruit, x.color, x.season, x.source, x.flibbertigibbet

each of these fields is a string. I also have an cell array of strings:

y = {'apple', 'banana', 'palm of granite'}

I would like to remove all struct where x.fruit is in y (e.g. x.fruit == 'apple'), but can't seem to find a way to do this other than by looping through y.

I was hoping for something along the lines of:

bad_idx = [x(:).fruit in y];
x(bad_idx) = [];

Is this doable? Is there someway to use cellfun to do this?


Solution

  • If each element of x only contains a string for the fruit field, you can easily do this the following way.

    toremove = ismember({x.fruit}, 'apple')
    x(toremove) = [];
    

    Or more briefly

    x = x(~ismember({x.fruit}, 'apple'));
    

    The {x.fruit} syntax combines all of the values of fruit for each struct into a cell array. You can then use ismember on the cell array of strings to compare each one to 'apple'. This will yield a logical array the size of x that can be used to index into x.

    You could also use something like strcmp instead of ismember above.

    x = x(~strcmp({x.fruit}, 'apple'));
    

    Update

    If each x(k).fruit contains a cell array, then you can use an approach similar to the above approach combined with cellfun.

    x(1).fruit = {'apple', 'orange'};
    x(2).fruit = {'banana'};
    x(3).fruit = {'grape', 'orange'};
    
    x = x(~cellfun(@(fruits)ismember('apple', fruits), {x.fruit}));
    
    %// 1 x 2 struct array with fields: 
    %//     fruit
    

    If you want to check for multiple types of fruit to remove at once, you could do something like.

    %// Remove if EITHER 'apple' or 'banana'
    tocheck = {'apple', 'banana'};
    x = x(~cellfun(@(fruits)any(ismember({'apple', 'banana'}, fruits)), {x.fruit}));
    
    %// Remove if BOTH 'apple' and 'banana' in one
    x = x(~cellfun(@(fruits)all(ismember({'apple', 'banana'}, fruits)), {x.fruit}));