Random Functionality

Discussion in 'Perl Misc' started by Scott Bryce, Jan 13, 2005.

  1. Scott Bryce

    Scott Bryce Guest

    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";
    };
    }
     
    Scott Bryce, Jan 13, 2005
    #1
    1. Advertising

  2. Scott Bryce

    Anno Siegel Guest

    Scott Bryce <> wrote in comp.lang.perl.misc:
    > 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
     
    Anno Siegel, Jan 13, 2005
    #2
    1. Advertising

  3. Scott Bryce

    Scott Bryce Guest

    Anno Siegel wrote:

    > 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.
     
    Scott Bryce, Jan 13, 2005
    #3
  4. Scott Bryce

    Guest

    Scott Bryce <> wrote:
    > 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";
    > };
    > }


    --
    -------------------- http://NewsReader.Com/ --------------------
    Usenet Newsgroup Service $9.95/Month 30GB
     
    , Jan 13, 2005
    #4
  5. Scott Bryce

    Scott Bryce Guest

    wrote:

    > 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.
     
    Scott Bryce, Jan 13, 2005
    #5
  6. Scott Bryce

    Scott Bryce Guest

    Jim Gibson wrote:

    > 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.
     
    Scott Bryce, Jan 13, 2005
    #6
  7. Scott Bryce wrote:

    > Anno Siegel wrote:
    >
    >> 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;


    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.

    >> 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.


    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.
     
    Brian McCauley, Jan 13, 2005
    #7
  8. Scott Bryce

    Scott Bryce Guest

    Brian McCauley wrote:

    > 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.

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

    >


    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!
     
    Scott Bryce, Jan 13, 2005
    #8
  9. At 2005-01-13 12:21PM, Scott Bryce <> wrote:
    > 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.

    --
    Glenn Jackman
    NCF Sysadmin
     
    Glenn Jackman, Jan 13, 2005
    #9
  10. Scott Bryce

    Bart Lateur Guest

    Scott Bryce wrote:

    >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);

    --
    Bart.
     
    Bart Lateur, Jan 13, 2005
    #10
  11. Scott Bryce

    Guest

    Scott Bryce <> wrote:
    > wrote:
    >
    > > 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.


    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

    --
    -------------------- http://NewsReader.Com/ --------------------
    Usenet Newsgroup Service $9.95/Month 30GB
     
    , Jan 13, 2005
    #11
  12. Scott Bryce

    Scott Bryce Guest

    wrote:

    > 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";
    }
     
    Scott Bryce, Jan 13, 2005
    #12
  13. Scott Bryce wrote:

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

    >
    > 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.
     
    Brian McCauley, Jan 14, 2005
    #13
  14. Scott Bryce

    Scott Bryce Guest

    Brian McCauley wrote:

    > 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.
     
    Scott Bryce, Jan 14, 2005
    #14
    1. Advertising

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

It takes just 2 minutes to sign up (and it's free!). Just click the sign up button to choose a username and then you can ask your own questions on the forum.
Similar Threads
  1. Darren Clark

    Random NOt random?

    Darren Clark, Jun 24, 2004, in forum: ASP .Net
    Replies:
    3
    Views:
    490
    mikeb
    Jun 24, 2004
  2. Maziar Aflatoun

    Random not really random...

    Maziar Aflatoun, Aug 4, 2004, in forum: ASP .Net
    Replies:
    4
    Views:
    26,796
    Maziar Aflatoun
    Aug 5, 2004
  3. Replies:
    2
    Views:
    385
    Rolf Magnus
    Apr 16, 2006
  4. globalrev
    Replies:
    4
    Views:
    818
    Gabriel Genellina
    Apr 20, 2008
  5. VK
    Replies:
    15
    Views:
    1,331
    Dr J R Stockton
    May 2, 2010
Loading...

Share This Page