Clear Filters
Clear Filters

Iterate over struct with length>1, with multiple fields

51 views (last 30 days)
I have a struct with size>1, and multiple 16 fields.
Each fieldname corresponds to some quantity or property, and the struct stores these properties for 47 different items.
I'm trying to iterate the whole dataset. Preferrably, I'd like to iterate by fieldname and retrieve an array for each filename because within each field name the variable type is uniform.
To illustrate:
K>> teststruct = struct('name', ['Alice', 'Bob', 'Eve'], 'age', {24, 45, 35})
teststruct =
1×3 struct array with fields:
name
age
This shows up nicely as a table in the worspace browser.
However, if I iterate by fieldname, this goes wrong (spurious empty lines removed for readability):
K>> fnames = fieldnames(teststruct);
K>> teststruct.(fnames{1})
ans =
'AliceBobEve'
ans =
'AliceBobEve'
ans =
'AliceBobEve'
What I wanted was an array of all the names, instead I get three answers, of compounded names.
If I do the same with the 'age' field, at least each answer contains only one age, but they're still not in any kind of structure I could use in a code which does not know the size or field names of a struct it needs to deal with. In fact, if I assign the above to a variable, this happens:
K>> names = teststruct.(fnames{1})
names =
'AliceBobEve'
Doing the same with the other field only gives me the first age. One correct piece of data at least, but still not at all what I wanted...
I tried applying the code I found here, which promises to print the contents of an entire struct, but the same happens: I only get the first value of everything.
I know that I could loop over the struct indices first, and access them like this:
value = testsruct(i).(fieldnames(j))
...but then I'd be getting them separately, one by one, instead of getting back the cell array (or any other kind of array) that was used to define the struct in the first place, which is way easier to deal with.
Is that possible somehow?
  3 Comments
Stephen23
Stephen23 on 16 Jul 2024 at 19:17
Edited: Stephen23 on 16 Jul 2024 at 20:16
"Is that possible somehow?"
Of course. Read this if you want to understand comma-separated lists and how to use them:
Also note that square brackets are a concatenation operator (not a "list" operator as some beginners incorrectly think, MATLAB does not have a "list" type). As Voss correctly pointed out, when you concatenate character vectors together you get one character vector:
['Alice', 'Bob', 'Eve']
ans = 'AliceBobEve'
You probably want a cell array:
{'Alice', 'Bob', 'Eve'}
ans = 1x3 cell array
{'Alice'} {'Bob'} {'Eve'}
For example:
S = struct('name', {'Alice', 'Bob', 'Eve'}, 'age', {24, 45, 35})
S = 1x3 struct array with fields:
name age
C = {S.name} % comma-separated list
C = 1x3 cell array
{'Alice'} {'Bob'} {'Eve'}
A Heidebrecht
A Heidebrecht on 17 Jul 2024 at 8:14
Yes, I've been mired in Python for over a decade, and it kind of shapes expectations, especially if the same syntax appears to do the same thing in some cases... thanks for the extra explanation. Definitely going to read up on this again.

Sign in to comment.

Accepted Answer

Voss
Voss on 16 Jul 2024 at 18:00
Edited: Voss on 16 Jul 2024 at 18:06
teststruct = struct('name', {'Alice', 'Bob', 'Eve'}, 'age', {24, 45, 35})
teststruct = 1x3 struct array with fields:
name age
names = {teststruct.name}
names = 1x3 cell array
{'Alice'} {'Bob'} {'Eve'}
ages = [teststruct.age]
ages = 1x3
24 45 35
<mw-icon class=""></mw-icon>
<mw-icon class=""></mw-icon>
Or, without hard-coding the field name references:
fnames = fieldnames(teststruct);
for ii = 1:numel(fnames)
values = {teststruct.(fnames{ii})};
disp(sprintf('field #%d (%s):',ii,fnames{ii}));
disp(values);
end
field #1 (name):
{'Alice'} {'Bob'} {'Eve'}
field #2 (age):
{[24]} {[45]} {[35]}
In this case each set of field values is stored in a cell array (previously the numeric ages were put into a numeric array) since a cell array can contain any class of variable.
  6 Comments
A Heidebrecht
A Heidebrecht about 2 hours ago
Thanks! It seems that all such heterogeneous constructs are in cell arrays in the data that I'm dealing with, and it kind of made sense that they would always have to be... so the case you show here would actually also break the code you previously showed. I believe I would have noticed by now if something like this occurred in the data I'm dealing with, though, because isnumeric(S.x) already produces an exception.
Cell arrays should not be an issue because my somecode() routine is already catching them separately and iterating over them, per element.
Anyway, this means that if I want to be quite safe, I should iterate over each element of a struct that I encounter. Good to know... also somewhat unfortunate since Matlab has those nice vectors and matrices ready to use, but I can't make sure that I can treat them as such (unless I know where they come from). I guess I could check whether a non-scalar struct's fields are uniformly shaped, but at that point it would get tedious.
=> I think I'm good now, thanks for the help!
Voss
Voss about 1 hour ago
Edited: Voss about 1 hour ago
You're welcome!
By the way, isnumeric(S.x) will throw an error about too many input arguments if S has more than one element (or an error about not enough input arguments if S is empty), because S.x is a comma-separated list (which is used as a set of input arguments to isnumeric in this case).
A way to check whether all values of a given field in a structure array are numeric is:
all(cellfun(@isnumeric,{S.x}))
which works regardless of what each x is in S, because they are put in a cell array ({S.x}), then cellfun iterates over the cells, running isnumeric on each cell's contents, getting true or false for each and concatenating those logicals into a single vector, which is passed to all.

Sign in to comment.

More Answers (0)

Categories

Find more on Structures in Help Center and File Exchange

Products


Release

R2023b

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!