MATLAB Answers

0

How do I iterate through multiple structures?

Asked by Isabella Reed on 10 Mar 2016
Latest activity Commented on by Stephen Cobeldick on 12 May 2018
I have a workspace filled with roughly 40 structs. I've attached a simplified version of the code I've written just to give you an idea of my approach(SEE CODE 1). The error I have been receiving is:
Attempt to reference field of non-structure array.
Error in example (line 11) xValues(i) = structNames{i}.Position(1);
But when I enter structNames{1} or (structNames{1}) in to the command window I get the value I expect: a1. When I type a1.Position(1) into the command window I also get the value I would expect: 5. But when I use structNames{1}.Position(1) it gives me the error message I mentioned before. When I use the ({}) syntax in the second value it works perfectly fine (SEE CODE 2). When running the code I've attached you have to delete one code to run the other. Help please :(
% ===== CODE 1 ==========
clear all;
a1.Position = [5 6] ;
a2.Position = [7 8] ;
b1.Position = [9 10] ;
b2.Position = [11 12] ;
a1.Time = [13];
a2.Time = [14];
b1.Time = [15];
b2.Time = [16];
holdStructNames = struct('a1',0,'a2',1,'b1',2,'b2',3);
structNames = fieldnames(holdStructNames);
holdFieldNames = struct('Position',0,'Time',1);
fieldNames = fieldnames(holdFieldNames);
for i = 1:numel(structNames)
xValues(i) = structNames{i}.Position(1);
end
%========== CODE 2 ==================
clear all;
a1.Position = [5 6] ;
a2.Position = [7 8] ;
b1.Position = [9 10] ;
b2.Position = [11 12] ;
a1.Time = [13];
a2.Time = [14];
b1.Time = [15];
b2.Time = [16];
holdStructNames = struct('a1',0,'a2',1,'b1',2,'b2',3);
structNames = fieldnames(holdStructNames);
holdFieldNames = struct('Position',0,'Time',1);
fieldNames = fieldnames(holdFieldNames);
for j = 1:numel(fieldNames)
timeANDposA(j) = a1.(fieldNames{j})(1);
timeANDposB(j) = b1.(fieldNames{j})(1);
end

  3 Comments

KSSV
on 10 Mar 2016
In CODE 1, structNames they carry only names. Their class is char. How you expect them to carry fileds and its values?
"But when I enter structNames{1} or (structNames{1}) I get the value I expect" &nbsp shows that structNames is the name of a cell array and that the value of the first cell is a structure
@Isabella Reed: "I have a workspace filled with roughly 40 structs"
The solution to your question is simple: don't create forty separate structures, but simply create one non-scalar structure. Then you can trivially iterate over it, just like you can iterate over any other array. By choosing to have separate variables you have removed the only easy and robust option to accessing all of the data in a loop. Think about it: you can loop over a vector because it is one variable:
for k = 1:10
vec(k) = ...
end
And the same applies to structures:
for k = 1:10
mystruct(k).test = ...
mystruct(k).data = ...
end
This is a million times easier and faster than any hack solution trying to access separate variables. And whatever you do, do not try to access those variables using dynamic variable names: this is the slowest, buggiest way to write code, although sadly loved by beginners.
See my answer for the simplest, fastest, and most robust solution.

Sign in to comment.

3 Answers

Answer by Stephen Cobeldick on 10 Mar 2016
Edited by Stephen Cobeldick on 10 Mar 2016
 Accepted Answer

The simplest solution is to create one non-scalar structure, like this:
% 1st row = "a"
% 2nd row = "b"
S(1,1).Position = 5:6 ;
S(1,2).Position = 7:8 ;
S(2,1).Position = 9:10 ;
S(2,2).Position = 11:12 ;
S(1,1).Time = 13;
S(1,2).Time = 14;
S(2,1).Time = 15;
S(2,2).Time = 16;
or equivalently
S = struct('Position',{5:6,7:8;9:10,11:12},'Time',{13,14;15,16})
and then accessing the data inside the structure is very simple
>> [S(1,:).Time] % "a" times
ans =
13 14
>> [S(2,:).Time] % "b" times
ans =
15 16
>> vertcat(S(2,:).Position) % all "b" positions
ans =
9 10
11 12
>> S(2,2).Position % second "b" position
ans =
11 12
Note how indexing can be used to access any element of the structure. This means that you can easily iterate over the structure in any way that you wish to:
>> for k = 1:size(S,2), S(1,k).Time, end % loop over the "a" times
ans =
13
ans =
14
You can also call arrayfun on a structure, for example to get the first elements of the "b" position vectors:
>> arrayfun(@(s)s.Position(1),S(2,:))
ans =
9 11

  2 Comments

I cannot get this type of command to work with structures that contain substructures. i.e.
[S(2,:).sub.Time] % "b" times
returns
Expected one output from a curly brace or dot indexing expression, but there were 2 results.
Is this the expected behavior? Is there a way to index over non-scalar arrays and return data from substructures?
"Is this the expected behavior?"
Yes. Read this to know why:
"Is there a way to index over non-scalar arrays and return data from substructures?"
Of course. The method to use depends on the sizes of the structures, which you have not told us about. You will probably have to allocate to a temporary variable.

Sign in to comment.


Answer by KSSV
on 10 Mar 2016

