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

This is a greatly simplified script (that doesn't do anything here) used to report webpage hits. The honorable Monks stress the use of strict but I am confused about what to do in a situation where variables are created on the fly (as in the first 'for' loop) and then used in the second 'for' loop. It all works fine if I don't use strict. The page names to be tracked are not known until the data file is read.

#!/usr/bin/perl use strict; my $datafile = "data.txt"; my @now = localtime(time); open(FIL,"<$datafile") or die "Can't open data file: $!\n"; my @data = <FIL>; close(FIL); my %maxCount; foreach my $line (@data) { (my $timestamp, my $page, my $moredata) = split('\|',$line); my @hits = localtime($timestamp); # Here I create and preset an array with a variable name defined b +y $page @$page = (0,0,0,0) if ($$page[3] eq ''); if ($hits[7] == $now[7]) { $$page[0]++; } if ($hits[4] == $now[4]) { $$page[1]++; } if ($hits[5] == $now[5]) { $$page[2]++; } $maxCount{'$page'}++; } my @pages = keys %maxCount; # Gets list of page names my $dAry = ""; my $wAry = ""; my $mAry = ""; foreach my $page (@pages) { $dAry .= "$$page[0],"; $wAry .= "$$page[1],"; $mAry .= "$$page[2],"; } #... continued exit;

Is it possible to have for a hash array element contain a list array? (a possible solution)

Thanks in advance....

Replies are listed 'Best First'.
Re: Problems with 'strict' and variables created on the fly
by tadman (Prior) on Nov 11, 2002 at 03:12 UTC
    My advice is simply do not create variables on the fly. What you should be doing is using a hash:
    my %data; foreach my $line (@data) { my ($timestamp, $page, $moredata) = split('|', $line); my @hits = localtime($timestamp); $data{$page} = [0,0,0,0]; $data{$page}[0]++ if ($hits[7] == $now[7]); # ... (etc) }
    This is a lot more practical than having legions of separate variables.
Re: Problems with 'strict' and variables created on the fly
by FamousLongAgo (Friar) on Nov 11, 2002 at 03:23 UTC
    First, I would urge you to read Mark Jason Dominus's argument against symbolic references, it is pure wisdom.

    Your suggested solution of using a hash is a very good one:
    my @now = localtime( time ); my %container; # use this rather than a symbolic ref foreach my $line ( @data ) { my ( $timestamp, $page, $moredata ) = split /|/, $line; my @hits = localtime( $timestamp ); $container{$page}[0]++ if $hits[7] == $now[7]; $container{$page}[1]++ if $hits[4] == $now[4]; $container{$page}[2]++ if $hits[5] == $now[5]; $maxCount{$page}++; } my @pages = keys %maxCount; my ( @dAry, @wAry, @mAry ); foreach my $page ( keys %maxCount ) { push @dAry, $container{$page}[0]; push @wAry, $container{$page}[1]; push @mAry, $container{$page}[2]; } my $dAry = join ',', @dAry; my $wAry = join ',', @wAry; my $mAry = join ',', @mAry;

    Note that other monks may have advice about a better way to code what you are doing; I just wanted to show you how it could be rephrased to run under strict and be a little more idiomatic with minimal changes. Note that I use a join to create the three strings at bottom - this eliminates the trailing comma problem.

    Also, note that you can group my declarations in parentheses, minimizing visual clutter.

    I hope this is at least a start - kudos for turning on strict, for all the hassle at first, you'll find it saves you invaluable hassle down the line, when you make a typo in the middle of a thousand lines of code.

    Good luck!

Re: Problems with 'strict' and variables created on the fly
by graff (Chancellor) on Nov 11, 2002 at 03:31 UTC
    I'm a little confused here -- sorry... When you say "it works fine when I don't use strict", what exactly do you mean by "works fine"? Does it actually do what you intend it to do? Or is it "fine" just because it doesn't report any errors?

    I'm curious, because this part:

    (my $timestamp, my $page, my $moredata) = split('\|',$line);
    seems to conflict harshly with this part, which comes just two lines later:
    @$page = (0,0,0,0) if ($$page[3] eq '');
    BTW, that looks like you're doing a test on an uninitialized array element -- nothing could have been assigned to $$page[3] at the time it's evaluated in the "if" statement here, so the test is always true.

    Then there's this third part a few lines later, which could coexist with either of the previous two:

    $maxCount{'$page'}++;
    The first part assigns a string to a scalar variable $page. The second part treats $part as a reference to an anonymous array, and if this works, it's because the earlier string value has been replaced (obliterated) by the array reference. The third part "stringifies" the array reference and uses it as a hash key.

    So, okay, I guess it might work fine without "use strict"; the point is that strings read from input data have nothing to do with the names of variables here. When you use strict, the anon.array held in $page goes out of scope, has no other references to its content, and is reaped.

    I would propose an alternate form for the first for loop that would make more sense: creating a new array, and setting the value of a hash element to be a reference to that array, or something of that nature -- but I can't quite make out your intentions in that code... Still, here's a try:

    foreach my $line (@data) { (my $timestamp, my $page, my $moredata) = split('\|',$line); my @hits = localtime($timestamp); # Here I create and preset an array with a variable name defined b +y $page my @counters = (0,0,0,0); if ($hits[7] == $now[7]) { $counters[0]++; } if ($hits[4] == $now[4]) { $counters[1]++; } if ($hits[5] == $now[5]) { $counters[2]++; } $maxCount{$page} = \@counters; } my $dAry = ""; my $wAry = ""; my $mAry = ""; foreach my $page (keys %maxCount) { my @counters = @{$maxcount{$page}}; $dAry .= "$counters[0],"; $wAry .= "$counters[1],"; $mAry .= "$counters[2],"; }
    Let me stress that this suggestion only serves to make a sensible use of variables and references -- I have no confidence that it would actually do whatever it is you're trying to do.
Re: Problems with 'strict' and variables created on the fly
by BrowserUk (Patriarch) on Nov 11, 2002 at 03:26 UTC

    One way to do this is to use another hash.

    There are a few notes in the comments.

    #!/usr/bin/perl use strict; my $datafile = "data.txt"; my @now = localtime(time); open(FIL,"<$datafile") or die "Can't open data file: $!\n"; my @data = <FIL>; close(FIL); my %maxCount; my %pages; foreach my $line (@data) { # My is a function and will take a list arguement. my ($timestamp, $page, $moredata) = split('\|',$line); my @hits = localtime($timestamp); # Here I create and preset an array with a variable name defined b +y $page ### @$page = (0,0,0,0) if ($$page[3] eq ''); #Its not quite clear to me when $$page[3] gets set??? Maybe this i +s something like what you want? $pages{$page} = [] unless exists $pages{$page}; if ($hits[7] == $now[7]) { $pages{$page}[0]++; } if ($hits[4] == $now[4]) { $pages{$page}[1]++; } if ($hits[5] == $now[5]) { $pages{$page}[2]++; } $maxCount{'$page'}++; } # Not everyone would endorse this form of list initialisation my ($dAry, $wAry, $mAry) = ('','',''); foreach my $page (keys %pages) { $dAry .= "$pages{$page}[0],"; $wAry .= "$pages{$page}[1],"; $mAry .= "$pages{$page}[2],"; } #... continued exit;

    Nah! You're thinking of Simon Templar, originally played (on UKTV) by Roger Moore and later by Ian Ogilvy
Re: Problems with 'strict' and variables created on the fly
by Notromda (Pilgrim) on Nov 11, 2002 at 03:17 UTC
    Is it possible to have for a hash array element contain a list array? (a possible solution)

    Well, yes!

    Untested code:

    my %pages; $pages{"page1"} = [ 1,2,3]; $pages{"page2"} = [ 5,6,7]; print $pages{"page1"}->[1]; print $pages{"page2"}->[2];

    What you are looking for is a hash of arrays, which is a common perl idiom. Check out the perldsc and perllol manual pages.

Re: Problems with 'strict' and variables created on the fly
by pg (Canon) on Nov 11, 2002 at 03:23 UTC
    Generally speaking:
    1. It is better practice to use 'use strict' all the time. We Perl programmers should grab each opportunity to prove to others, that we can code with a style.
    2. As for whether you can nest hashes, arrays and lists. The answer is yes, as long as you be careful, and use references properly.
Re: Problems with 'strict' and variables created on the fly
by nedals (Deacon) on Nov 11, 2002 at 20:36 UTC
    Thank you, fellow monks!

    strict is now applied and all is working well. It took a little time but also highlighted a few not so obvious errors. Your responses also gave me some good tips and left me with a couple of questions.

    What's the difference between these two lines of code and why use the first when the second will work?

    (my $timestamp,my $page,my $userURL,etc) = split('\|',$line); my ($timestamp,$page,$userURL,etc) = split('\|',$line); # which is muc +h cleaner.
    Not everyone would endorse this form of list initialisation BrowserUk
    my ($var1,$var2,$var3) = ('','',''); my ($var4,$var5,$var6);
    This seems like a easy, clean way to initilize. Any comments? Also, can variables be declared as in the second line?

    BTW, that looks like you're doing a test on an uninitialized array element (graff)
    You're right!
    So!! Another good tip on an in-line initilizing technique. (BrowserUk)

    my %hits for (....) { .. $hits{$page} = [0,0,0,0] unless exists $hits{$page}; $hits($page}[0]++; .. }
    I see this piece of code. (Notromda).
    Could this also be written as on line 2 and what's the difference?
    print $pages{"page1"}->[1]; print $pages{"page1'}[1];