2

Summary: I want to call a function that returns multiple structs n times. How can I append the results to existing fields in my output structs (i.e. create vectors) instead of creating new fields that contain a scalar each iteration?


Example: Consider a function sample_fct( x ) that 1) perfroms some operations on x and saves the result in a couple of new variables (a and b in the sample code) and then 2) calls some sub-functions calculate_one( x ) and calculate_two( x ) with a and b as input. It doesn't matter what exactly these functions do. Output of these functions is then collected in struct A and B.

function [A, B] = sample_fct( x )
    a = 1 * x;
    b = 2 * x;
    [A.one, A.two] = call_functions( a );
    [B.one, B.two] = call_functions( b );
    function [one, two] = call_functions( input )
        one = calculate_one( input );
        two = calculate_two( input );
        function one = calculate_one( input )
            one = input.^2;
        end
        function two = calculate_two( input )
            two = input.^3;
        end
    end
end

I then want to call this function n times with different input parameters in my script

n = 3;
for i = 1:n
    [A(i), B(i)] = sample_fct( i );
end

When I do this, A and B become 1*n structs, each field again containing the fields one and two. So in my example with n = 3 I have 3 instances of scalars one and two. The output of my sample code looks like this:

>> A
A = 
1x3 struct array with fields:
    one
    two
>> A.one
ans =
     1
ans =
     4
ans =
     9

What I actually want is A and B to be 1*2 structs with 1*n vectors one and two, so the desired output should look something like this:

>> A
A = 
    two: [1 8 27]
    one: [1 4 9]

How exactly can I do this without [one, two] being my function's output variable and without calling my function for A and B seperately?


Why I want to do this: I want to run a predictive model with different parameter combinations on a time series and calculate some goodness-of-fit measures and other statistics for 1 minute, 1 hour, 1 day, etc. representations. In my example, x would be the time series, the loop over n a loop over different parameter vectors, a and b representations with different sampling time and one and two some statistics that I want to gather in structs A and B. I'm pretty sure theres a much more sophisticated way to do this but I just can't wrap my head around it.

I know this is easy to do with vectors/matrices instead of structs, but I would like to be able to call my output with a variable name instead of A.hourly(:,19) or something like that, since I calculate many many statistics and not just two in my actual code.

Fred S
  • 1,421
  • 6
  • 21
  • 37
  • Your question is quite long, could you put a short summary on top? – Dennis Jaheruddin Aug 12 '13 at 12:37
  • I tried, but I believe my question is a bit too complicated to summarize it in a few sentences. – Fred S Aug 12 '13 at 12:53
  • I just read the summary :o --> suggestion: put them into a cell array; well if this is not a question about "how do I add data to my matrix"... like a=[a newValues] or a(end+1)=newValues – Lucius II. Aug 12 '13 at 12:59
  • See the last sentence of my question. ;-) I'd like to be able to access my results by a variable name, i.e. `A.one` instead of `A{1}` because in the actual application, `A` has a lot more fields than in my example and I want to avoid confusing them. – Fred S Aug 12 '13 at 13:04
  • 1
    @FredS: you might be interested in this post: http://stackoverflow.com/a/4169216/97160 – Amro Aug 12 '13 at 18:08
  • That was indeed helpful, thank you @Amro . – Fred S Aug 12 '13 at 18:53

2 Answers2

3

EDIT: Updated based on the error mentioned in the comments.

You can convert them by

A = struct('one', [A.one], 'two', [A.two]);

In general:

D = [fieldnames(A), cellfun(@(x) [A.(x)], fieldnames(A), 'Uni', false)].';
A = struct(D{:});

OLD ANSWER:

You can convert them by

A.one = [A.one];
A.two = [A.two];

In general

for theField = fieldnames(A)'
    F = theField{1};
    A.(F) = [A.(F)];
end
Mohsen Nosratinia
  • 9,844
  • 1
  • 27
  • 52
  • This is another very useful suggestion. Thank you. – Fred S Aug 12 '13 at 14:21
  • 1
    Evaluating the first line of your answer raises the error: `Incorrect number of right hand side elements in dot name assignment. Missing [] around left hand side is a likely cause.`, although there definitely is an []. I guess that has to do with the fact that the original A.one is a 1*3 struct of 1*1 structs that contain `one` and `two`. Any idea what to do in this case except using a different variable name? – Fred S Aug 12 '13 at 15:20
  • 1
    @FredS That happens when you write the answer without testing. I updated it with correct and tested answer :-) – Mohsen Nosratinia Aug 12 '13 at 18:02
2

One alternative would be to have sample_fct take A and B as additional arguments and do the appending inside sample_fct:

[A,B] = sample_fct(1)
for i=2:n
    [A,B] = sample_fct(i, A, B)
end

You would then have to change the two call_functions calls in sample_fct in case of the 3-arguments-call. This could e.g. look like this:

if nargin == 3
    [A.one(end+1), A.two(end+1)] = call_functions( a );
    [B.one(end+1), B.two(end+1)] = call_functions( b );
elseif nargin == 1
    [A.one, A.two] = call_functions( a );
    [B.one, B.two] = call_functions( b );
end

As a general remark: I don't see any reason for using nested functions in this case, so I would recommend to rather implement them as normal subfunctions in the same m-file.

sebastian
  • 9,526
  • 26
  • 54
  • You're correct, nesting isn't necessary in this case. Your first solution seems to do something different to me. I need one and two evaluated for different inputs (which is why I have separate structs A and B). Your second solution seems to be a good idea, though. I will try to implement that. Thanks a lot. /edit: Whoops, and your first solution is gone. ;) – Fred S Aug 12 '13 at 14:19