What is the best practice to recursively extract data from a nested structure in matlab? - matlab

I'm trying to exctract some data from a nested structure in a recursive manner. First, I know that this has a field (values) which repeats itself inside the nested structure. Secondly I know that the structure which has those values has only structures as fields. In the below code I've tried to acces the structure.values by searching if my current structure has a field named values. If it has, I put .values at the end of my structure name. If it doesn't have this field, I verify if all the fields are structures. If they are, it means that I will have to consider them further and to extract the values from each one. If the fields are not structures, it means that they are values and I save them into a new simplified structure. Example of fields that I want: S.values.model1.values.mission.values.(alt/list). Currently, with the below code I'm only able to get the values from one field and then I get an error and don't know how to approach further.
Code example:
clear all
clc
S=struct()
S.case='1';
S.type='A';
S.values.model1.case='2'
S.values.model1.type='C'
S.values.model1.values.mission.case='3'
S.values.model1.values.mission.type='D'
S.values.model1.values.mission.values.alt='none'
S.values.model1.values.mission.values.list=2
S.values.model1.values.mission.values.parameter=4
S.values.model1.values.phase.case='4'
S.values.model1.values.phase.type='A'
S.values.model1.values.phase.values.num='all'
S.values.model1.values.phase.values.eq=2
S.values.model1.values.phase.values.unit=4
S.values.model1.values.analysis.case='1'
S.values.model1.values.phase.type='A'
S.values.model1.values.phase.values.nump1.list='all'
S.values.model1.values.phase.values.nump1.table='four'
S.values.model1.values.phase.values.nump1.mean=0
S.values.model1.values.phase.values.nump2.list='none'
S.values.model1.values.phase.values.nump2.table='three';
S.values.model1.values.phase.values.nump2.mean=1
s=S.values.model1;
names=fieldnames(s);
nnames=numel(names);
newStruct={};
[valsi,newstructi]=extractValues(names,s,nnames,newStruct)
function [vals,newStruct]=extractValues(names,vals,nnames,newStruct)
if any(strcmp(names,'values'))
vals=vals.('values');
names=fieldnames(vals)
nnames=numel(names)
[vals,newStruct]=extractValues(names,vals,nnames,newStruct);
end
for j=1:nnames
value(j)=isstruct((vals.(names{j})));
end
if all(value)
for k=1:nnames
vals=(vals.(names{k}));
names=fieldnames(vals);
nnames=numel(names);
[vals,newStruct]=extractValues(names,vals,nnames,newStruct);
end
else
for j=1:nnames
value=(vals.(names{j}));
newStruct.(names{j})=value;
end
end
end

As it is known beforehand what fields are requested you can arrange the subsequent filed names in a cell array and use a loop to extract the value:
names = {'values', 'model1', 'values', 'mission', 'values', 'alt'};
out = S;
for name : names
out = out.(name{1});
end
So that is a loop version of using:
out = S.values.model1.values.mission.values.alt;
EDIT:
If you want to list all field names and all field values you can used these functions:
function out = names(s, p)
if isstruct(s)
out = {};
f = fieldnames(s);
for i = 1:numel(f)
s1 = s.(f{i});
p1 = [p '.' f{i}];
out = [out; names(s1, p1)];
end
else
out = {p};
end
end
function out = values(s)
if isstruct(s)
out = {};
f = fieldnames(s);
for i = 1:numel(f)
out = [out; values(s.(f{i}))];
end
else
out = {s};
end
end
Use them as:
n = names(S, 'S');
v = values(S);

Related

Swap variable names and field names in a set of structs, so the field names become the struct names and vice versa?

