Elegant alternative to nested loops?

4 views
Skip to first unread message

Francis Burton

unread,
Apr 24, 2003, 7:34:14 AM4/24/03
to
I want to run a simulation using ranges of parameters that can be
specified in a GUI. So, for example, there could be three edit boxes
into which one could type either a single scalar value or an
expression like "1:5". When the simulation is run, a function is
called once for every combination of the specified parameter values.

The code I wrote to do this looks something like this:

s = get(handles.editParam1, 'String');
try
eval(['param1=' s ';']);
catch
...
end;

% get 2nd & 3rd parameters in same way

for p1 = param1
for p2 = param2
for p3 = param3
DoSimulation(p1, p2, p3);
end;
end;
end;

Is there a neater and/or more flexible way of doing this?

My concern is that each parameter is tied to a particular edit box
and another nesting of the for loops. So if I wanted to add another
three possible parameters (say), I would have to make space for
three new edit boxes and further indent the looping code.

What would be nice would be to allow arbitrary parameter
specifications to be typed into one edit box, e.g. "p1=1:5 p2=0.4",
and have the code go through the combinations automatically.

Is this possible in Matlab? (I'm not so worried about performance.)

Francis

us

unread,
Apr 24, 2003, 8:09:36 AM4/24/03
to
one of the many solutions:


1) have users put a <delimiter> between sets of parameters in
your edit box, eg, </>


2) <s> now could look like this
s='p1=1:5 / p2=0.4 / p3=pi*[0:10]'


3) retrieve each parameter
b=s;
plist=[];
while ~isempty(b)
[a,b]=strtok(b,'/');
com=sprintf('plist.%s',a);
eval(com);
end


4) ... you'll end up with <plist>:
plist =
p1: [1 2 3 4 5]
p2: 0.4
p3: [1x11 double]


5) run your loop
for p1=plist.p1
for p2=plist.p2
% ...
end
end


us


Francis Burton wrote:
> s = get(handles.editParam1, 'String');
> try
> eval(['param1=' s ';']);
> catch
> ...
> end;
>
> % get 2nd & 3rd parameters in same way

Francis Burton

unread,
Apr 24, 2003, 2:10:10 PM4/24/03
to
In article <eebc...@WebX.raydaftYaTP>, us <u...@neurol.unizh.ch> wrote:
>3) retrieve each parameter
> b=s;
> plist=[];
> while ~isempty(b)
> [a,b]=strtok(b,'/');
> com=sprintf('plist.%s',a);
> eval(com);
> end

I like this! Thanks for the new ideas.

>5) run your loop
> for p1=plist.p1
> for p2=plist.p2
>% ...
> end
> end

Is there any way to avoid "hardwiring" the loops in this way
and have the iterations 'eval'ed somehow? Maybe a function
that takes the above plist containing an arbitrary number of
elements and calls another function repeatedly with that
number of arguments, once for each combination.

Francis

Steven Lord

unread,
Apr 24, 2003, 4:44:26 PM4/24/03
to

One thing that might work for you is using NDGRID (assuming you know or can
find out the number of parameters ... maybe by capturing each variable name
while tokenizing the string) and either calling your function on those
multi-D arrays (assuming the function is vectorized) or looping once from 1
to the number of elements in one of the arrays NDGRID created (using NUMEL)
and operating on the appropriate elements of those multi-D arrays
individually.

--
Steve Lord
sl...@mathworks.com


us

unread,
Apr 24, 2003, 7:08:56 PM4/24/03
to
Francis Burton wrote:
> Is there any way to avoid "hardwiring" the loops in this way
> and have the iterations 'eval'ed somehow? Maybe a function
> that takes the above plist containing an arbitrary number of
> elements and calls another function repeatedly with that
> number of arguments, once for each combination.


there is.


some time ago i created an automatic loop-creator <aloop>,
which does exactly what you want. i uploaded it to TMWs central;
maybe it appears within the next years :-)


here's how it works:


c=aloop(a_command_template,...
option,...
indices1,...,indicesN);


eg,


