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

I am reading data out of a flatfile including names, employee classifications, and countries to get statistics. Since I'm only concerned about numbers, I've made hashes for each classification %class1{'countryname'}, etc.

Now to get the number of countries represented within class1, I use scalar(keys(%class1)).

My question is how to get the number of countries represented in the whole company.

Here is my (correctly working) code:

foreach (keys(%class1),keys(%class2),keys(%class3)) { $tot{$_}=1; } $t=scalar(keys(%tot));

Is this the "right" way to do this? Is there another way? Also, is it possible to "merge" two hashes in an automatic way? (My hunch is "no" because Perl can't know what to do with overlapping keys, but I thought I'd ask...)

Replies are listed 'Best First'.
(Ovid - use a HoA to merge hashes) Re: Combining hashes
by Ovid (Cardinal) on May 11, 2001 at 21:54 UTC
    My thought regarding merging hashes would be to make the values into references to anonymous arrays. The following demonstrates a roughly hacked "merge" subroutine that takes references to hashes, grabs all unique keys, and then iterates over the keys to build an array containing values in all hashes. I'm sure someone else can come behind me to improve this :)

    Incidentally, what I like about this solution is that you don't lose any data!

    use strict; use warnings; use Data::Dumper; my %first = ( one => 1, two => 2, three => 3 ); my %second = ( one => 'uno', two => 'dos', foo => 'bar' ); my %third = ( one => [ 'this', 'is', 'a', 'test' ], two => 'dos', foo => { bar => 'raise', baz => 'test' } ); my %new_hash = merge( \%first, \%second, \%third ); print Dumper( \%new_hash ); sub merge { my @hashes = @_; my ( %merged_hash, %temp, @keys ); # grab unique keys foreach my $hash ( @hashes ) { foreach my $key ( keys %$hash ) { push @keys, $key if ! $temp{ $key }++; } } my @vals; # build the array foreach my $key ( @keys ) { foreach my $hash ( @hashes ) { push @vals, $hash->{ $key } if exists $hash->{ $key }; } $merged_hash{ $key } = [@vals]; @vals = (); } return %merged_hash; }
    Output:
    $VAR1 = { 'one' => [ 1, 'uno', [ 'this', 'is', 'a', 'test' ] ], 'three' => [ 3 ], 'foo' => [ 'bar', { 'baz' => 'test', 'bar' => 'raise' } ], 'two' => [ 2, 'dos', 'dos' ] };
    Note that it even allows you to merge complex data structures. Of course, I haven't tested (or commented) this too thoroughly, so there is no warranty included.

    This answers your question about merging hashes. Of course, to get the country count, simply merge the hashes and and count the keys.

    Cheers,
    Ovid

    Join the Perlmonks Setiathome Group or just click on the the link and check out our stats.

      sub merge { my %merged; for (@_) { while (my ($k, $v) = each %$_) { push @{$merged{$k}}, $v; } } %merged; }
      Though, if I were using this, I would prefer for it to a return a reference.
         MeowChow                                   
                     s aamecha.s a..a\u$&owag.print
Re: Combining hashes
by MeowChow (Vicar) on May 11, 2001 at 21:27 UTC
    You can leave off the explicit scalar if you are evaluating keys in a scalar context, as in:
    my $count = keys %class1;
    Regarding your second question, yes you can merge hashes together in a simple list. Perl transforms each hash into a list of key-value pairs, and the last value for any given key in the list is what is set in %merged:
    my %merged = (%class1, %class2, %class3); my $count = keys %merged;
    This version is somewhat more readable than your original one, at the expense of a little inefficiency.
       MeowChow                                   
                   s aamecha.s a..a\u$&owag.print
Re: Combining hashes
by LD2 (Curate) on May 11, 2001 at 21:05 UTC
    Also, is it possible to "merge" two hashes in an automatic way? (My hunch is "no" because Perl can't know what to do with overlapping keys, but I thought I'd ask...)

    Take a look at Merging hashes
Re: Combining hashes
by Sifmole (Chaplain) on May 11, 2001 at 21:48 UTC
    Well, this way would work... but there are others. Such as:
    my $total = scalar(keys %class1) + scalar(keys %class2) + scalar(keys +%class3); #or my $totb = 0; $totb += $_ for map { scalar keys %$_; } (\%class1, \%class2, \%class3 +);
    As for merging, I believe the answer to be no. A hash can not have two keys of the exact same value. You can create a union of them easily enough by using
    my %new = (%class1, %class2, %class3);
    You will however lose any duplicates.
      I believe that mpolo actually wants to lose the duplicates, since his intention is to count the total number of countries representated between employee classes.

      Also, you can rewrite:

      my $totb = 0; $totb += $_ for map { scalar keys %$_; } (\%class1, \%class2, \%class3 +);
      as the following:
      my $totb = 0; $totb += keys %$_ for \(%class1, %class2, %class3);
      No need to iterate twice.
         MeowChow                                   
                     s aamecha.s a..a\u$&owag.print
        Very good point. I felt that something like that could be done to avoid the second iteration. I just gave up before I found it, thanks for showing me.