Access hash of hashes element problem.

J

Justin C

I can't seem to access the values in a hash of hashes, but if I iterate
over it I'm able to print all the values. The error I get if I try to
access one value is :
Use of uninitialized value in print at ./test line 20, <PRICES> line 62.

I've done the best I can in reducing this to the smallest possible code
that works, it's still quite long, sorry about that.

======= START CODE =======
#!/usr/bin/perl

use warnings ;
use strict ;

my %priceHash ;
my $zone = 5 ;
my $weight = 5 ;

lookupCost() ;

#for $weight ( keys %priceHash ) {
# print "$weight : " ;
# for $zone ( keys %{$priceHash{$weight}} ) {
# print "\t", $zone, "=", $priceHash{$weight}{$zone}, "\n" ;
# }
# print "\n" ;
#}

print "Price = ", $priceHash{$weight}{$zone}, "\n" ;

sub lookupCost {
open PRICES, "<", "/var/www/inhouse/pforce/pforcePrices.csv"
or die "Cannot opne prices csv file: $!" ;

my @lines = <PRICES> ;
my $header = shift @lines ;
chomp $header ;
$header =~ s/\cM//g ; # MS CR/LF
$header =~ s/zone//ig ;
$header =~ s/\s+//g ;

my @zones = split /,/ , $header ;
shift @zones ; #first one is empty
foreach ( @lines ) {
chomp ;
s/\cM//g ;
my @prices = split /,/ ;
my $kg = shift @prices ;

foreach ( @zones ) {
$priceHash{$kg}{$_} = shift @prices ;
}
}
}
======= CODE END =======

If I comment the "print" line and uncomment the itteration over the
hashes it prints, for each weight, the different zones and the prices
for those zones. It appears to me that all of the information is in the
hash but I'm not accessing the individual values correctly.

The .csv, if you want to try this out, can be found at:
http://81.6.247.208/pforcePrices.csv I didn't paste it here because of
the size (it's not too big, but it's not appropriate for a usenet post).

I thank you for any help you can give with this.

Justin.
 
J

J. Gleixner

alexamaschoolJustin said:
I can't seem to access the values in a hash of hashes, but if I iterate
over it I'm able to print all the values. The error I get if I try to
access one value is :
Use of uninitialized value in print at ./test line 20, <PRICES> line 62.

I've done the best I can in reducing this to the smallest possible code
that works, it's still quite long, sorry about that.

It could be smaller. Use __DATA__, for a few lines, of your CSV
file in your script so that others can run it without having to
download your CSV file.

You could use Data::Dumper to verify what's actually in %priceHash.

use Data::Dumper;
print Dumper( \%priceHash );
 
M

Mumia W.

I can't seem to access the values in a hash of hashes, but if I iterate
over it I'm able to print all the values. The error I get if I try to
access one value is :
Use of uninitialized value in print at ./test line 20, <PRICES> line 62.
[...]

Your program is so close to working, but you have one problem--you
forgot that hash keys are strings and only strings. Where string
comparisons are concerned, '5' != '5.0'. Change $weight (at the top of
the program) to "5.0" and rerun your program.

However, it's better to create the hash keys as simpler numbers, e.g.
insert 5 rather than 5.0:

$priceHash{$kg+0}{$_} = shift @prices;

This causes a warning about "per" which has some function in your data
that I don't know about.

I also have two minor nickpicks: "open" is misspelled in the die()
statement, and the PRICES file is never explicitly closed by program.
You also do a little more work than is necessary to read in the data.

This is a shorter version of your program:

use strict;
use warnings;
use File::Slurp;

my %priceHash;
my $weight = 5;
my $zone = 5;

my @lines = read_file('pforcePrices.csv');
s/\cM\cJ$// for @lines;

my $header = shift @lines;
$header =~ s/(zone|\s+|^,)//ig;
my @zones = split /,/,$header;

foreach my $line (@lines) {
next unless $line =~ /^\d/;
my @prices = split /,/,$line;
my $kg = shift @prices;
foreach my $zn (@zones) {
$priceHash{$kg+0}{$zn} = shift @prices;
}
}

print "Price: $priceHash{$weight}{$zone}\n";
 
J

Jens Thoms Toerring