% ===== CODE 1 ==========
clear all;
a1.Position = [5 6] ;
a2.Position = [7 8] ;
b1.Position = [9 10] ;
b2.Position = [11 12] ;
a1.Time = [13];
a2.Time = [14];
b1.Time = [15];
b2.Time = [16];
holdStructNames = struct('a1',0,'a2',1,'b1',2,'b2',3);
structNames = fieldnames(holdStructNames);
holdFieldNames = struct('Position',0,'Time',1);
fieldNames = fieldnames(holdFieldNames);
for i = 1:numel(structNames)
str = eval(structNames{i}) ;
xValues(i) = str.Position(1);
end
Hope the above CODE 1 works for you?

  0 Comments

Sign in to comment.


Answer by KSSV
on 10 Mar 2016

As you said you have many structures.....I would suggest you to make all the structures a single structure and play with it. Like below:
clc; clear all;
a1.Position = [5 6] ;
a2.Position = [7 8] ;
b1.Position = [9 10] ;
b2.Position = [11 12] ;
a1.Time = [13];
a2.Time = [14];
b1.Time = [15];
b2.Time = [16];
% Make all structures one single structure
var = whos ;
for i = 1:length(var)
if isstruct(var(i))
gstr(i) = var(i) ;
end
end
You have all the structures in gstr. You can access any structure using eval(gstr(i)). You can fields using fields(eval(gstr(i)). Filed values using getfiled(eval(gstr(i)),'filedname') .
Good luck.

  3 Comments

"I would suggest you to make all the structures a single structure and play with it"
This is good advice and you should follow it.
"You can access any structure using eval(gstr(i)). You can fields using fields(eval(gstr(i)). Filed values using getfiled(eval(gstr(i)),'filedname') ."
This is bad advice and you should ignore it. It does not work. Do not use eval for such trivial code as accessing structures. These suggestions of using eval are slow, buggy, and obfuscated, and they do not work. DO NOT DO THIS. It would be nice if beginners learned to avoid using eval for such trivial tasks, because then they would write much faster and more robust code. Using eval like this shows basic ignorance of how to use MATLAB efficiently, and how to write robust code. Structures do not need to be evaluated to access them, any more than a numeric array needs to be. This is just a buggy waste of time.
Summary: DO NOT USE EVAL for such trivial code as accessing structures like this. Here is why:
KSSV
on 10 Mar 2016
Hi Stephen Cobeldick
When you make a structure from variables using whos. You cannot access the fields in the given way. You may check it once. That's why I have mentioned eval().
"You may check it once" thank you for the tip. I did check your code, and found many bugs. For a start your description "Make all structures one single structure" is incorrect. And "You have all the structures in gstr" is also incorrect. You are not actually merging those data structures into one non-scalar structure, but in fact you create an entirely new structure based on whos' output structure, i.e. essentially a list of the names of the variables in that workspace. A correct description would be "Make a new structure listing all variables in the workspace", because you do not "make" the original data structures into "one single structure" at all. And also, as I explain later, you do not even correctly check if those variables are structures.
The proposal makes even less sense now that you have highlighted to me what you are really doing (creating a list of variable names), because you are just introducing more complexity (a new structure listing the variables) which still needs the same awful eval to access the original (separate) data structures. Why? Bad, nonsensical advice.
If you are going to bother using eval, then why not actually concatenate all of the original separate data structures into one, from whence they can be referred to using simpler syntaxes without eval?
Your usage of whos's output also shows some basic misunderstanding of MATLAB, so that the test you thought you were doing is actually totally useless. The function whos returns a structure:
var = whos; % var is a structure.
You then test each element of var to check if it is a structure. But of course each element of var is a structure, because var is too! It seems that you think that testing an element of var means testing the corresponding variable that whos was checking, but in reality this is incorrect. Have a look at this example:
>> clear
>> X = 1;
>> var = whos;
>> isstruct(var(1)) % this does NOT test if X is a structure!
ans =
1
So although you thought that testing each of isstruct(var(i)) means testing if the corresponding variable is a structure, in fact because it only tests if var itself is a structure (which it always is), then this condition will always be true and so the loop does nothing at all, and so gstr will always be identical to var. What is the point in that?
So ultimately you have not "Make all structures one single structure" at all, because your entire code is equivalent to this:
gstr = whos;
and it is clear that gstr does not merge or join those original data structures in any way whatsoever. It just lists all variables in the workspace.
Then we get to the final point: That this code does not work. This code is broken. The author thinks that eval-ing an element of the structure var will return the original variable checked by the function whos: "You can access any structure using eval(gstr(i))". It doesn't. In fact this is totally wrong, and it will produce an error. Lets have a look at a simple example:
>> clear
>> X = 1;
>> var = whos;
>> eval(var(1))
Undefined function 'eval' for input arguments of type 'struct'.
Evaluating a structure is an error. All of the other suggested methods for accessing the fields also throw errors.
Summary
This "Answer" is a classic example of how beginners use eval to try to solve problems, and by doing so just introduce more problems. By choosing to use eval they create slow complicated code that does not work, and because it is so obfuscated it is not clear why it does not work. The convoluted code hides more bad mistakes, and so the author cannot even recognize their mistakes. This is exactly why using eval is such a bad idea, no matter how much beginners love using it.
Better advice would have made the task simpler, not more complicated: Put the data structures into one non-scalar structure. This would be much simpler, does not create any extra lists (i.e. your gstr), and can then be accessed using standard syntaxes without requiring eval.

Sign in to comment.