help on hash of hashes

S

Stephen Moon

Hi,

Can you help me with the below error? Thanks in advance.

-Steve



input file:
====================================================
flintstones: lead=fred pal=barney
jetsons: lead=george wife=jane boy=elroy
simpsons: lead=homer wife=marge kid=bart

perl source code:
====================================================

#!/usr/bin/perl -w

use strict;

my %HoH; #hash table for the data structure
my $rfwho;
if($#ARGV != 1) {
die("Invalid number of arguments\nUsage: perl test.pl <input_file>
<output_file>\n");
}

my $infile = $ARGV[0];
my $outfile = $ARGV[1];

open(DATA_IN,"$infile") || die("could not open $infile\n");
open(DATA_OUT,">$outfile") || die("could not open $outfile\n");

while ( my $line = <DATA_IN> ) {
chomp($line);
if($line =~ /^(.*?):\s*/){
my $who = $1;
$rfwho = \$who;
my $rfrec = {};
$HoH{$who} = $rfrec;
for my $field ( split /\s+/, $line) {
my ($key, $value) = split /=/,$field;
$rfrec->{$key} = $value;
}
}
}

foreach my $family ( keys %HoH ) {
print "$family: ";
foreach my $role ( keys %{ $HoH{$family} } ) {
printf(DATA_OUT "$role=$HoH{$family}{$role}"); <= error here
}
printf(DATA_OUT "\n");
}

output error:
====================================================

Use of uninitialized value in concatenation (.) or string at test.pl
line 34, <DATA_IN> line 3.
Use of uninitialized value in concatenation (.) or string at test.pl
line 34, <DATA_IN> line 3.
Use of uninitialized value in concatenation (.) or string at test.pl
line 34, <DATA_IN> line 3.
 
P

Paul Lalli

Hi,

Can you help me with the below error? Thanks in advance.

-Steve



input file:
====================================================
flintstones: lead=fred pal=barney
jetsons: lead=george wife=jane boy=elroy
simpsons: lead=homer wife=marge kid=bart

perl source code:
====================================================

#!/usr/bin/perl -w

use strict;

my %HoH; #hash table for the data structure
my $rfwho;
if($#ARGV != 1) {
die("Invalid number of arguments\nUsage: perl test.pl <input_file>
<output_file>\n");
}

my $infile = $ARGV[0];
my $outfile = $ARGV[1];

open(DATA_IN,"$infile") || die("could not open $infile\n");
open(DATA_OUT,">$outfile") || die("could not open $outfile\n");

while ( my $line = <DATA_IN> ) {
chomp($line);
if($line =~ /^(.*?):\s*/){
my $who = $1;
$rfwho = \$who;
my $rfrec = {};
$HoH{$who} = $rfrec;
for my $field ( split /\s+/, $line) {
^^^^^^

$line still contains the entire line, including (for example)
"flintsones:". THerefore, "flinstones:" becomes one of your fields
my ($key, $value) = split /=/,$field;

There is no = found in "flinstones:", so $key gets that whole string, and
$value is left as undef.


$rfrec->{$key} = $value;

You've just assigned a new position in the hash to be undef.
}
}
}

foreach my $family ( keys %HoH ) {
print "$family: ";
foreach my $role ( keys %{ $HoH{$family} } ) {
printf(DATA_OUT "$role=$HoH{$family}{$role}"); <= error here


Now you try to print out that undef value, and Perl correctly warns you.


Hope this helps,
Paul Lalli
 
A

Anno Siegel

Stephen Moon said:
Hi,

Can you help me with the below error? Thanks in advance.

[lots of code snipped]

Do not dump your problem as is to the newsgroup, make an attempt to
solve it yourself first.

The code I snipped contained things like checking the number of parameters
in @ARGV. Why should hundreds of people have to read that code,
understand what it does, and ignore it? It's your job to do that.
foreach my $family ( keys %HoH ) {
print "$family: ";
foreach my $role ( keys %{ $HoH{$family} } ) {
printf(DATA_OUT "$role=$HoH{$family}{$role}"); <= error here

Don't say "printf" when you mean "print". There's a difference, and
some day it will bite you. Also, I suppose you want to print a line
feed inside the loop too.
}
printf(DATA_OUT "\n");
}

output error:
====================================================

Use of uninitialized value in concatenation (.) or string at test.pl
line 34, <DATA_IN> line 3.

That's not an error but a warning, so the program should have printed
something to the output file. It would be interesting to know what
it was, but you didn't supply it.

In any case, you have not exhausted your own means by a long shot.

The warning tells you there's an undefined value in a string, so
at least one of "$role" or "$hoh{$family}{$role}" is undefined.

To find out which, change the code to tell you:

foreach my $family ( keys %HoH ) {
print "$family: ";
foreach my $role ( keys %{ $HoH{$family} } ) {
die '$role' unless defined $role;
die '$HoH{$family}{$role}' unless defined $HoH{$family}{$role};
print "$role=$HoH{$family}{$role}";
}
print "\n";
}

I bet you'll find that "$role" is defined but "$HoH{$family}{$role}" isn't.

Ask yourself why you think it should have a value. Then go back through
your code and verify that it does what you think it does.

In other words, learn to debug a program. It's a skill as necessary as
writing it in the first place.

Anno
 
S

Stephen Moon

Stephen Moon said:
Hi,

Can you help me with the below error? Thanks in advance.

[lots of code snipped]

Do not dump your problem as is to the newsgroup, make an attempt to
solve it yourself first.

The code I snipped contained things like checking the number of parameters
in @ARGV. Why should hundreds of people have to read that code,
understand what it does, and ignore it? It's your job to do that.
foreach my $family ( keys %HoH ) {
print "$family: ";
foreach my $role ( keys %{ $HoH{$family} } ) {
printf(DATA_OUT "$role=$HoH{$family}{$role}"); <= error here

Don't say "printf" when you mean "print". There's a difference, and
some day it will bite you. Also, I suppose you want to print a line
feed inside the loop too.
}
printf(DATA_OUT "\n");
}

output error:
====================================================

Use of uninitialized value in concatenation (.) or string at test.pl
line 34, <DATA_IN> line 3.

That's not an error but a warning, so the program should have printed
something to the output file. It would be interesting to know what
it was, but you didn't supply it.

In any case, you have not exhausted your own means by a long shot.

The warning tells you there's an undefined value in a string, so
at least one of "$role" or "$hoh{$family}{$role}" is undefined.

To find out which, change the code to tell you:

foreach my $family ( keys %HoH ) {
print "$family: ";
foreach my $role ( keys %{ $HoH{$family} } ) {
die '$role' unless defined $role;
die '$HoH{$family}{$role}' unless defined $HoH{$family}{$role};
print "$role=$HoH{$family}{$role}";
}
print "\n";
}

I bet you'll find that "$role" is defined but "$HoH{$family}{$role}" isn't.

Ask yourself why you think it should have a value. Then go back through
your code and verify that it does what you think it does.

In other words, learn to debug a program. It's a skill as necessary as
writing it in the first place.

Anno

Thanks for all your help.

Well, the above code is not mine. It's actually taken out from the
Programming Perl Third Edition book (i.e. Camel book). I was trying
to figure out how to generate a hash of hashes and how to print them
out by trying out his examples, but somehow I couldn't not get it to
work.

-Steve
 
S

Stephen Moon

Have another question. I fixed the code as you told me to

while ( my $line = <DATA_IN> ) {
chomp($line);
if($line =~ s/^(.*?):\s*//){ <===fixed here
my $who = $1;
$rfwho = \$who;
my $rfrec = {};
$HoH{$who} = $rfrec;
for my $field ( split /\s+/, $line) {
my ($key, $value) = split /=/,$field;
$rfrec->{$key} = $value;
}
}
}

foreach my $family ( sort keys %HoH ) {
printf(DATA_OUT "$family:\n");
foreach my $role ( sort keys %{ $HoH{$family} } ) {
printf(DATA_OUT "$role=$HoH{$family}{$role}\n");
}
}

The output that I get is below:

flintstones:
lead=fred
pal=barney
jetsons:
boy=elroy
lead=george
wife=jane
simpsons:
kid=bart
lead=homer
wife=marge

How can I change the above code so that I can output as below?

flintstones:,jetsons:,simpsons:
lead=fred,boy=elroy,kid=bart
pal=barney,lead=george,lead=homer
,wife=jane,wife=marge

Is this even possible? I have been struggling with this one a bit.

Thanks in advance.

-Steve
 
A

Anno Siegel

Have another question. I fixed the code as you told me to

Please show a little of the context you are replying to. It seems to
me that you are really replying to Paul Lalli. You have incorporated
some of his advice, it appears, but partly ignored mine.

You did reduce the code to the essentials, and that makes it much more
usenet-friendly. Now if it also used the DATA filehandle for input, and
STDOUT for output, instead of external files, it would be an ideal
example of code you can copy/paste into an editor and run it. Well,
there is still an unused and undeclared variable. The code should run
under strict and warnings.
while ( my $line = <DATA_IN> ) {
chomp($line);
if($line =~ s/^(.*?):\s*//){ <===fixed here
my $who = $1;
$rfwho = \$who;

Unused, undeclared. Why is it here?
my $rfrec = {};
$HoH{$who} = $rfrec;
for my $field ( split /\s+/, $line) {
my ($key, $value) = split /=/,$field;
$rfrec->{$key} = $value;
}
}
}

foreach my $family ( sort keys %HoH ) {
printf(DATA_OUT "$family:\n");
foreach my $role ( sort keys %{ $HoH{$family} } ) {

It doesn't make much sense to sort the roles, since they are largely
independent in each family. If there were a set of roles that must
be present in every family it would make sense to place these first
and sort them, but in general it doesn't.
printf(DATA_OUT "$role=$HoH{$family}{$role}\n");

Still using printf without a format. I told you it's going to bite.
}
}

The output that I get is below:

flintstones:
lead=fred
pal=barney
jetsons:
boy=elroy
lead=george
wife=jane
simpsons:
kid=bart
lead=homer
wife=marge

How can I change the above code so that I can output as below?

To see the relation of what you have to what you want more clearly,
print one line for each family. I think your original code tried to
do that, but it didn't seem to make sense then. It does now. So,
instead of your second loop, do this:

foreach my $family ( sort keys %HoH ) {
my $line = "$family: ";
foreach my $role ( keys %{ $HoH{$family} } ) {
$line .= "$role=$HoH{$family}{$role} ";
}
print "$line\n"; # not printf!
}

This prints

flintstones: pal=barney lead=fred
jetsons: boy=elroy lead=george wife=jane
simpsons: kid=bart lead=homer wife=marge

while you want
flintstones:,jetsons:,simpsons:
lead=fred,boy=elroy,kid=bart
pal=barney,lead=george,lead=homer
,wife=jane,wife=marge

Looking at it and squinting a little we see that the columns of the first
one make up the lines of the second. So the problem turns out to be
one of matrix transposition.
Is this even possible? I have been struggling with this one a bit.

It's certainly possible, I'll sketch a solution. You may want to try and
come up with your own method, the one I'm using is a little advanced.

It also ignores the HoH representing the families. Looking at the
line-wise output again, it appears that it essentially reproduces the
input lines. So to arrive at the wanted output, we can start directly
from the input without building the HoH structure.

We build an array of arrays, splitting the input lines on white space,
then transpose that. The first line of the resulting array of arrays
contains the families, the following lines contain the various roles.
We never bothered to separate the role specifications ("lead", "wife"...)
from the associated names. We print the families and the role lines in
slightly different formats. The result is:

flintstones: jetsons: simpsons:
lead=fred, lead=george, lead=homer
pal=barney, wife=jane, wife=marge
, boy=elroy, kid=bart

The table is differently arranged from your example, but it contains
the same information.

Anno

--------------------------------------------------------
#!/usr/bin/perl
use strict; use warnings;

my @columns = transpose( map [ split], <DATA>);

print "@$_\n" for shift @columns; # print header
foreach ( @columns ) {
print join( ', ', @$_), "\n"; # print role columns
}

sub transpose {
my $max = 0;
$max > @$_ or $max = @$_ for @_; # length of longest line
map [ map shift( @$_) || '', @_], 1 .. $max;
}

__DATA__
flintstones: lead=fred pal=barney
jetsons: lead=george wife=jane boy=elroy
simpsons: lead=homer wife=marge kid=bart
 
S

Stephen Moon

Please show a little of the context you are replying to. It seems to
me that you are really replying to Paul Lalli. You have incorporated
some of his advice, it appears, but partly ignored mine.

You did reduce the code to the essentials, and that makes it much more
usenet-friendly. Now if it also used the DATA filehandle for input, and
STDOUT for output, instead of external files, it would be an ideal
example of code you can copy/paste into an editor and run it. Well,
there is still an unused and undeclared variable. The code should run
under strict and warnings.

You are pretty funny:) just kidding:) I did take your advice and the
prior poster. I just realized that my mistake was making some changes
to the original
code without carefully taking a look at what it did. Your advice also
helped me since it showed how I can debug my program. Much thanks to
both of you.
Unused, undeclared. Why is it here?

well, I was trying to use the reference of $who in the following loop
in different scope for some other thing and didn't come around fixing
it.
It doesn't make much sense to sort the roles, since they are largely
independent in each family. If there were a set of roles that must
be present in every family it would make sense to place these first
and sort them, but in general it doesn't.


Still using printf without a format. I told you it's going to bite.

Well, I am a C programmer, so bear with me. I takes a little while to
make a transition.
}
}