c=aloop('sprintf(''%2d/%7.3f > %s'')',...
'-c',...
1:2,pi*[1:3],{'apple' 'banana'})


% output during runtime shows progression
ALOOP> sprintf(' 1/ 3.142 > apple')
ALOOP> sprintf(' 1/ 3.142 > banana')
ALOOP> sprintf(' 1/ 6.283 > apple')
ALOOP> sprintf(' 1/ 6.283 > banana')
ALOOP> sprintf(' 1/ 9.425 > apple')
ALOOP> sprintf(' 1/ 9.425 > banana')
ALOOP> sprintf(' 2/ 3.142 > apple')
ALOOP> sprintf(' 2/ 3.142 > banana')
ALOOP> sprintf(' 2/ 6.283 > apple')
ALOOP> sprintf(' 2/ 6.283 > banana')
ALOOP> sprintf(' 2/ 9.425 > apple')
ALOOP> sprintf(' 2/ 9.425 > banana')


c =
tmpl: sprintf('%2d/%7.3f > %s')'
opt: [1x1 struct]
argc: 3 % nr of index args
call: 24 % nr of internal recursions
n: 12 % nr of commands created
com: {12x1 cell}


c.com = % cell of command(s)
'sprintf(' 1/ 3.142 > apple')'
'sprintf(' 1/ 3.142 > banana')'
'sprintf(' 1/ 6.283 > apple')'
'sprintf(' 1/ 6.283 > banana')'
'sprintf(' 1/ 9.425 > apple')'
'sprintf(' 1/ 9.425 > banana')'
'sprintf(' 2/ 3.142 > apple')'
'sprintf(' 2/ 3.142 > banana')'
'sprintf(' 2/ 6.283 > apple')'
'sprintf(' 2/ 6.283 > banana')'
'sprintf(' 2/ 9.425 > apple')'
'sprintf(' 2/ 9.425 > banana')'


you can run commands:


for i=1:c.n
eval(c.com{i})
end


alternatively, <aloop> takes the option
-r
in which case the <template> is <eval>ed right away in
the base workspace


as you might guess, this snippet is rather versatile.


us

Francis Burton

unread,
Apr 25, 2003, 5:20:00 AM4/25/03
to
In article <eebc...@WebX.raydaftYaTP>, us <u...@neurol.unizh.ch> wrote:
>some time ago i created an automatic loop-creator <aloop>,
>which does exactly what you want. i uploaded it to TMWs central;
>maybe it appears within the next years :-)

I will check weekly, if not daily, for it. :-)

It looks like it will solve my problem nicely, and be
worthwhile studying to learn a bit more about "advanced
programming" in Matlab.

Thanks!

Francis

Francis Burton

unread,
Apr 25, 2003, 5:26:28 AM4/25/03
to
In article <b89iba$d9v$1...@ginger.mathworks.com>,

Steven Lord <sl...@mathworks.com> wrote:
>One thing that might work for you is using NDGRID (assuming you know or can
>find out the number of parameters ... maybe by capturing each variable name
>while tokenizing the string) and either calling your function on those
>multi-D arrays (assuming the function is vectorized) or looping once from 1
>to the number of elements in one of the arrays NDGRID created (using NUMEL)
>and operating on the appropriate elements of those multi-D arrays
>individually.

The number of parameters will vary (which is why I didn't
like the hardcoded option), but will be known.

I think what you suggest could well work - and work well!
I briefly racked my brains for a solution based on matrices,
until it became apparent how many gaps there still are in my
working knowledge. Your clue will spur me on to fill some of
those gaps - thanks!

Francis

Rob Henson

unread,
Apr 25, 2003, 2:10:47 PM4/25/03
to
> 3) retrieve each parameter
> b=s;
> plist=[];
> while ~isempty(b)
> [a,b]=strtok(b,'/');
> com=sprintf('plist.%s',a);
> eval(com);
> end

This particular usage of strtok in a loop to run through a string can be
very slow for long strings. You are much better off using

b= strread(s,'%s','delimiter','/');

to pull the string apart.

So you would then use something like this to achieve the same goal.

