필터 지우기
필터 지우기

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

조회 수: 11 (최근 30일)
Art
Art 2016년 3월 11일
댓글: Kelly Kearney 2016년 3월 12일
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
Guillaume
Guillaume 2016년 3월 11일
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 2016년 3월 11일
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?

댓글을 달려면 로그인하십시오.

답변 (3개)

Jos (10584)
Jos (10584) 2016년 3월 11일
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
Art
Art 2016년 3월 11일
편집: Art 2016년 3월 11일
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 2016년 3월 11일
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.

댓글을 달려면 로그인하십시오.


Guillaume
Guillaume 2016년 3월 11일
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 2016년 3월 11일
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
Art
Art 2016년 3월 11일
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 2016년 3월 12일
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.

댓글을 달려면 로그인하십시오.

카테고리

Help CenterFile Exchange에서 Structures에 대해 자세히 알아보기

Community Treasure Hunt

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

Start Hunting!

Translated by