The output that I get is below:

flintstones:
lead=fred
pal=barney
jetsons:
boy=elroy
lead=george
wife=jane
simpsons:
kid=bart
lead=homer
wife=marge

How can I change the above code so that I can output as below?

To see the relation of what you have to what you want more clearly,
print one line for each family. I think your original code tried to
do that, but it didn't seem to make sense then. It does now. So,
instead of your second loop, do this:

foreach my $family ( sort keys %HoH ) {
my $line = "$family: ";
foreach my $role ( keys %{ $HoH{$family} } ) {
$line .= "$role=$HoH{$family}{$role} ";
}
print "$line\n"; # not printf!
}

This prints

flintstones: pal=barney lead=fred
jetsons: boy=elroy lead=george wife=jane
simpsons: kid=bart lead=homer wife=marge

while you want
flintstones:,jetsons:,simpsons:
lead=fred,boy=elroy,kid=bart
pal=barney,lead=george,lead=homer
,wife=jane,wife=marge

Looking at it and squinting a little we see that the columns of the first
one make up the lines of the second. So the problem turns out to be
one of matrix transposition.
Is this even possible? I have been struggling with this one a bit.

It's certainly possible, I'll sketch a solution. You may want to try and
come up with your own method, the one I'm using is a little advanced.

It also ignores the HoH representing the families. Looking at the
line-wise output again, it appears that it essentially reproduces the
input lines. So to arrive at the wanted output, we can start directly
from the input without building the HoH structure.

