scotchfx has asked for the wisdom of the Perl Monks concerning the following question:

Greetings,
I'm a former hardware guy (embedded C & C++ experience mainly) trying to get up to speed with Java and Perl for a set of graduate courses.

I've written up a Perl script that simulates a scheduling algorithm, the script consists of two primary loops the first to parse process/event info from a file, the second to schedule each event. The first loop executes fine, but I'm having issues getting the script to run with my second loop - since I'm doing this in Perl (with no main block) I'm concerned it may have something to do with the scope of my vars (my three primary data structures are a 2d array used as an array of stacks, and two hashes used to reference event dependencies.

I am using Eclipse/EPIC/ActiveState Perl 5.8.6 to debug my script. I'm a noob at this, although I've had some experience using regular expressions/Perl in a Unix environment, admittedly at the moment I'm completely stuck...
Parse loop (appears to work fine):

foreach $line (<FILE>) { chomp $line; if ($line =~ m/(\d)-(\d)->(\d)-(\d)/) { #create local sender and receiver vars my $sp = "$1-$2"; my $rp = "$3-$4"; #update sender and receiver hashes $sender{$sp} = $rp; $receiver{$rp} = $sp; print "enter $sender{$sp}->$receiver{$rp} in dependency hashes +\n"; } elsif($line =~ m/(\d)-(\d)/) { #create local process var my $p = "$1-$2"; #queue process on @p_s multi-dimensional array unshift (@{$p_s[$1]},$p); print "unshift $p_s[$1][0] onto process array\n"; $numevents++; } else { print "\nunmatched line: $line \n"; chomp($line); #remove empty newlines in input file } }

Scheduling loop (script produces no output with this included)
while($timestamp <= $numevents) { print "\ntimestamp: $timestamp, numevents: $numevents\n"; foreach $pid (@p_s) { my $p_e = 0, $p_t = 0; #for every process pop the first event and examine it if($p_e = pop @{$p_s[$pid]}) { #if process-event has receiver-dependency then push it b +ack on the stack if($receiver{$p_e}) { push (@{$p_s[$pid]},$p_e); } else { my $s_o = 0; #else if process-event has sender-obligation then up +date sender and reciever hashes if($s_o = $sender{$p_e}) { delete $sender{$p_e}; delete $receiver{$s_o}; } #and then enqueue process-event with process-timesta +mp in schedule hash if($p_e =~ m/(\d)-(\d)/) { $p_t = "$timestamp.$1" } else { print "\nerror parsing processor-event signature +\n"; } $schedule{$p_e} = $p_t; unshift (@totalorder, $p_t); } } } $timestamp++; }

Since the timestamp of each event is it's occurance relative to every other event, I don't need any abosolute means of stamping each event, and I can say that my $timestamp < $numevents always. I do need to output the total order of events, as well as each event and it's timestamp, hence I create a third and fourth hash and array respectively, %schedule and @totalorder. I have created a subroutine check_data() that outputs all of the data stored in all of my major data structures, the definition for this is located after all of the code I have in what would be my main() block in C/C++.

Sorry for the lengthy description - anyone here have an idea why the first loop would execute but not the second?

Do I need to initialize my vars prior to use, ex:

$pid = 0; #process index @p_s = (); #2d process array/stack %sender = (); #sending event hash %receiver = (); #receiving event hash $numevents = 0; #total number of events in all processes $timestamp = 0; #timestamp for event scheduler %schedule = (); #hash of each event with timestamp @totalorder = (); #final order of each event

Should be declared before the first loop (I've tried this already w/o success but maybe I'm missing something)?

Will vars declared on the fly in the first loop be visible in the second loop?

I will append the full script at the end of this post for the reference of the more experienced perl-sons who are hopefully perusing this forum...

################### #Lamport.pl ################### #!/usr/bin/perl -w #begin main routine #$pid = 0; #process index #@p_s = (); #process stack #%sender = (); #sending event hash #%receiver = (); #receiving event hash #$numevents = 0; #total number of events in all processes #$timestamp = 0; #timestamp for event scheduler #%schedule = (); #= Tie::IxHash->new(); #@totalorder = (); #read process/event input from file open FILE, $ARGV[0] or die "$ARGV[0] can't be opened:\n$!"; #local $/; # Set input to "slurp" mode. #initialize process/event stack/hash from input file print "\nbegin Lamport's algorithm for input file $ARGV[0]\n"; foreach $line (<FILE>) { chomp $line; if ($line =~ m/(\d)-(\d)->(\d)-(\d)/) { #create local sender and receiver vars my $sp = "$1-$2"; my $rp = "$3-$4"; #update sender and receiver hashes $sender{$sp} = $rp; $receiver{$rp} = $sp; print "enter $sender{$sp}->$receiver{$rp} in dependency hashes +\n"; } elsif($line =~ m/(\d)-(\d)/) { #create local process var my $p = "$1-$2"; #queue process on @p_s multi-dimensional array unshift (@{$p_s[$1]},$p); print "unshift $p_s[$1][0] onto process array\n"; $numevents++; } else { print "\nunmatched line: $line \n"; chomp($line); #remove empty newlines in input file } } print "\ncompleted $ARGV[0] parsing\n"; close FILE; check_data(); #schedule process events according to Lamport's algorithm while($timestamp <= $numevents) { print "\ntimestamp: $timestamp, numevents: $numevents\n"; foreach $pid (@p_s) { my $p_e = 0, $p_t = 0; #for every process pop the first event and examine it if($p_e = pop @{$p_s[$pid]}) { #if process-event has receiver-dependency then push it b +ack on the stack if($receiver{$p_e}) { push (@{$p_s[$pid]},$p_e); } else { my $s_o = 0; #else if process-event has sender-obligation then up +date sender and reciever hashes if($s_o = $sender{$p_e}) { delete $sender{$p_e}; delete $receiver{$s_o}; } #and then enqueue process-event with process-timesta +mp in schedule hash if($p_e =~ m/(\d)-(\d)/) { $p_t = "$timestamp.$1" } else { print "\nerror parsing processor-event signature +\n"; } $schedule{$p_e} = $p_t; unshift (@totalorder, $p_t); } } } $timestamp++; } check_data(); #create "totalorder" copy of schedule hash #pop totalorder as stack and print #sort and print schedule hash #end MAIN routine #begin subroutine definitions sub check_data { #print "numprocs = $numprocs\n"; print "\ntimestamp = $timestamp\n"; print "numevents = $numevents\n"; print "\ncontents of process stack:\n"; foreach my $r (@p_s) { my $j=0; foreach my $c (@{$r}) { print "P$i j: $j Value: $c\n"; $j++; } $i++; print "\n"; } print "\nsender key/value pairs:\n"; foreach my $k (sort keys %sender) { print "$k => $sender{$k}\n"; } print "\nreceiver key/value pairs:\n"; foreach my $k (sort keys %receiver) { print "$k => $receiver{$k}\n"; } print "\nschedule key/value pairs:\n"; foreach my $k (sort keys %schedule) { print "$k => $schedule{$k}\n"; } print "\ncontents of totalorder queue:\n"; foreach my $r (@totalorder) { print "$r, "; } print "\n\n"; }

Replies are listed 'Best First'.
Re: simple script question scope/structure for Perl noob
by GrandFather (Saint) on Jun 26, 2005 at 21:59 UTC

    For a start put:

    use strict; use warnings;

    at the start of your script.

    By the time you have removed all the warnings and errors your problem will have changed!

    Some sample data would help. You may like to use <DATA> rather than <FILE> and put a __DATA__ section at the end of your script.

    Update: Add sample data comment


    Perl is Huffman encoded by design.

      Well, at least use strict; I would think the warnings part would be covered by the #!/usr/bin/perl -w line.

          -Bryan

        In this case, I'd put use warnings anyway. Note that OP's shabang does not start from the very first character in the script, because there are some lines of comment before (and an empty one, too). Thus, the -w switch is likely to be ignored in this case.

        Flavio
        perl -ple'$_=reverse' <<<ti.xittelop@oivalf

        Don't fool yourself.

        My eye skipped over it. Can't even blame coffee lack :^(


        Perl is Huffman encoded by design.
Re: simple script question scope/structure for Perl noob
by GrandFather (Saint) on Jun 26, 2005 at 22:34 UTC

    I turned the commet #'s into my's in your "declaration" block, added declarations for $line (before the input loop) and $i (in sub check_data) and changed the my $p_e = 0, $p_t = 0; line into two "my" lines. Running the result against a data file containing:

    0-0->0-1 0-0->0-2

    gave the following output:

    I don't know what it means, but the second loop sure is running.


    Perl is Huffman encoded by design.
      you guys... Rock!

      Thanks for all the re's in so short a time - I was thinking I'd be lucky to get one or two by the end of the day:)

      I've added "use strict;" and "use warnings;" and have declared all my global vars "our", all local block vars "my" etc..

      Grandfather is correct with his sample of my input - here is an extended snippit for further clarity:

      1-1 1-2 1-3 1-4 1-2->2-1 1-4->3-4 2-1 2-2 2-3 2-4 2-2->1-3 2-4->3-3 3-1 3-2 3-3 3-4 3-2->2-3

      the /\d-\d/ lines are individual events to be unshift'd to (and later pop'd from) the @p_s 2D-array, and /\d-\d->\d-\d/ are dependencies to be added to the %sender and %receiver hashes...

      Currently Eclipse is throwing the following error:

      Use of uninitialized value in array element at C:/Lamport.pl line 69.

      The error refers specifically to the following lines:

      foreach $pid (@p_s) { #...rest of scheduler loop }

      I ran this loop alone with only a print "$pid\n"; statement and got a set of ARRAY(0x186b8f8) values for $pid. I think that I've been to liberal in my use of foreach() and will restructure this more intelligently.

      I'm curious why the loop seems to execute for Grandfather though...? Perhaps it is because his test case was limited to a single process/stack....

      Any suggestions on an good way to iterate through the first level (@p_s[]) of a 2D array? The trick is that I need to get an index for all of the @p_s[$pid][#pop'd] stacks one at a time, (each $pid stack is pop'd once one level at a time then the process is repeated)? I think I can do this with a regular while loop provided that I count the number of processes while parsing the input, however is there a more elegant way of doing this?

      Many Thanks, Thanks and Thanks again to all of you for the quick responses & insight - ya'll have helped me immensely already.

Re: simple script question scope/structure for Perl noob
by Zaxo (Archbishop) on Jun 27, 2005 at 03:04 UTC

    This comment has nothing to do with your difficulties (except that we'll spot the source of one), just some notes on more idiomatic perl for your read loop.

    First the preliminaries,

    #!/usr/bin/perl use warnings; use strict; our (%sender, %receiver, @p_s};

    You open a data file from a path given on the command line. There is an implicit mechanism in perl - the diamond operator with default argument - which does that, allowing you to skip opening the file. You were slurping the file, but that makes your loop run only once, with match only catching the first instance. Let's read it line by line with a while loop, and accept the default loop variable, $_, which allows us to use default match bindings and argument to chomp.

    while (<>) { chomp;
    You're not changing notation matching (\d)-(\d) and pasting the digits back together with "-", so let's keep them together and eliminate a couple of variables.
    if (m/(\d-\d)->(\d-\d)/) { $sender{$1} = $2; $receiver{$2} = $1;
    I'll change diagnostic prints to warns, so they go over STDERR.
    warn "enter ", "$sender{$1}->$receiver{$2} ", "in dependency hashes\n"; }
    More tricks follow with $1 and $2:     elsif (m/((\d)-\d)/) { I'm inclined towards push and shift in making a queue, but we'll stick with what you have,
    unshift @{$p_s[$2]}, $1; warn "unshift $1 onto process array\n";
    I'll skip the $numprocess variable entirely; you can get that from the arrays in scalar context.
    } else { warn "unmatched line: $_\n"; } } # . . .
    That's your code up through the read loop.

    Using the empty diamond has two effects which may not suit you. First, every file listed on the command line will be read and parsed in order. Second, if none are listed there the diamond will look at STDIN, either a pipe or the terminal. Both are useful if you design for them, otherwise they can be a source of surprise.

    After Compline,
    Zaxo

Re: simple script question scope/structure for Perl noob
by flachschippe (Novice) on Jun 26, 2005 at 22:27 UTC
    Add use strict; use warnings; to the start of your program. Add declarations until it runs without warnings.

    The scope of variables defined with my ends at the next closing brace. The scope of variables defined 'on the fly' (which is forbidden with use strict) ends at the end of the file.

    > Will vars declared on the fly in the first loop be visible in the second loop?
    Yes.

    > Perl (with no main block)
    It is good practice to put all your top-level code in a sub main($) { .. } and put main(\@_); at the end of your file; precisely because it separates the scope of variables local to the concepual or actual main from the purposefully global variables (which I assume you want to use to communicate between the loops).

    What does "I'm having issues getting the script to run" mean? What is the exact error message?

    In order to simply try your program, an input file would be needed, consider providing an example.

    Also, your program is a bit long for debugreading, please try to reduce your problem by elimination.

    Finally, please read How do I post a question effectively?