plist=[];
b = strread(s,'%s','delimiter','/')'
for count = 1:numel(b)
com=sprintf('plist.%s',b{count});
eval(com);
end

Rob


us

unread,
Apr 25, 2003, 4:50:34 PM4/25/03
to
Rob Henson wrote:
> This particular usage of strtok in a loop to run through a string
can be
> very slow for long strings. You are much better off using
>
> b= strread(s,'%s','delimiter','/');
>
> to pull the string apart.


very bad news, rob:


in the past, i used to use <strread> because after all its main
engine (<dataread>) is mex-ed.
however, the max token size is 4095!
now, you assume that the user will enter a <very long> string
...


eg,


nr=1000;
nd=10;
z=char(double('a':'z'));
s=repmat(z,1,nr);
v=randperm(length(g));
s(v(1:nd))='/';
b = strread(s,'%s','delimiter','/')';


which typically yields:
??? Buffer overflow (bufsize = 4095) while reading string from
file (row 4, field 1) ==> uvwxyzabcdefghi


Error in ==> G:\usr\ml13\toolbox\matlab\iofun\dataread.dll
Error in ==> G:\usr\ml13\toolbox\matlab\iofun\strread.m
On line 51 ==> [varargout{1:nlhs}]=dataread('string',varargin{:});


hence, i didn't feel comfortable to suggest this - otherwise very
nice - function.


assuming of course that the OP gui users would find the time to enter
several thousand characters into the edit box ...


:-)
us

us

unread,
Apr 25, 2003, 4:55:11 PM4/25/03
to
us wrote:
> v=randperm(length(g));
is:
v=randperm(length(s));
us

Rob Henson

unread,
Apr 25, 2003, 6:59:36 PM4/25/03
to

"us" <u...@neurol.unizh.ch> wrote in message
news:eebc...@WebX.raydaftYaTP...

> Rob Henson wrote:
> > This particular usage of strtok in a loop to run through a string
> can be
> > very slow for long strings. You are much better off using
> >
> > b= strread(s,'%s','delimiter','/');
> >
> > to pull the string apart.
>
>
> very bad news, rob:
>
>
> in the past, i used to use <strread> because after all its main
> engine (<dataread>) is mex-ed.
> however, the max token size is 4095!
> now, you assume that the user will enter a <very long> string
> ...
>
>
> eg,
>
>
> nr=1000;
> nd=10;
> z=char(double('a':'z'));
> s=repmat(z,1,nr);
> v=randperm(length(s));

> s(v(1:nd))='/';
> b = strread(s,'%s','delimiter','/')';
>
>
> which typically yields:
> ??? Buffer overflow (bufsize = 4095) while reading string from
> file (row 4, field 1) ==> uvwxyzabcdefghi

You are correct. The command to get around this is:

b = strread(s,'%s','delimiter','/','bufsize',2*numel(s)+1)';

This somewhat pathological example shows the performance difference.
s = repmat('a/',1,50000);
tic;b = strread(s,'%s','delimiter','/','bufsize',2*numel(s)+1)';toc

elapsed_time =

0.3710

tic; b=s; while ~isempty(b), [a,b] = strtok(b,'/');end; toc

elapsed_time =

128.2250


Rob


us

unread,
Apr 25, 2003, 7:12:01 PM4/25/03
to
Rob Henson wrote:
> This somewhat pathological example (5k chars)


rob, only slighty pathological for most ML users ...


> shows the performance difference.
<SNIP BIGGISH performance delta>


... touche!


repmat(' :-) ',inf,inf)


us

us

unread,
Apr 25, 2003, 7:25:11 PM4/25/03
to
Rob Henson wrote:
> b = strread(s,...
'%s',...
'delimiter','/',...
'bufsize',2*numel(s)+1)'


... and this afterthought:


unfortunately, the average user (such as myself) will not see these
additional parameters using a pedestrian
help strread
he/she rather has to scrutinize
help textread


while it IS mentioned, it isn't THAT obvious


a lame excuse - i know
us

Will

unread,
Apr 26, 2003, 11:51:29 PM4/26/03
to
hi,
Your aloop seems to be a very effective one, Where is TMW center?
Could u give a web-link?
Regards,
Will


