Any simple way to change a struct with fields of length "n" to a struct of length"n"?

10 views (last 30 days)
I have 2 structs each containing a field called 'time', along with other differently named fields of length "n"
i.e.
Mystruct.time = [1 4 6 8 9]
Mystruct.field2 = [5 6 7 8 9]
Mystruct.field3 = [23 423 2 4 7]
Yourstruct.time = [3 7]
Yourstruct.fieldX = [5 6]
What I need to do is create a time ordered "orderedStruct" that contains each index of these two structs, ordered by their time fields.
e.g. in this example I could type in "orderedStruct(num)" and could get the result:
orderedStruct(1) = struct with fields 'time' = 1, 'field2' = 5; 'field3' = 23;
orderedStruct(2) = struct with fields 'time' = 3, 'fieldX' = 5
orderedStruct(7) = struct with fields 'time' = 9, 'field2' = 9; 'field3' = 7;
Is there an efficient way to do this? I can imagine putting everything in a nested loop, from 1:number of structs and within that loop another going from 1:number of fields, but my structs could have 300 fields, with length of 50,000 or more. This would be a huge time hog.
Any ideas?
Thanks in advance
  5 Comments
Guillaume
Guillaume on 11 Mar 2016
Note that in an array of structure, all structures must have the same fields, so in your example orderedStruct must also have field2 and field3. What value should these be?
Art
Art on 11 Mar 2016
After much searching and some therapy, this code will do what I want, however it takes WAY too long for what I need. It appears to create an array of the two starting structs (that contain different fields), although I may not be clear on that part:
Mystruct.time = [1 4 6 8 9];
Mystruct.field2 = [5 6 7 8 9];
Mystruct.field3 = [23 423 2 4 7];
Yourstruct.time = [3 7];
Yourstruct.fieldX = [5 6];
allStructs = {'Mystruct' 'Yourstruct'};
totind = 0;
for ind = 1:2
thisStruct = eval(char(allStructs(ind)));
fn = [rot90(fieldnames(thisStruct))]';
nItems = numel(thisStruct.(fn{1})); %%all fields are same length
sf=fn';
sf(2,1:numel(fn))={{}};
sf = sf(:)';
S=struct(sf{:});
for f=1:numel(fn)
for n = 1:nItems %%nItems: -1 : 1;
S(n).(fn{f}) = thisStruct.(fn{f})(:,n);
end
end
%%include struct "S" into "all_messages" and "all_times".
%%"all_times" is used to sort the cell array "all_messages".
for ind = 1:numel(S)
totind = totind + 1;
all_messages(totind) = {S(ind)};
all_times(totind) = S(ind).time;
end
end
[~,msg_order] = sort(all_times);
ordered_msgs = all_messages(msg_order);
Typing ordered_msgs{1} gives a struct with fields time, field2 and field3, typing ordered_msgs{2} gives a struct with time and fieldX.
Any way to make this faster?

Sign in to comment.

Answers (3)

Jos (10584)
Jos (10584) on 11 Mar 2016
This question clearly shows how not thinking about your data management in the beginning will get you into trouble later on.
What do you want to have as the value of a field for which there is no time point ...?
  3 Comments
Art
Art on 11 Mar 2016
Edited: Art on 11 Mar 2016
Well, thanks for the feedback but let me clarify. I inherited two very usable structs which are, in fact, separate messages that are normally handled as separate entities. Because there are some fields common to both (not indicated in this example), it would be very handy for me to incorporate them into one time-ordered variable for plotting and analysis.
Just looking for an easy way of doing this.
Art
Art on 11 Mar 2016
BTW Jos, the messages are based on time. If there is no time at a particular index, there won't be that index point in any field.

Sign in to comment.


Guillaume
Guillaume on 11 Mar 2016
Regarding your latest comment, there is of course a simpler way to achieve the same thing, without eval and only looping once over the structures:
MyStruct = struct('Time', [1 4 6 8 9], 'field2', [5 6 7 8 9], 'field3', [23 423 2 4 7]);
YourStruct = struct('Time', [3 7], 'fieldX', [5 6]);
allstructs = {MyStruct, YourStruct}; %store structures in a cell array so we can iterate over them.
reshapedstructs = cell(size(allstructs));
timings = cell(size(allstructs));
%transform each struct from a struct of arrays into an array of structs of scalars
for sidx = 1:numel(allstructs)
fn = fieldnames(allstructs{sidx});
svalues = cell2mat(struct2cell(allstructs{sidx}));
reshapedstruct = cell2struct(num2cell(svalues), fn, 1); %the vertcat will fail if fields of the struct have different size
reshapedstructs{sidx} = num2cell(reshapedstruct'); %split the array of structs into a row cell array of scalar struct
timings{sidx} = allstructs{sidx}.Time;
end
%flatten the cell arrays and reorder by timing
reshapedstructs = [reshapedstructs{:}];
timings = [timings{:}];
[~, order] = sort(timings);
reshapedstructs = reshapedstructs(order);
celldisp(reshapedstructs)
A similar way to achieve the same in even less lines of code, but probably slower:
allstructs = {MyStruct, YourStruct};
reshapedstructs = cellfun(@(s) num2cell(cell2struct(num2cell(cell2mat(struct2cell(s))), ...
fieldnames(s), 1)'), allstructs, ...
'UniformOutput', false);
timings = cellfun(@(s) s.Time, allstructs, 'UniformOutput', false);
reshapedstructs = [reshapedstructs{:}];
timings = [timings{:}];
[~, order] = sort(timings);
reshapedstructs = reshapedstructs(order);
celldisp(reshapedstructs)

Kelly Kearney
Kelly Kearney on 11 Mar 2016
How about this?
A.time = [1 4 6 8 9];
A.field2 = [5 6 7 8 9];
A.field3 = [23 423 2 4 7];
B.time = [3 7];
B.fieldX = [5 6];
t = unique([A.time, B.time]);
[tf1,loc1] = ismember(A.time, t);
[tf2,loc2] = ismember(B.time, t);
flds = unique([fieldnames(A); fieldnames(B)]);
data = nan(length(flds), length(t));
for ii = 1:length(flds)
if isfield(A, flds{ii})
data(ii,loc1) = A.(flds{ii});
end
if isfield(B, flds{ii})
data(ii,loc2) = B.(flds{ii});
end
end
data = num2cell(data);
C = cell2struct(data, flds, 1);
You didn't specify how you wanted missing values or duplicated times to be treated. So in this example, times without a field value are indicated by NaNs, and any time that has data in both structures simply uses the data from the second one.
  2 Comments
Art
Art on 11 Mar 2016
Thanks to both you, Kelly, and Guillaume for great help. Let me add one more wrinkle: if there were one field with char instead of num, could your examples work with this?
e.g, in your example Kelly, if
A.field2 = [{'blah'} {'blah'} {'xxxx'} {'yyyy'} {'zzzzz'}];
Kelly Kearney
Kelly Kearney on 12 Mar 2016
No, it would need to be modified a bit. Instead of preallocating data with NaNs, you'd need to set it up as a cell array, and add in a few num2cell's when assigning the numeric arrays to it. I'm away from my computer right now but I'll try to post a modified version later.

Sign in to comment.

Categories

Find more on Structures in Help Center and File Exchange

Community Treasure Hunt

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

Start Hunting!