Random Functionality

S

Scott Bryce

I want to create a script that has random functionality.

I want the script to randomly choose between an unknown number of
subroutines and run one of them. By "unknown" I mean that the neither
names of the subroutines, the references to the subroutines, nor the
number of subroutines are hard coded into the script, but are determined
at runtime. This will allow me to add additional functions to the list
of possible random functions without having to update any other portion
of the script.

If that is as clear as mud, the code sample below demonstrates what I
want to do.

I came up with this. Is there a better way to do it?



use strict;
use warnings;

my @sub_list;

print &{$sub_list[int(rand(@sub_list))]};

BEGIN
{
push @sub_list, sub
{
return "Sub One was chosen\n";
};


push @sub_list, sub
{
return "Sub Two was chosen\n";
};


push @sub_list, sub
{
return "Sub Three was chosen\n";
};
}
 
A

Anno Siegel

Scott Bryce said:
I want to create a script that has random functionality.

I want the script to randomly choose between an unknown number of
subroutines and run one of them. By "unknown" I mean that the neither
names of the subroutines, the references to the subroutines, nor the
number of subroutines are hard coded into the script, but are determined
at runtime. This will allow me to add additional functions to the list
of possible random functions without having to update any other portion
of the script.

If that is as clear as mud, the code sample below demonstrates what I
want to do.

I came up with this. Is there a better way to do it?



use strict;
use warnings;

my @sub_list;

print &{$sub_list[int(rand(@sub_list))]};

BEGIN
{
push @sub_list, sub
{
return "Sub One was chosen\n";
};


push @sub_list, sub
{
return "Sub Two was chosen\n";
};


push @sub_list, sub
{
return "Sub Three was chosen\n";
};
}

That code fulfills almost none of your requirements. The number of
subroutines is hard-coded (it is 3), as are their references. You only
avoid hard-coding their names by not giving them any.

Further, if you were to change the code above, a program running
the previous version would blithely ignore your changes. You're
not even close.

If you want the code to change at run-time (not a good idea in general),
you'll have to compile it at run-time. See "perldoc -f do" and "perldoc
-f eval" for alternative ways to do that.

Anno
 
S

Scott Bryce

Anno said:
That code fulfills almost none of your requirements. The number of
subroutines is hard-coded (it is 3), as are their references. You only
avoid hard-coding their names by not giving them any.

Then I must have done a poor job of describing my requirements.

What I want to avoid is having to include a chunk of code like:

my $i = int(rand(3)); # This line has the number hard coded

Sub_1() if $i == 0; # These lines have sub names hard coded.
Sub_2() if $i == 1;
Sub_3() if $i == 2;



If I add a Sub_4, the code above would have to be changed to reflect
that. I want to avoid having to make that kind of change to the code. I
have found from working in other environments that this approach is
prone to errors when the code is maintained.

If you want the code to change at run-time (not a good idea in general),
you'll have to compile it at run-time. See "perldoc -f do" and "perldoc
-f eval" for alternative ways to do that.

My original approach involved eval'ing the contents of external files. I
found this approach difficult to maintain.
 
X

xhoster

Scott Bryce said:
I want to create a script that has random functionality.

I want the script to randomly choose between an unknown number of
subroutines and run one of them. By "unknown" I mean that the neither
names of the subroutines, the references to the subroutines, nor the
number of subroutines are hard coded into the script,

I started using a Nerf-brand keyboard. Now none of the features in
my programs are hard-coded.
but are determined
at runtime. This will allow me to add additional functions to the list
of possible random functions without having to update any other portion
of the script.

What do you mean any "other" portion of the script? If you are modifying
any portion of your scipt in the first place, that is hard-coding, isn't
it?
If that is as clear as mud, the code sample below demonstrates what I
want to do.

But it is at odds with your description above. Which should we trust?
I came up with this. Is there a better way to do it?

Assuming that what it does is what you want it to do, I think it is just
fine. I might get rid of the push and go with a format more like:

@sub_list = (
sub {adfsf},
sub {lkqjer},
sub {lkjwre;lk},
);


Xho

use strict;
use warnings;

my @sub_list;

print &{$sub_list[int(rand(@sub_list))]};

BEGIN
{
push @sub_list, sub
{
return "Sub One was chosen\n";
};

push @sub_list, sub
{
return "Sub Two was chosen\n";
};

push @sub_list, sub
{
return "Sub Three was chosen\n";
};
}
 
S

Scott Bryce

But it is at odds with your description above. Which should we trust?

The fault lies in the description of the problem. I am finding it
difficult to describe exactly what I want to do. I attempted to clarify
the problem description in my reply to Anno. If the problem description
is still unclear, I guess I'm on my own.

The code does what I want it to do. I want to know if there is a better
way to do it than what I wrote.
 
S

Scott Bryce

