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

I'm attempting to store hash names in an array and then interpolate them in a loop. Is this even possible? My data is an array with the data arranged in groups for each value.

So $a{1}=5.5, $a{2}=3.2, $b{1}=15.0, $b{2}=-22, $c{1}=.02 and $c{2}=.15.

Here's what I'm attempting to do:

my @fields = qw(a b c); my @input = qw( 5.5 3.2 15.0 -22 .02 .15); for ($i=0; $i<=2; $i++) { for ($j=1; $j<=2; $j++) { $${fields[$i]}{$j} = $input[((2*$i)+$j)-1]; } } for ($i=0; $i<=2; $i++) { for ($j=1; $j<=2; $j++) { print $fields[$i] . "{" . $j . "} -> " . $${fields[$i]}{$j}, "\n +"; } } printf "%.1f\t%.1f\t%.1f\n", $a{1}, $b{1}, $c{1};

The data gets entered into the hashes, though I'm not so sure now. If I run it, it prints the for loop correctly. This croaks when it gets to the printf line with a couple of "uninitialed value" warnings and prints out zeros.

Can I use the hashes by name? Do they only exist elements of the array they're in? Do I need to do my homework and look up references? I was hoping to be clever and get my way out of that (being inexperienced and all...)

Thanks for any answers

- chris

Replies are listed 'Best First'.
Re: Interpolating an array element as a hash name.
by saintmike (Vicar) on Mar 15, 2004 at 04:41 UTC
    Using the names of hashes (or of any other variables) to implement a nested data structure is almost always a bad idea.

    Instead, make use of Perl's nested data structures -- they're very easy to use.

    Instead of storing the name of a hash in $fields[0] and then going $${fields[$i]}{$j}, you could just as easily create a hash %hash (use a name appropriate to your application instead ;) containing the sub-hashes as values (strictly speaking, it stores references): $hash{$fields[$i]}{$j} is easier to debug because it shows the nature of the nested data structure.

    With this in mind, your program looks like this:

    my @fields = qw(a b c); my @input = qw( 5.5 3.2 15.0 -22 .02 .15); my %hash; for (my $i=0; $i<=2; $i++) { for (my $j=1; $j<=2; $j++) { $hash{$fields[$i]}{$j} = $input[((2*$i)+$j)-1]; } } for (my $i=0; $i<=2; $i++) { for (my $j=1; $j<=2; $j++) { print $fields[$i] . "{" . $j . "} -> " . $hash{$fields[$i]}{$j}, + "\n +"; } } printf "%.1f\t%.1f\t%.1f\n", $hash{a}{1}, $hash{b}{1}, $hash{c}{1};

    --saintmike

Re: Interpolating an array element as a hash name.
by Sidhekin (Priest) on Mar 15, 2004 at 04:48 UTC

    You are accidentally using real references already. Your data is by chance stored in HASHREFs stored in an array referenced by $fields. I am amazed. :-)

    As you say you are inexperienced, there are two standard tips I ought to give: (1) use strict; -- always use strict; (2) Do not try to access variables by name. This is called using symbolic references (as opposed to real reaferences), and you should not use them until you know why you should not use them.

    As noted, it is by chance, but your program will do what you want if you just use this sprintf line instead:

    printf "%.1f\t%.1f\t%.1f\n", $fields->[0]{1}, $fields->[1]{1}, $fields +->[2]{1};
    ... but you don't want to do that. You want to use strict and write proper code. And you want to know what you are doing. At least you should be able to explain why there is such a weird mix of different braces in that expression of mine. perldoc perlreftut is as good a place as any to start.

    print "Just another Perl ${\(trickster and hacker)},"
    The Sidhekin proves Sidhe did it!

Re: Interpolating an array element as a hash name.
by graff (Chancellor) on Mar 15, 2004 at 05:04 UTC
    Do I need to do my homework and look up references?

    Well, since you're using references already, you might as well go ahead and learn how to use them properly, so that you avoid the evil, insanity-inducing idea of interpolating variable names.

    I assume you're showing us a toy example that has very little to do with an application where you believe this problem is coming up. But since you didn't say anything about your real application, let's try to see why you think the following approach poses any sort of problem for the inexperienced programmer:

    my %hash; my @topkeys = qw/a b c/; my @innerkeys = qw/1 2/; my @values = qw/5.5 3.2 15.0 -22 .02 .15/; for my $k ( @topkeys ) { for my $i ( @innerkeys ) { $hash{$k}{$i} = shift @values; } } print join "\t", (map { sprintf("%.1f",$hash{$_}{1}) } qw/a b c/), "\n +"; # or, if "map" is too tough for the inexperienced: printf( "%.1f\t", $hash{$_}{1} ) for ( qw/a b c/ ); print "\n";
Re: Interpolating an array element as a hash name.
by TomDLux (Vicar) on Mar 15, 2004 at 05:03 UTC

    Your brain is still doing C, C++, Java, whatever you used to do before perl.

    While C style for loops do work in Perl, it's better to think of loops in terms of lists, arrays, sets:

    for my $i ( 0..2 ) { for my $j ( 0..2 ) { ... } }

    But why do you have these hashes named $a{0}, $a{1}, $a{2}? Those sound like arrays, not hashes. In fact, I kept accidentally using square brackets instead of curlies, and then had to repair them.

    Better yet, why not start thinking in meaningful terms, for example %expense, %income, %profits over the keys qw( January February March .... ). Hmmm ... still sounds like arrays.

    What are you really trrying to achieve? As opposed to how you think you need to implement it ...?

    --
    TTTATCGGTCGTTATATAGATGTTTGCA

      Thanks for the style tip. Yes, started out in C and I'm still getting used to thinking in perl.

      As far as the array names, they were used for ease of input for this problem only. Their real names look like: $high_price, $low_price, etc.

      As to what I want to achieve; the data I'm reading in consists of a file with many different sets of input all smashed together. Ten years of a stock's high price then ten years of a stock's low price and then ten years of a stock's something else, etc. I figured that using an array with the elements in order of their appearance in the data would make it easy for me to get to by reading the index of the array and multiplying by 10 (number of years) and then doing a loop to add the data into the hash ($high_price{1995}=52.50).

      I'm writing my first, uh, program with perl. I'm a system administrator and I live off of one-liners and one-off scripts, so my ability to do this in perl is being stretched.

      Again, thanks for your help,
      - chris

        Names with modifiers are an indicator that you should be using a hash instead of separate variables. Instead of $price_high, it might be an idea to use $price{high}.

        How about something like ...

        open STOCKS, ">", $dataFileName or die $!; my $price; PRICES: for my $category ( qw( high low average purple green ) ) { for my year ( 1990..2000 ) { last PRICES unless $price = <STOCKS>; # break loop at eof. $prices{$category}{$year} = $price; } }

        --
        TTTATCGGTCGTTATATAGATGTTTGCA