We build an array of arrays, splitting the input lines on white space,
then transpose that. The first line of the resulting array of arrays
contains the families, the following lines contain the various roles.
We never bothered to separate the role specifications ("lead", "wife"...)
from the associated names. We print the families and the role lines in
slightly different formats. The result is:

flintstones: jetsons: simpsons:
lead=fred, lead=george, lead=homer
pal=barney, wife=jane, wife=marge
, boy=elroy, kid=bart

The table is differently arranged from your example, but it contains
the same information.

Anno

--------------------------------------------------------
#!/usr/bin/perl
use strict; use warnings;

my @columns = transpose( map [ split], <DATA>);

print "@$_\n" for shift @columns; # print header
foreach ( @columns ) {
print join( ', ', @$_), "\n"; # print role columns
}

sub transpose {
my $max = 0;
$max > @$_ or $max = @$_ for @_; # length of longest line
map [ map shift( @$_) || '', @_], 1 .. $max;
}

__DATA__
flintstones: lead=fred pal=barney
jetsons: lead=george wife=jane boy=elroy
simpsons: lead=homer wife=marge kid=bart

cool! I will check it out! Well, the reason that I use sort is
because I will have frequency index associated with magnitudes.

Thanks for your help.

-Steve
 
B

Ben Morrow

(e-mail address removed)-berlin.de (Anno Siegel) wrote in message