us wrote:
>
>
> Francis Burton wrote:
>> Is there any way to avoid "hardwiring" the loops in this way
>> and have the iterations 'eval'ed somehow? Maybe a function
>> that takes the above plist containing an arbitrary number of
>> elements and calls another function repeatedly with that
>> number of arguments, once for each combination.
>
> there is.
>

> some time ago i created an automatic loop-creator <aloop>,
> which does exactly what you want. i uploaded it to TMWs central;
> maybe it appears within the next years :-)
>

Will

unread,
Apr 26, 2003, 11:51:37 PM4/26/03
to

us

unread,
Apr 27, 2003, 6:56:05 AM4/27/03
to
TMW central:
<http://www.mathworks.com/matlabcentral>
see:
recently added files
i uploaded the file <aloop> a few days ago; it still hasn't
show up, however
us

us

unread,
Apr 29, 2003, 12:56:07 PM4/29/03
to
thanks to <min poh> it's available now at


<http://www.mathworks.com/matlabcentral/fileexchange/loadFile.do?object>
Id=3338&objectType=FILE


note there was an earlier version that should NOT have been uploaded


us


us wrote:
> i uploaded the file <aloop> a few days ago; it still hasn't
> show up, however

Kirill Andreev

unread,
Apr 30, 2003, 10:49:08 AM4/30/03
to
Below the code that might be useful. The idea to populate a structure
with unique levels of categorical variables or levels of variables
ones wants to loop over. As they represent a multidimensional matrix
it is relatively easy to loop over them.

Try run this example:

methods = {'vek' 'lc'}'
pnames = {'mjpn', 'fjpn'};

% iteration
dsu = [];
dsu.methods = methods;
dsu.pnames = pnames;

n = dsfactorsidx(dsu);
for i = 1:n
[k, di] = dsfactorsidx(dsu, i);
disp(i)
disp(di);
end


Hope it is help

Kirill Andreev

function [j, di] = dsfactorsidx(dsu, i);
% dsfactorsidx(): given row index of a normalized data set returns
indices of unique factor levels
% Description:

%
% Suppose we have the following structure with unique levels of
factor variables:
% dsu=[];
% dsu.population = {'jpn' 'dnk'};
% dsu.sex = [1 2];
% dsu.decade = [1950 1960 1970];
%
% [[it is important to use dsu =[] so the fieldnames() will return
% {'population' 'sex' 'decade'}. In this case we know the order of
fields.
% If 'dsu' have been initialized fieldnames() will return fields in
order of initialization]]
%
% In this case normalized data set will have 3 columns and 12 rows
and the following form
%
% jpn 1 1950 1
% jpn 1 1960 2
% jpn 1 1970 3
% jpn 2 1950 4
% jpn 2 1960 5
% jpn 2 1970 6
% dnk 1 1950 7
% dnk 1 1960 8
% dnk 1 1970 9
% dnk 2 1950 10
% dnk 2 1960 11
% dnk 2 1970 12
%
% The second parameter 'i' of this procedure is interpreted as a
row number of this data set (see the last column).
% For any 'i' between 1 and 12 this procedure returns indices of
factor variables stored in 'dsu'.
% If i = 9, for example, it returns [2 1 3] and corresponding
levels are
% di.population = {'dnk'};
% di.sex = 1;
% di.decade = 1970;
%
% Structure 'di' is returned as a second output argument.
%
% If no 'i' specified, the total number of unique combinations of
factor levels
% (number of rows in the normalized data set) is returned (12 in
this example).
%

% One of the important applications of this procedure is mapping of
multi-level nested loops into

% a single level loop. Suppose we need to loop over following
variables:

% dsu=[];

% dsu.population = {'jpn' 'dnk'};

% dsu.sex = [1 2];

% dsu.decade = [1950 1960 1970];

% This can be accomplished with the following code:

%

% n = dsfactorsidx(dsu)

% for i = 1:n

% [j, di] = dsfactorsidx(dsu)

% end

%

%
% Parameters:

% dsu - structure with unique factor levels

% i - row index of normalized data set
% Returns:

