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

Hello all, I am kinda stuck with what I am trying to do. Here is my code.  I am creating a subroutine, which will be going into a larger script. Where I am stuck is trying to figure out the best to tackle to following problem.
#!/usr/bin/perl use strict; use Tie::File; use List::Util qw/shuffle/; my $line; my $i=0; my @array; my $count=0; my $alphaFile = 'alphabetFile.txt'; my $numberFile = 'numberFile.txt'; my $fileCounter = 'counter.txt'; my $tempFile = 'temp.txt'; open(FILECOUNTER,$fileCounter); $count = <FILECOUNTER>; chomp($count); &readfile(); sub readfile { tie @array, 'Tie::File', $numberFile || die("failed to open $numbe +rFile");; my %hashData; open(CHARFILE, $alphaFile) || die("failed to open $alphaFile"); if ($count == 5) { $count=0; unlink($tempFile); } else { $count++; } open(FILECOUNTER,">$fileCounter") || die("failed to open $fileCoun +ter"); print FILECOUNTER $count; close(FILECOUNTER); if (-e $tempFile) { open(TEMPFILE,"+<$tempFile") || die("failed to open $tempFile" +); while (<TEMPFILE>) { chomp; if ($_) { $_ =~ s/\s+//; $hashData{$_} = 1; } } } else { open(TEMPFILE,">>$tempFile") || die("failed to open $tempFile" +); } my $counter=0; tell(TEMPFILE); while ($line = <CHARFILE>) { chomp($line); if (exists $hashData{"$line$array[$i]"}) { next; } else { $hashData{"$line$array[$i]"} = 1; #print TEMPFILE "$line $array[$i]\n"; #print "$line $array[$i]\n"; if ("$line $array[$i]") { foreach ( $line ) { if ( $array[$i] ) { print $line . " "; print shuffle $array[$i] . "\n"; print TEMPFILE "$line $array[$i]\n" } else { print "\n" . $line . " " . rand "$array[$i]" . + "\n"; print TEMPFILE "$line $array[$i]\n" } } } } $i++; } print TEMPFILE "\n\n"; close(TEMPFILE); close(CHARFILE); untie @array; }
I am working with two sample files right now, just to get thing working.
alpha: has values 1-5, with 1 value per line
number: has values a-m, with 1 character per line.

Now what I need the code to do is the following:
print the data from $alphaFile with any of the numbers in $numberFile (random is fine, order does not really matter).

Once it prints to STDOUT, it should also keep track of what was printed. Which is where the tempFile comes in.  

Now the next time the script runs, it should check the tempFile before it print outs the data to STDOUT. It should check to make sure that the combinations that it is now printing, have not occurred during the last run.  It does this by checking the data stored in the tempFile.  It will keep track of the last five times the script has been run, along with what was printed. And during those five times the script should not print any of the same combinations.
for example: RUN1 output a 1 b 2 c 3 skipping… l 3 m 1 RUN2 output a 3 b 5 c 1 skipping… l 2 m 4 RUN3 output a 5 b 1 c 4 skipping… l 1 m 5
Each time the script runs it should not print the same combination of data. So it should, again, check the tempFile, where it stores each run and shuffle/rand the data until is unique (This is where I am having problems/Stuck).

I am having two main problems, which I am beating my head trying to figure. How to I get it to print a random number once all the numbers in the hash have been used? Right now it prints fine the first time, but once it gets to all the numbers in the hash it does not print a number, nor does it print any of the alphas. It currently only print 5 values after the first run, but prints all of them during the first run. I added a few new lines but they are decimal numbers not random numbers from the hash($numberFile)

Any help pointers would be greatly appreciated.  I think i've been playing with this for so long that i need a second pair of eyes.  
Thanks for all the help.