Well, I am a C programmer, so bear with me. I takes a little while to
make a transition.

Even in C you should always printf("%s", string) rather than simply
printf(string): otherwise you've got a security hole on your hands.
What if you end up with string = "%d"?

Ben
 
S

Stephen Moon

Ben Morrow said:
Even in C you should always printf("%s", string) rather than simply
printf(string): otherwise you've got a security hole on your hands.
What if you end up with string = "%d"?

Ben

i c. Of course, that's what I would if i were to write a C program.
I didn't understand what he meant by "using printf without a format."

What is a difference between using "print" and "printf"? How would
you use "printf" with a format? I am relatively new to perl. Maybe
you can enlighten me. So, you are telling me that

printf(DATA_OUT "$role=$HoH{$family}{$role}\n");

should be written as

printf(DATA_OUT "%s = %s\n", $role, $HoH{$family}{$role});

-Steve
 
G

gnari

What is a difference between using "print" and "printf"? How would
you use "printf" with a format? I am relatively new to perl. Maybe
you can enlighten me. So, you are telling me that

printf(DATA_OUT "$role=$HoH{$family}{$role}\n");

should be written as

printf(DATA_OUT "%s = %s\n", $role, $HoH{$family}{$role});

yes, or just
print DATA_OUT "$role=$HoH{$family}{$role}\n";

