Wauzl
Wauzl

Reputation: 971

Concatenating (sub)fields of structs in a cell array

I have a Matlab object, that is a cell array containting structs that have almost identical structures and I want to programmatically get a (sub)field of the structs of all cell array elements.

For example, we take test

test = {struct('a',struct('sub',1)), struct('a',struct('sub',2),'b',1)};

This will create a cell array with the following structure:

cell-element 1: a --> sub --> 1
cell-element 2: a --> sub --> 2
                  \-> b --> 1

It can be seen that the elements of test don't have the exact same structure, but similar. How can I get all values of the a.sub fields of the elements of the cell. I can obtain them in this specific problem with

acc=zeros(1,numel(test));
for ii=1:numel(test)
   acc(ii) = test{ii}.a.sub;
end

but I can't quite get this method to work in a more general context (ie. having different fields).

Upvotes: 2

Views: 997

Answers (3)

user2271770
user2271770

Reputation:

You may want to use the function getfield:

%//Data to play with
test = {struct('a',struct('sub',1)), struct('a',struct('sub',2),'b',1)};

%//I'm interested in these nested fields
nested_fields = {'a', 'sub'};

%//Scan the cell array to retrieve the data array
acca = cellfun(@(x) getfield(x, nested_fields{:}), test);

In case your data cannot guarantee that all the elements are the same type and size, then you need to output a cell array instead:

%//Scan the cell array to retrieve the data cell array
accc = cellfun(@(x) getfield(x, nested_fields{:}), test, 'UniformOutput', false);

Later Edit

If one wants to use different sets of nested fields for each cell element then:

%//nested_fields list should have the same size as test
nested_fields = {{'a','sub'}, {'b'}};
accm = cellfun(@(x,y) getfield(x,y{:}), test, nested_fields, 'UniformOutput', false);

Upvotes: 4

dfrib
dfrib

Reputation: 73206

Edit: No need for recursion, as shown by @CST-link:s answer; the native getfield function can neatly unfold a cell array of fields as its second argument, e.g. getfield(foo{i}, fields{:}) instead of the call to the recursive function in my old answer below. I'll leave the recursive solution below, however, as it could have some value in the context of the question.


You can build you own recursive version of getField, taking a cell array of fields.

function value = getFieldRec(S,fields)
  if numel(fields) == 1 
    value = getfield(S, fields{1});
  else
    S = getfield(S,fields{1})
    fields{1} = [];
    fields = fields(~cellfun('isempty',fields));
    value = getFieldRec(S,fields);
  end
end

Example usage:

foo = {struct('a',struct('sub',1)), ...
  struct('a',struct('sub',2),'b',3), ...
  struct('c',struct('bar',7),'u',5)};
accessFields = {'a.sub', 'b', 'c.bar'};

values = zeros(1,numel(foo));
for i = 1:numel(foo)
  fields = strsplit(accessFields{i},'.');
  values(i) = getFieldRec(foo{i},fields);
end

With the following result

values =

     1     3     7

Upvotes: 1

Wauzl
Wauzl

Reputation: 971

I have found a way to do this using eval:

function out = catCellStructSubField(cellStruct, fieldName)
out = zeros(1,numel(cellStruct));
for ii = 1:numel(cellStruct)
   out(ii) = eval(['cellStruct{ii}.' fieldName]);
end

Where it can be used on my test example like this:

catCellStructSubField(test, 'a.sub')

Dynamic field names (cellStruct{ii}.(fieldName)) does not work because I'm accessing sub-fields.

I know eval is often a bad idea. I'm curious for different solutions.

Upvotes: 0

Related Questions