Replies are listed 'Best First'.
Re: Hash shuffle help
by jwkrahn (Abbot) on Nov 15, 2011 at 04:40 UTC
    #!/usr/bin/perl use strict; use Tie::File; use List::Util qw/shuffle/;

    You should also include the warnings pragma (it would have warned you about one of the lines of your program.)

    #!/usr/bin/perl use warnings; use strict; use Tie::File; use List::Util qw/shuffle/;


    my $line; my $i=0; my @array; my $count=0; my $alphaFile = 'alphabetFile.txt'; my $numberFile = 'numberFile.txt'; my $fileCounter = 'counter.txt'; my $tempFile = 'temp.txt'; open(FILECOUNTER,$fileCounter); $count = <FILECOUNTER>; chomp($count);

    Why are these lines in file scope?    They should be inside the subroutine.    You should also verify that open worked correctly.



    &readfile();

    There is no need to prepend an ampersand when using subroutine calls and it may cause unwanted side effects.



    tie @array, 'Tie::File', $numberFile || die("failed to open $numbe +rFile");;

    You should declare @array here where you first use it instead of at file scope.

    You are using the high precedence || operator which means that you are testing the boolean value of $numberFile NOT the success or failure of tie.

    tie my @array, 'Tie::File', $numberFile or die "failed to open $nu +mberFile";


    if (-e $tempFile) { open(TEMPFILE,"+<$tempFile") || die("failed to open $tempFile" +);

    There is no need to stat the file first, just let open do its job:

    if ( open TEMPFILE, '+<', $tempFile ) {


    my $counter=0;

    You never use this variable anywhere??



    tell(TEMPFILE);

    You are using tell in void context so this line is superfluous.    (The warnings pragma would have warned you of this.)



    while ($line = <CHARFILE>) {

    You should declare $line here where you first use it instead of at file scope:

    while ( my $line = <CHARFILE> ) {


    if ("$line $array[$i]") {

    This test is superfluous because the string "$line $array[$i]" is ALWAYS true.



    foreach ( $line ) {

    You are looping over a list with one element.    The foreach loop aliases $line to $_ but you are not using $_ inside the loop so it is superfluous.



    print shuffle $array[$i] . "\n";

    shuffle works on a list and returns a shuffled list but you are only passing shuffle a single element, the string "$array[$i]\n", so there is nothing really for shuffle to do, except return that one element.



    print "\n" . $line . " " . rand "$array[$i]" . + "\n";

    You have three problems there: 1) rand works with numbers but you are copying the contents of $array[$i] to a string, so perl has to convert that string to a number for rand to work, and: 2) because of the high precedence of the concatenation operator you are actually passing rand the string "$array[$i]\n" which means that: 3) the newline is not printed out as you may have expected.

    That will work correctly if you use the comma operator instead:

    print "\n", $line, " ", rand $array[ $i ], "\n +";


    You should also include the $! in your error messages when using open so you know why it failed.

      There is no need to prepend an ampersand when using subroutine calls and it may cause unwanted side effects.

      FUD much? :)

      Just testing in Production. Sorry, it shouldn't have let me post this.

Re: Hash shuffle help
by GrandFather (Saint) on Nov 15, 2011 at 00:43 UTC

    If you maintain the history in a YAML file then you can do something like:

    #!/usr/bin/perl use strict; use YAML qw(); my $kGenLines = 3; my $history = eval {YAML::LoadFile('history.yaml')} || {}; # Adjust history $history->{$_} && --$history->{$_} for keys %$history; #generate lines my @alphas = 'A' .. 'M'; my @nums = 1 .. 5; LINES: for (1 .. $kGenLines) { my @aPool = @alphas; my $pair; while (@aPool) { my @nPool = @nums; my $tryAlpha = splice @aPool, rand(@aPool), 1; while (@nPool) { my $tryNum = splice @nPool, rand(@nPool), 1; next if $history->{"$tryAlpha|$tryNum"}; @alphas = grep {$_ ne $tryAlpha} @alphas; @nPool = grep {$_ != $tryNum} @nPool; print "$tryAlpha $tryNum\n"; $history->{"$tryAlpha|$tryNum"} = 5; next LINES; } @alphas = grep {$_ ne $tryAlpha} @alphas; } } YAML::DumpFile('history.yaml', $history);
    True laziness is hard work