Jim said:
Maybe you can put Perl files defining functions in a directory, scan
the directory, get the subroutine names from the files, 'do' the files,
put the subroutine names and references in a hash, get an array of hash
keys, and select one at random to execute.

My original approach was similar to this. I put Perl files containing a
chunk of code (not in subroutines. Perhaps my implementation was bad?)
in a directory, scanned the directory, created a list of file names,
randomly selected one file name from the list, and eval'ed the contents
of the file.

I found this approach difficult to maintain.

I would prefer that the functions in question be in the script, rather
than residing in external files.

I want the code that selects one subroutine to work regardless of the
number of subroutines.
 
B

Brian McCauley

Scott said:
Then I must have done a poor job of describing my requirements.

What I want to avoid is having to include a chunk of code like:

my $i = int(rand(3)); # This line has the number hard coded

Sub_1() if $i == 0; # These lines have sub names hard coded.
Sub_2() if $i == 1;
Sub_3() if $i == 2;

Sometimes symbolic CODErefs are the right tool.
{
no strict 'refs';
( 'Sub_' . ( $i + 1 ) )->();
}
If I add a Sub_4, the code above would have to be changed to reflect
that. I want to avoid having to make that kind of change to the code. I
have found from working in other environments that this approach is
prone to errors when the code is maintained.

You can find all the functions with a prefix 'Sub_' by analysing the
symbol table. However rather than using a prefix string it's probably
better to use a separate symbol table (package) and simply find _all_
the subrouines in that symbol table.
My original approach involved eval'ing the contents of external files. I
found this approach difficult to maintain.

Can you explain the nature of the difficulty.

Usually I'd do something like:

{
# This package is used only for the handlers defined in handlers.pl
package MyModule::Handlers;
# handlers.pl does not countain a package directive
do 'handlers.pl';
}

my @sub_list =
map { \&$_ }
grep { exists(&$_) }
map {"MyModule::Handlers::$_"}
sort keys %MyModule::Handlers::;

Note \&$_ and exists(&$_) are exempt from strict refs precisely so that
you can do stuff like this.
 
S

Scott Bryce

Brian said:
Sometimes symbolic CODErefs are the right tool.

I would not have thought of that.

However rather than using a prefix string it's probably
better to use a separate symbol table (package) and simply find _all_
the subrouines in that symbol table.

OK, I'm with you.

Brian McCauley responded:
Can you explain the nature of the difficulty.

It was probably more a problem with my implementation. I had code in the
external files that was dependent on code in the script. That got messy.
Also, the whole project would eventually have hundreds of these external
files in use by close to 100 scripts. I would prefer not to have to
manage all of them.

Usually I'd do something like:

{
# This package is used only for the handlers defined in handlers.pl
package MyModule::Handlers;
# handlers.pl does not countain a package directive
do 'handlers.pl';
}

my @sub_list =
map { \&$_ }
grep { exists(&$_) }
map {"MyModule::Handlers::$_"}
sort keys %MyModule::Handlers::;

Note \&$_ and exists(&$_) are exempt from strict refs precisely so that
you can do stuff like this.

Unfortunately, that is a little over my head. I'll spend some time
looking at the docs and see if I can figure out what that code is doing.

It occurs to me that the package containing the subroutines in question
could be in the script itself rather than in an external file.

This gives me something to work with. Would this be better than pushing
references to anonymous subroutines on to a list (as in the example code
I posted in the OP), or is this just another way to do it? To answer
that question, I would probably have to define "better."

Thanks!
 
G

Glenn Jackman

At 2005-01-13 12:21PM said:
What I want to avoid is having to include a chunk of code like:

my $i = int(rand(3)); # This line has the number hard coded

Sub_1() if $i == 0; # These lines have sub names hard coded.
Sub_2() if $i == 1;
Sub_3() if $i == 2;

If I add a Sub_4, the code above would have to be changed to reflect
that. I want to avoid having to make that kind of change to the code. I
have found from working in other environments that this approach is
prone to errors when the code is maintained.


One approach is to maintain a list of subroutine references:
my @subrefs = ( \&sub_1, \&sub_2, \&sub_3, ...);
and invoke a random one like:
$subrefs[rand @subrefs]->();

If that's too much maintainance, have the script read itself to find
subroutines:

use strict;
use warnings;

# find subroutines in this script
my @subrefs;
open my $self, $0 or die "can't open $0: $!\n";
while (<$self>) {
# ignore lines up to the delimiter
next if 1 .. /^### read these subs ###/;

next unless /^sub (\S+)/;
push @subrefs, \&$1;
}
close $self;

# call a random subroutine
$subrefs[rand @subrefs]->();

sub not_a_random_subroutine {}
### read these subs ###
sub Sub_1 {print "1\n";}
sub secondsub {print "2\n";}
sub the3rd {print "3\n";}
#...

When you add new subroutines, just add them below the delimiting comment.
 
B

Bart Lateur

Scott said:
What I want to avoid is having to include a chunk of code like:

