Nested structure array assignment

18 views (last 30 days)
Lucas Kassardjian Codjaian
Commented: David Saidman on 23 Jan 2020
I currently have a function that performs some calcuations for me based on a folder and now that I also have data in other folders, I am writting a new script to go through all these folders. The way I wrote my code at first does not allow me to store results when running a loop, so I decided to write the script to do so, so I don't have to write a new function.
Suppose I have several arrays called array1,array2,....,arrayN and need to assign those to a nested structure, whose 'nestedStruct' and 'Field' names also vary. As an example, I need to have as output
Case.Speed1.Location1.array1 = [....]
Case.Speed1.Location2.array1 = [....]
....
Case.SpeedN.LocationN.arrayN = [....]
And so it goes, for different values of N. The 'Case' is easy to change using a if and a strcmp, but the rest I could not succeed. I tried doing the following (note that I wrote only 2 arrays in the example to make things shorter):
[Case,Speed,Location,Array1,Array2,...ArrayN] = function(....)
Case = struct(['Speed' num2str(Speed)],struct(['Location' num2str(Location)], struct ('name_of_array1',Array1,'name_of_array_2',Array2)))
But the output is being rewritten and nothing is stored in different nested structure or field. What can I do?
Thanks in advance
  3 Comments
Lucas Kassardjian Codjaian
Hi. I used an index N just to be more general. There are not that many variables, and my external function already outputs me all of them. I cannot change it because I don't want to/can't re-write it once it is a quite long one, and it would take me too long.
Thanks
Stephen23
Stephen23 on 12 Dec 2017
Edited: Stephen23 on 12 Dec 2017
"I cannot change it because I don't want to/can't re-write it once it is a quite long one, and it would take me too long."
Neither of those are good reasons to avoid writing better code.
I think you underestimate the complexity of what you are trying to do, compared to collecting the data in a simpler structure or in one array, right from the start inside that function.
If you really insist on lots of output arguments then you can easily collect all of the outputs into one cell array using this method (as long as you do not use varargout):
C = cell(1,nargout(funname));
[C{:}] = funname(...);
If you use varargout then you should just return a simpler structure or cell array rather than lots of individual output arguments.

Sign in to comment.

Answers (2)

Stephen23
Stephen23 on 11 Dec 2017
Edited: Stephen23 on 11 Dec 2017
One option would be to use setfield:
>> S = struct();
>> S = setfield(S,'a','b','c1',1);
>> S = setfield(S,'a','b','c2',22);
>> S = setfield(S,'a','b','c3',333);
>> S.a.b.c2
ans = 22
But I notice that you are putting a pseudo-index into each fieldname, in which case you would be much better off turning it into a real index and using non-scalar structures. Instead of this (slow numeric to string conversion, complex, awkward code):
Case.Speed1.Location1.array1 = [....]
Case.Speed1.Location2.array1 = [....]
you should really be using non-scalar structures (fast, efficient, numeric indices remain numeric):
Case(1).Speed(1).Location(1).Array = [...]
Case(1).Speed(1).Location(2).Array = [...]
...
Case(M).Speed(N).Location(P).Array = [...]

David Saidman
David Saidman on 3 Dec 2019
Edited: David Saidman on 3 Dec 2019
Even if old, this may help for someone else looking.
Fancy dynamic field assignment with the help of the colon operator to handle an unknown number of nested structures. The above comments would work fine, but you may not have the option of changing the function being called. So this could work (didnt check the syntax):
% Get the output arguments as mentioned in above, thats important to make it dynamic
% when you dont know the number of nested structures
C = cell(1,nargout(funname));
[C{:}] = funname(...);
% Now we have a cell of {case, speed, location, array1,...,arrayN}
% Let C{1} be case, C{2} speed, C{3} location, arrays are C(4:end) are Array1, Array2, etc
% Get strings that will be each of the fields to assign the output arrays (reason is shown in next step)
outArrays = C(4:end);
fNameX = @(n) sprintf('Case%i.Speed%i.Location%i.Array%i', C{1}, C{2}, C{3}, n)
myFieldNames = arrayfun(fNameX, 1:numel(outArrays),'UniformOutput',false);
% Assuming C{1}=1, C{2}=2, and C{3}=3 are 1 (for simplicty, can be any integer)
% =
% ... 'Case1.Speed2.Location3.Array1'
% ... 'Case1.Speed2.Location3.Array2'
% ... 'Case1.Speed2.Location3.ArrayN'
s = struct(); % Final answer to populate
for iName = 1:numel(myFieldNames)
fName = myFieldNames{iName};
% Split
% 'Case1.Speed2.Location3.Array1'
% into
% {'Case1','Speed2','Location3','Array1'}
% number of nested structures wont matter, can use something like this
subFieldNames = regexp(fName,'\.','split');
% Finally set the array to your structure with long dynamic fieldname, using
% colon operator to pass in unknown number of arguments
s = setfield(s,subFieldNames{:}, outArrays{iArray})
% Now we have
% s.Case1.Speed2.Location3.Array1 = Values of Array 1
% s.Case1.Speed2.Location3.Array2 = Values of Array 2
% ...
% s.Case1.Speed2.Location3.ArrayN = Values of Array N
end
I agree with the comments above for simplifying how you handle your outputs, but this will work assuming you don't have that option.
Making a container (like a python dict) would also work. Check out map containers
  2 Comments
Stephen23
Stephen23 on 23 Jan 2020
@David Saidman: why are you joining all of the fieldnames into one string just to split that string up again a few lines later? You already have all of the fieldnames conveniently in one cell array C, so why not use them?
Note that it is easy to write code for arbitrary structure nesting, without explicit loops:
>> C = {1, 2, 3, 'hello','crazy','world'}; % function output.
>> P = {'case', 'speed', 'location'}; % fieldname prefixes.
>> N = numel(P);
>> F = cellfun(@(p,n)sprintf('%s%i',p,n),P,C(1:N),'UniformOutput',false); % fieldnames.
>> A = arrayfun(@(n)sprintf('Array%i',n),1:numel(C)-N,'UniformOutput',false); % array names.
>> S = struct();
>> S = setfield(S,F{:},cell2struct(C(N+1:end),A,2));
>> S.case1.speed2.location3
ans =
Array1: 'hello'
Array2: 'crazy'
Array3: 'world'
Of course the code would be improved by using non-scalar structures and not forcing meta-data into fieldnames (consider what occurs if any of those numerics has a fractional value).
"The above comments would work fine, but you may not have the option of changing the function being called"
Using non-scalar structures does NOT require the function to be changed, as it only affects how those matrices are handled once they have been returned by the function.
"Fancy dynamic field assignment with the help of the colon operator..."
is called a comma-separated list in MATLAB terminology:
David Saidman
David Saidman on 23 Jan 2020
Just to highlight the nesting for the question being asked, thought easier to follow.
No objections here, both'll work.
Your answer above it helped me solve my problem, so thanks for that!

Sign in to comment.

Categories

Find more on Structures in Help Center and File Exchange

Tags

Community Treasure Hunt

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

Start Hunting!