I have a set of structs that all have the same field names. For example structs A, B, and C all have field names name and section. Is there a way to rearrange this organization of data so that the data goes from:
A.name = 'bb';
A.section = 199;
B.name = 'joe';
B.section = 101;
C.name = 'rob';
C.section = 33;
to this:
name =
A: 'bb'
B: 'joe'
C: 'rob'
section =
A: 199
B: 101
C: 33
The current code I have, for example, operates like this:
% ORIGINAL STRUCTS
A.name = 'bb';
A.section = 199;
B.name = 'joe';
B.section = 101;
C.name = 'rob';
C.section = 33;
% CREATE VARIABLE NAMES FOR NEW STRUCTS
oldFNames = fieldnames(A); % old field names
oldVNames{1} = varname(A); % old variable names
oldVNames{2} = varname(B);
oldVNames{3} = varname(C);
% RESTRUCTURE STRUCTS (SWITCH FIELDNAMES AND VARIABLE NAMES)
for j = 1:length(oldFNames)
for k = 1:length(oldVNames)
eval([oldFNames{j} '.' oldVNames{k} ' = ' oldVNames{k} '.' oldFNames{j}]);
end
end
function out = varname(var) % Function to get variable name
out = inputname(1);
end
I find the hack to use the varname function to be not great, and I don't know whether there is a way to make it easily adaptable to the number of variables I have. Any input on how to simplify this procedure would be great. Thanks.
It would be easier to do it all in a function, instead of just using a function to get the input variable name. Here's one solution:
function invert_struct(varargin)
varNames = arrayfun(#inputname, 1:nargin, 'UniformOutput', false); % Get input names
s = [varargin{:}]; % Combine inputs into a structure array
for f = fieldnames(s).' % Loop over fields
assignin('caller', f{:}, cell2struct({s.(f{:})}, varNames, 2));
end
end
And you would call it like so, with however many inputs you like:
invert_struct(A, B, C, ...);
The function uses assignin to create your new variables in the calling function and cell2struct to create a new structure from your field data and old variable names. Note that it also makes use of dynamic field names.

assignment of structure field values in loop - matlab

I am trying to assign the field values of structure in loop.
Structure declaration with empty values:
result_struct = struct('a',{},'b',{},'c',{},'d',{})
I am assigning values in loop like that:
% assume a, b, c, d are variables in my workspace
% field names match with the variable names
for index=1:n
% some computation and store results in variables (a-d)
result_struct(index).a = a;
result_struct(index).b = b;
result_struct(index).c = c;
result_struct(index).d = d;
end
How can I assign the values to the fields using another loop? Like that:
for fname = fieldnames(result_struct)'
result_struct(index).fname = fname; % field names and variable names match
end
You need to use dynamic field names to assign to the struct (the leff-hand side). For the right hand side you could use eval but that is dangerous, so it's better to save your variable fname to a file and then load it back in as a struct prior to accessing fname, again using dynamic field names.
names = fieldnames(result_struct);
for k = 1:numel(names)
% Save variable to a file
save('tmp.mat', names{k});
% Load it back into a struct
tmp = load('tmp.mat', names{k});
result_struct(index).(names{k}) = tmp.(names{k});
end
Alternately, you can use the save and load to just transform the entire thing into a struct without having to loop through the fields.
fields = fieldnames(result_struct);
% Save all relevant variables to a file
save('tmp.mat', fields{:});
% Load it back into the result_struct
result_struct(index) = orderfields(load('tmp.mat'), fields);

Access data in structures when field names are unknown

I have data as a struct with several layers, for example:
data.A.B
The data I want to access is in layer B. But the problem is that field names in B can be different depending on where the data comes from. Therefore I can't just type:
data.A.B.myData
myData is itself a struct
I can use:
fieldnames(data.A)
to find the names, but this doesn't help my much. I would have to write code sections for every possible field name that can occur at this level. And that's just what i trying to avoid.
Is there a way to get down to the data I have (myData) without knowing the field names of B?
Traditionally, you can loop over the fieldnames and perform the search of myData at a specific sub-structure of the struct. However, if you don't know which sub-structure you need to search, then you can perform a recursive algorithm. Below is an example. It will return the first match of myData in the struct or an empty matrix if no match found. The code can be improved to find all matches of myData.
function S2=getmyfield(S1,queriedField)
if isstruct(S1)
% Get all fieldnames of S1
fieldArray=fieldnames(S1);
% Find any match with the queried field. You can also use isfield().
% If there is a match return the value of S1.(queriedField),
% else perform a loop and recurse this function.
matchTF=strcmp(queriedField,fieldArray);
if any(matchTF)
S2=S1.(fieldArray{matchTF});
return;
else
S2=[];
i=0; % an iterator count
while isempty(S2)
i=i+1;
S2=getmyfield(S1.(fieldArray{i}),queriedField);
end
end
else
S2=[];
end
end
Cheers.
You just need a recursive function that checks fieldnames at each level for the structure.
This is roughly what you need (it could be improved to supply the path to the found field).
function [ value, found ] = FindField( rootStruct, fieldName )
%FindField - Find a field with a structure
value = [];
found = 0;
if isstruct( rootStruct )
fields = fieldnames(rootStruct);
for fi=1:length(fields)
if strcmp(fields{fi}, fieldName )
value = rootStruct.(fieldName);
found = true;
return;
end
[value, found ] = FindField( rootStruct.(fields{fi}), fieldName );
if found
return;
end
end
end
end
Usage example:
a.b = 1;
a.b.c = 2;
a.b.d = struct('Index',1,'Special',2);
FindField(a,'d')
ans =
Index: 1
Special: 2

Using a string to refer to a structure array - matlab

I am trying to take the averages of a pretty large set of data, so i have created a function to do exactly that.
The data is stored in some struct1.struct2.data(:,column)
there are 4 struct1 and each of these have between 20 and 30 sub-struct2
the data that I want to average is always stored in column 7 and I want to output the average of each struct2.data(:,column) into a 2xN array/double (column 1 of this output is a reference to each sub-struct2 column 2 is the average)
The omly problem is, I can't find a way (lots and lots of reading) to point at each structure properly. I am using a string to refer to the structures, but I get error Attempt to reference field of non-structure array. So clearly it doesn't like this. Here is what I used. (excuse the inelegence)
function [avrg] = Takemean(prefix,numslits)
% place holder arrays
avs = [];
slits = [];
% iterate over the sub-struct (struct2)
for currslit=1:numslits
dataname = sprintf('%s_slit_%02d',prefix,currslit);
% slap the average and slit ID on the end
avs(end+1) = mean(prefix.dataname.data(:,7));
slits(end+1) = currslit;
end
% transpose the arrays
avs = avs';
slits = slits';
avrg = cat(2,slits,avs); % slap them together
It falls over at this line avs(end+1) = mean(prefix.dataname.data,7); because as you can see, prefix and dataname are strings. So, after hunting around I tried making these strings variables with genvarname() still no luck!
I have spent hours on what should have been 5min of coding. :'(
Edit: Oh prefix is a string e.g. 'Hs' and the structure of the structures (lol) is e.g. Hs.Hs_slit_XX.data() where XX is e.g. 01,02,...27
Edit: If I just run mean(Hs.Hs_slit_01.data(:,7)) it works fine... but then I cant iterate over all of the _slit_XX
If you simply want to iterate over the fields with the name pattern <something>_slit_<something>, you need neither the prefix string nor numslits for this. Pass the actual structure to your function, extract the desired fields and then itereate them:
function avrg = Takemean(s)
%// Extract only the "_slit_" fields
names = fieldnames(s);
names = names(~cellfun('isempty', strfind(names, '_slit_')));
%// Iterate over fields and calculate means
avrg = zeros(numel(names), 2);
for k = 1:numel(names)
avrg(k, :) = [k, mean(s.(names{k}).data(:, 7))];
end
This method uses dynamic field referencing to access fields in structs using strings.
First of all, think twice before you use string construction to access variables.
If you really really need it, here is how it can be used:
a.b=123;
s1 = 'a';
s2 = 'b';
eval([s1 '.' s2])
In your case probably something like:
Hs.Hs_slit_01.data= rand(3,7);
avs = [];
dataname = 'Hs_slit_01';
prefix = 'Hs';
eval(['avs(end+1) = mean(' prefix '.' dataname '.data(:,7))'])

Better code for accessing fields in a matlab structure array?

I have a matlab structure array Modles1 of size (1x180) that has fields a, b, c, ..., z.
I want to understand how many distinct values there are in each of the fields. i.e.
max(grp2idx([foo(:).a]))
The above works if the field a is a double. {foo(:).a} needs to be used in the case where the field a is a string/char.
Here's my current code for doing this. I hate having to use the eval, and what is essentially a switch statement. Is there a better way?
names = fieldnames(Models1);
for ix = 1 : numel(names)
className = eval(['class(Models1(1).',names{ix},')']);
if strcmp('double', className) || strcmp('logical',className)
eval([' values = [Models1(:).',names{ix},'];']);
elseif strcmp('char', className)
eval([' values = {Models1(:).',names{ix},'};']);
else
disp(['Unrecognized class: ', className]);
end
% this line requires the statistics toolbox.
[g, gn, gl] = grp2idx(values);
fprintf('%30s : %4d\n',names{ix},max(g));
end
Indeed, there is a better way. Surprisingly, MATLAB allows you to access the struct fields using a key string without eval, for instance:
Models1(1).(names{ix})
so instead, you can write this:
className = class(Models1(1).(names{ix});
...
values = [Models1(:).(names{ix})];
...
values = {Models1(:).(names{ix})};
Also, instead of using class and strcmp, you can just test the same conditions with isa:
v1 = Models1(1).(names{ix});
if (isa(v1, 'double') || isa(v1, 'logical'))
values = [Models1(:).(names{ix})];
% # ...
elseif (isa(v1, 'char'))
values = {Models1(:).(names{ix})};
% # ...
else
disp(['Unrecognized class: ', class(v1)]);
end
It should be much faster.