% j - indices of factor variables

% di - structure with combination of factor levels corresponding to j

% Example:
% Notes:

% This procedure doesn't create a normalized data set in memory so it
is very fast
% See also:
% Date of revision: 2002-11-27
% Copyright (c) Kirill Andreev


% get numbers of factor levels

f = fieldnames(dsu);

l = zeros(length(f), 1);

j = 1;

for k = 1:length(f)

l(k) = length(getfield(dsu, f{k}));

end

if nargin < 2

% return total number of unique combinations of factor levels

j = prod(l);

return

end

j = zeros(size(l));

m = rev(cumprod(rev(l))) ./ l;

for k = 1:length(l)

j(k) = ceil(i / m(k));

i = i - (j(k)-1) * m(k);

end


di = [];

k = 1;

for k = 1:length(f)

v = getfield(dsu, {1,1}, f{k}, {j(k)});

di = setfield(di, f{k}, v);

end

Will

unread,
Apr 30, 2003, 9:53:13 PM4/30/03
to
??? Undefined function or variable 'rev'.


Error in ==> C:\MATLAB6p5\work\multiloop.m (dsfactorsidx)
On line 126 ==> m = rev(cumprod(rev(l))) ./ l;


Error in ==> C:\MATLAB6p5\work\multiloop.m
On line 19 ==> [k, di] = dsfactorsidx(dsu, i);

Kirill Andreev

unread,
May 1, 2003, 10:37:44 AM5/1/03
to
Ok, going to profile..

function y = rev(x);
% rev(): reverses the order of the rows in a matrix
% Parameters:
% x - matrix to be reversed
% Date of revision: 2001-08-29


% Copyright (c) Kirill Andreev

y = flipud(x);


Will <was...@sina.com> wrote in message news:<eebce...@WebX.raydaftYaTP>...

Peter J. Acklam

unread,
May 1, 2003, 11:41:40 AM5/1/03
to
andr...@post.queensu.ca (Kirill Andreev) wrote:

> Ok, going to profile..
>
> function y = rev(x);
> % rev(): reverses the order of the rows in a matrix
> % Parameters:
> % x - matrix to be reversed
> % Date of revision: 2001-08-29
> % Copyright (c) Kirill Andreev
>
> y = flipud(x);

First of all, why do you need an alias for "flipud"? Why not
simply call "flipud" directly?

Secondly, "rev" is a very generic term and a function with such a
generic name ought to be more general than this.

Peter

--
I wish dialog boxes had a button saying "Whatever". I hate being
forced to answer "Yes" or "No" to a question I have no opinion on
whatsoever. There ought to be a button matching my indifference.

us

unread,
May 1, 2003, 11:51:02 AM5/1/03
to
Peter J. Acklam wrote:

> andr...@post.queensu.ca (Kirill Andreev) wrote:
>> function y = rev(x);
>> % rev(): reverses the order of the rows in a matrix
>> % Parameters:
>> % x - matrix to be reversed
>> % Date of revision: 2001-08-29
>> % Copyright (c) Kirill Andreev
>> y = flipud(x);
<SNIP>

> Secondly, "rev" is a very generic term and a function with such a
> generic name ought to be more general than this.


not <generic> anymore, peter:
the creator put a <copyright> on it!
us

Peter J. Acklam

unread,
May 1, 2003, 2:56:12 PM5/1/03
to
us <u...@neurol.unizh.ch> wrote:

For a pretty generic reversing function, try my "flipdims", which
is an extension of "flipdim" in two ways:

1) You can flip along as many dimensions as you want, as many
times as you want.

2) If no dimension is given, it flips along the first
non-singleton dimension.

As a special case: Given a vector, the vector is REVersed. :-)

http://home.online.no/~pjacklam/matlab/software/util/matutil/flipdims.m

Kirill Andreev

unread,
May 2, 2003, 9:06:18 AM5/2/03
to
No one needs such alias except people who has been using Gauss for
years like me. Just makes my life a little easier.. In my original
posting I tried to illustrate an idea with some working code.
--Kirill
Reply all
Reply to author
Forward
0 new messages