my $i = int(rand(3)); # This line has the number hard coded

Sub_1() if $i == 0; # These lines have sub names hard coded.
Sub_2() if $i == 1;
Sub_3() if $i == 2;


$subs[int rand @subs]->();

where

@subs = (\&Sub_1, \&Sub_2, \&Sub_3);
 
X

xhoster

Scott Bryce said:
The fault lies in the description of the problem. I am finding it
difficult to describe exactly what I want to do. I attempted to clarify
the problem description in my reply to Anno. If the problem description
is still unclear, I guess I'm on my own.

I posted my reply before your response to Anno reached me. It is clearer
what you want now.

The code does what I want it to do. I want to know if there is a better
way to do it than what I wrote.

Well, I still think populating the array all at once is better than the
sequence of pushes you have, but other than that I think your way is fine.

But it is not possible to say what is better or best for you. While some
methods are clearly inferior to others, once you remove all the dreck you
are left with several good methods, and which of those is the best is
extremely sensitive on the minutiae of your environment and usage patterns.

Are you having some kind of difficulty with the method you are currently
using? If not, you don't need something better. If so, what is/are the
problem(s) you are having?

Xho
 
S

Scott Bryce

Are you having some kind of difficulty with the method you are currently
using? If not, you don't need something better. If so, what is/are the
problem(s) you are having?

I don't have a lot of experience working with references in Perl. The
code I posted was hacked together rather quickly last night. I was
reading about creating references to anonymous subroutines, and that led
me to write the code. It "works," but I was wondering if someone with
more Perl experience could suggest a better approach.

So this isn't a "I can't make this work" question, but a "can you review
my code?" question. Or, to borrow a word you used in your post, "Is this
approach dreck compared to what might be better approaches?"

Right now I'm leaning toward the approach suggested by Brian McCauley,
unless someone knows a good reason why I shouldn't be grabbing the list
of references from the symbol table.

So the latest rendition of the code looks like this:



use strict;
use warnings;

my @sub_list =
map { \&$_ }
grep { exists(&$_) }
map {"Handlers::$_"}
keys %::Handlers::;

print $sub_list[int rand @sub_list]->('stuff');

package Handlers;

sub One
{
return "Sub One was chosen.\n";
}

sub Two
{
return "Sub Two was chosen.\n";
}

sub Three
{
return "Sub Three was chosen.\n";
}

sub Four
{
my ($string) = @_;

return "You passed $string into sub Four.\n";
}
 
B

Brian McCauley

Scott said:
Unfortunately, that is a little over my head. I'll spend some time
looking at the docs and see if I can figure out what that code is doing.

Working backwards..
(*) get all the symbol names in the MyModule::Handlers package
(*) prefix them to make them fully qualified symbol names
(*) filter down to only those symbols declared as subroutines
(*) get CODErefs for those subroutines
(*) store them in an array
It occurs to me that the package containing the subroutines in question
could be in the script itself rather than in an external file.

No, but I'd (perhaps mistakenly) got the impression that these were
something that is configured on the custommer site rather than being an
integral part of the module you were writing.

If the subroutines are logically part of the module you're writting then
they can simply go in the same file but it would seem a tidy division to
put then in their own .pm file anyhow.
This gives me something to work with. Would this be better than pushing
references to anonymous subroutines on to a list (as in the example code
I posted in the OP), or is this just another way to do it? To answer
that question, I would probably have to define "better."

This is just another way. IMNSHO it makes the source look neater. The
big advantage of named subroutines (particularly if they have mnemonic
names rather than just Sub_1, Sub_2 etc) is that their names show up in
stack dumps.
 
S

Scott Bryce

Brian said:
Working backwards..
(*) get all the symbol names in the MyModule::Handlers package
(*) prefix them to make them fully qualified symbol names
(*) filter down to only those symbols declared as subroutines
(*) get CODErefs for those subroutines
(*) store them in an array

Thanks.

I already figured this out. Working *backwards* was the key. I was
trying to work forward. That and I'm not very familiar with map, grep or
the symbol table. This was a good opportunity for me to learn something new.
No, but I'd (perhaps mistakenly) got the impression that these were
something that is configured on the custommer site rather than being
an integral part of the module you were writing.

What I am trying to do is write a story problem generator. The end
result will be a PDF file with a page full of story problems having a
similar level of difficulty--for example: all require 1 digit addition
to solve the problem.

The individual subroutines will each generate one story problem from a
template. Each subroutine has a different template. The subroutine fills
out the template from randomly generated data and returns the story problem.

The script will find the subroutines that contain the story problem
templates, build a list of references to these subroutines, shuffle the
list, then call them one at a time until the page is full.

Each of the templates is specific to its own script. That is why I think
it would be easier to have them in the script.

That is more than you needed to know, but it gives you a clearer picture
of what I'm doing.
This is just another way. IMNSHO it makes the source look neater.

I agree, which is why I'm leaning toward this method.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top