Justin C said:
I can't seem to access the values in a hash of hashes, but if I iterate
over it I'm able to print all the values. The error I get if I try to
access one value is :
Use of uninitialized value in print at ./test line 20, <PRICES> line 62.
I've done the best I can in reducing this to the smallest possible code
that works, it's still quite long, sorry about that.
======= START CODE =======
#!/usr/bin/perl
use warnings ;
use strict ;
my %priceHash ;
my $zone = 5 ;
my $weight = 5 ;
lookupCost() ;
#for $weight ( keys %priceHash ) {
# print "$weight : " ;
# for $zone ( keys %{$priceHash{$weight}} ) {
# print "\t", $zone, "=", $priceHash{$weight}{$zone}, "\n" ;
# }
# print "\n" ;
#}
print "Price = ", $priceHash{$weight}{$zone}, "\n" ;
sub lookupCost {
open PRICES, "<", "/var/www/inhouse/pforce/pforcePrices.csv"
or die "Cannot opne prices csv file: $!" ;
my @lines = <PRICES> ;
my $header = shift @lines ;
chomp $header ;
$header =~ s/\cM//g ; # MS CR/LF
$header =~ s/zone//ig ;
$header =~ s/\s+//g ;
my @zones = split /,/ , $header ;
shift @zones ; #first one is empty
foreach ( @lines ) {
chomp ;
s/\cM//g ;
my @prices = split /,/ ;
my $kg = shift @prices ;
foreach ( @zones ) {
$priceHash{$kg}{$_} = shift @prices ;

Your problem is that you use floating point numbers as hash keys
and rely on some automatic conversion between strings and floating
point values.

In this line

$priceHash{$kg}{$_} = shift @prices ;

'$kg' will be a floating point number you just read from the file.
This could be e.g. "5.0" within the file but since floating point
numbers only have a limited precision the key in the hash could
actually be 4.9999999999 or 5.000000000001 or something similar.
So you end up with an element that has a key of e.g. 4.9999999999
and that, of course, won't be found when you look for an element
by the number 5 as the key. One way around that could be to use
double quotes arount the '$kg' to avoid conversion to a floating
point number, so the key isn't whatever approximation you have
for 5.0 but the string "5.0". Of course, to retrieve values from
the hash you will also need strings, not floating point numbers
and they must fit exactly what you used when you created the
hash, so you must use "5.0" and not "5" as the key.

But I would strongly recommend that you avoid anything related
to floating point numbers as keys. It's going to be extremely
hard to get right and there must be better ways to create keys
for your hash.
Regards, Jens
 
J

Justin C

I can't seem to access the values in a hash of hashes, but if I iterate
over it I'm able to print all the values. The error I get if I try to
access one value is :
Use of uninitialized value in print at ./test line 20, <PRICES> line 62.
[...]

Your program is so close to working, but you have one problem--you
forgot that hash keys are strings and only strings. Where string
comparisons are concerned, '5' != '5.0'. Change $weight (at the top of
the program) to "5.0" and rerun your program.

Hash keys are strings! Of course! Thank you for pointing this out, I'd
been staring at it for hours and just could not see it.

However, it's better to create the hash keys as simpler numbers, e.g.
insert 5 rather than 5.0:

$priceHash{$kg+0}{$_} = shift @prices;

I don't understand what's going on here. $kg is a string, the string is
"5.0", yet "$kg+0" removes ".0". Please tell me what's going on, it's
confusing and my brain is already a little bruised from today's work!

This causes a warning about "per" which has some function in your data
that I don't know about.

Ah, yes, I need to skip that line too, I'll fix my script to ignore it.

I also have two minor nickpicks: "open" is misspelled in the die()
statement,

That *is* a nit pick!

and the PRICES file is never explicitly closed by program.

I'm sure I read here that file-handles are closed automatically when
they go out of scope, I've not bothered closing a file since. Is this
bad?

You also do a little more work than is necessary to read in the data.

I know, I don't do anywhere near enough perl to be proficient, though I
am trying!

This is a shorter version of your program:
[snip]

Lots of better ways of doing things. Thank you for pointing them out.

Justin.
 
J

John W. Krahn

Jens said:
Your problem is that you use floating point numbers as hash keys
and rely on some automatic conversion between strings and floating
point values.

In this line

$priceHash{$kg}{$_} = shift @prices ;

'$kg' will be a floating point number you just read from the file.
This could be e.g. "5.0" within the file but since floating point
numbers only have a limited precision the key in the hash could
actually be 4.9999999999 or 5.000000000001 or something similar.
So you end up with an element that has a key of e.g. 4.9999999999
and that, of course, won't be found when you look for an element
by the number 5 as the key.

No, that is not correct. The data is read from the file and stored in Perl's
scalars as strings and is not converted to a floating point number because it
is not used in any mathematical operations.


John
 
J

John W. Krahn

Mumia said:
I can't seem to access the values in a hash of hashes, but if I iterate
over it I'm able to print all the values. The error I get if I try to
access one value is :
Use of uninitialized value in print at ./test line 20, <PRICES> line 62.
[...]

Your program is so close to working, but you have one problem--you
forgot that hash keys are strings and only strings. Where string
comparisons are concerned, '5' != '5.0'. Change $weight (at the top of
the program) to "5.0" and rerun your program.

However, it's better to create the hash keys as simpler numbers, e.g.
insert 5 rather than 5.0:

$priceHash{$kg+0}{$_} = shift @prices;

This causes a warning about "per" which has some function in your data
that I don't know about.

I also have two minor nickpicks: "open" is misspelled in the die()
statement, and the PRICES file is never explicitly closed by program.
You also do a little more work than is necessary to read in the data.

This is a shorter version of your program:

use strict;
use warnings;
use File::Slurp;

my %priceHash;
my $weight = 5;
my $zone = 5;

my @lines = read_file('pforcePrices.csv');
s/\cM\cJ$// for @lines;

my $header = shift @lines;
$header =~ s/(zone|\s+|^,)//ig;
my @zones = split /,/,$header;

foreach my $line (@lines) {
next unless $line =~ /^\d/;
my @prices = split /,/,$line;
my $kg = shift @prices;
foreach my $zn (@zones) {
$priceHash{$kg+0}{$zn} = shift @prices;
}
}

print "Price: $priceHash{$weight}{$zone}\n";

I'd do it more like this instead: :)

open my $PRICES, '<', 'pforcePrices.csv'
or die "Cannot open 'pforcePrices.csv' $!";

( my $header = <$PRICES> ) =~ s/\s+\z//;
my ( undef, @zones ) = map { s/\s*zone\s*//i; $_ } split /,/ , $header;

my %priceHash;
while ( <$PRICES> ) {
s/\s+\z//;
my ( $kg, @prices ) = split /,/;
@{ $priceHash{ $kg } }{ @zones } = @prices;
}




John
 
M

Martien verbruggen

I don't understand what's going on here. $kg is a string, the string is
"5.0", yet "$kg+0" removes ".0". Please tell me what's going on, it's
confusing and my brain is already a little bruised from today's work!

$kg + 0 is an arithmetic expression, which perl calculates. The result
of it is a number (not a string). That number happens to be 5. The
number then gets stringified, because it is used as a hash key, and the
result is "5". There is a lot of stuff going on behind the scenes, which
has to do with the way Perl's scalars can represent all these different
things like strings, integers, doubles, references, etc.

Watch:

$ perl -w
my $a1 = 5.0;
my $a2 = "5.0";
print "$a1 $a2\n";
$a2 += 0;
print "$a1 $a2\n";
__END__
5 5.0
5 5

Generally, if you want numbers to be stringified in a particular format
you should use (s)printf.

Martien
 
T

Tad McClellan

Justin C said:
I'm sure I read here that file-handles are closed automatically when
they go out of scope,


That is correct.

I've not bothered closing a file since.


perl will always do what it is supposed to do, it is a machine...

Is this
bad?


.... but that does not lead to the conclusion that the human telling
perl what to do will do what _it_ is supposed to. :-(

See my tale of woe from leaving out a close():

http://groups.google.com/group/comp.lang.perl.misc/msg/73d4587743c64e2f
 
J

Justin C

Justin C said:
That is correct.




perl will always do what it is supposed to do, it is a machine...




... but that does not lead to the conclusion that the human telling
perl what to do will do what _it_ is supposed to. :-(

See my tale of woe from leaving out a close():

http://groups.google.com/group/comp.lang.perl.misc/msg/73d4587743c64e2f

OK, point taken. Explicit close() added... now to grep the rest of what
I'd written and see how many have more open()s than close()s :)

Justin.
 

Ask a Question

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

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Similar Threads


Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,580
Members
45,055
Latest member
SlimSparkKetoACVReview

Latest Threads

Top