gnari
 
J

Jay Tilton

(e-mail address removed) (Stephen Moon) wrote:

: I didn't understand what he meant by "using printf without a format."
:
: What is a difference between using "print" and "printf"?

Consult perlfunc for explanations of each function's purpose.

: How would
: you use "printf" with a format? I am relatively new to perl. Maybe
: you can enlighten me. So, you are telling me that
:
: printf(DATA_OUT "$role=$HoH{$family}{$role}\n");
:
: should be written as
:
: printf(DATA_OUT "%s = %s\n", $role, $HoH{$family}{$role});

Yes, that is the appropriate printf() statement.

More simply, change the printf() to an ordinary print().

print( DATA_OUT "$role=$HoH{$family}{$role}\n" );
 
S

Stephen Moon

Anno,
--------------------------------------------------------
#!/usr/bin/perl
use strict; use warnings;

my @columns = transpose( map [ split], <DATA>);

print "@$_\n" for shift @columns; # print header
foreach ( @columns ) {
print join( ', ', @$_), "\n"; # print role columns
}

sub transpose {
my $max = 0;
$max > @$_ or $max = @$_ for @_; # length of longest line
map [ map shift( @$_) || '', @_], 1 .. $max;
}

I have a hard time figuring out how transpose function. Maybe, a
little explanation? Especially where you have "map [ map shift( @$_)
|| '',@_], 1 .. $max" Furthermore, what would be difference in using
{} instead of [].

-Steve
 
A

Anno Siegel

Stephen Moon said:
Anno,
[...]
sub transpose {
my $max = 0;
$max > @$_ or $max = @$_ for @_; # length of longest line
map [ map shift( @$_) || '', @_], 1 .. $max;
}

I have a hard time figuring out how transpose function. Maybe, a
little explanation? Especially where you have "map [ map shift( @$_)
|| '',@_], 1 .. $max"

Well, it is rather compressed, so let's make it more explicit. First let's
expand the outer map and introduce meaningful variable names and some
comments along the way. (I'll assume $max has been calculated as before.
Since it's the length of the longest line it is also the number of lines
in the transposed matrix).

my @lines = @_; # an array of arrays which represents a matrix
my @cols; # will become the transposed matrix
# extract $max columns
for ( 1 .. $max ) {
push @cols, [ map shift @$_ || '', @lines]; # unchanged
}
# done, return result
@cols;

Now do the same with the inner map. We are using a fundamental identity
here. If some expression EXPR returns a list there are two ways to
get an array reference to that list. You can write

$ref = [ EXPR ];

or you can do

my @array = LIST;
$ref = \ @array;

The original code uses the first way for each (anonymous) column. The
expanded code uses the second with the named variable @col. The result
is the same.

my @lines = @_; # an array of arrays which represents a matrix
my @cols; # will become the transposed matrix
# extract $max columns
for ( 1 .. $max ) {
# extract one column
my @col;
# walk through all lines
for my $line ( @lines ) {
# shave off one element of $line and add it to the current
# column. if there are no more elements left in the line, add
# an empty string. $line is destroyed in the process.
push @col, shift @$line || '';
}
# another column is ready, push it onto the result list
push @cols, \ @col;
}
# done, return result
@cols;

I hope that makes things clearer.

As presented, this transposition routine isn't quite a general-purpose
matrix transposition. For one, it's destructive. After using it on
a matrix, the matrix lines will be reduced to empty lists. A general-
purpose routine shouldn't do that. At least, a property like that must
be documented. In blinking red, with a howling background noise.

Secondly, it replaces undefined elements in the original matrix with
empty strings. (Sloppily, it would even replace a 0 element with an
empty string.) A general purpose routine has no business doing that,
but in the case at hand it was convenient.
Furthermore, what would be difference in using
{} instead of [].

"{}" produces a hashref from a list of pairs. "[]" produces an
arrayref from any list. The two are never[1] interchangeable.

Anno.

[1] Huh. I said "never" about a Perl construct. Someone is going to
come up with a counter-example.
 

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

Forum statistics

Threads
473,769
Messages
2,569,576
Members
45,054
Latest member
LucyCarper

Latest Threads

Top