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

I want to fill a hash with some pairs with a for-loop. I read filenames, split them and push them into the hash. For some reason, the hash gets overwritten with every entry and I can't find the mistake. It's probably a silly mistake, still a PERL - newbee! Here's the code:

opendir(DIR, $semDir); my @filenames =readdir(DIR); closedir(DIR); my %customer_domain_hash; foreach my $line(@filenames) { $line =~ s/\..*//; print "$line\n"; %customer_domain_hash = split(/@@@/, $line); } print Dumper(\%customer_domain_hash)."\n";

This is the result:

CustomerA@@@DomainA CustomerA@@@DomainB CustomerA@@@DomainC CustomerA@@@DomainD CustomerA@@@DomainE CustomerB@@@DomainA CustomerB@@@DomainB CustomerB@@@DomainC CustomerB@@@DomainD CustomerB@@@DomainE $VAR1 = { 'CustomerB' => 'DomainE' };

Replies are listed 'Best First'.
Re: Filling a Hash
by toolic (Bishop) on Jul 17, 2015 at 13:57 UTC
    You keep overwriting the hash in your loop. Also, you likely want a hash-of-arrays (perldsc) since you have multiple domains per customer:
    use warnings; use strict; my %customer_domain_hash; while (my $line = <DATA>) { chomp $line; my ($k, $v) = split(/@@@/, $line); push @{ $customer_domain_hash{$k} }, $v; } use Data::Dumper; $Data::Dumper::Sortkeys=1; print Dumper(\%customer_d +omain_hash); __DATA__ CustomerA@@@DomainA CustomerA@@@DomainB CustomerA@@@DomainC CustomerA@@@DomainD CustomerA@@@DomainE CustomerB@@@DomainA CustomerB@@@DomainB CustomerB@@@DomainC CustomerB@@@DomainD CustomerB@@@DomainE

    Outputs:

    $VAR1 = { 'CustomerA' => [ 'DomainA', 'DomainB', 'DomainC', 'DomainD', 'DomainE' ], 'CustomerB' => [ 'DomainA', 'DomainB', 'DomainC', 'DomainD', 'DomainE' ] };
Re: Filling a Hash
by hippo (Archbishop) on Jul 17, 2015 at 13:54 UTC

    When you do

    %customer_domain_hash = split(/@@@/, $line);

    you are overwriting the entire hash every time whereas what really ought to happen is that only one entry should be added/changed. So, if you replace that one line with:

    my ($key, $value) = split(/@@@/, $line); $customer_domain_hash{$key} = $value;

    it should build up the hash on each iteration of the foreach.

    Update: Unless you have duplicate keys, of course, which has been pointed out below (thanks, GotToBTru). A simple hash is not sufficient to store this. toolic's answer shows an alternative approach.

      Almost! He will end up with just 2 entries in the hash because the key repeats.

      Dum Spiro Spero
      Good solution. To clarify for the OP, the statement that has been fixed here was essentially akin to saying "Make my hash an array". Whereas what you should be doing is assigning a particular key in the hash a value. Pardon me if I'm pointing out something obvious - not sure how much of a newbie you are! :)
Re: Filling a Hash
by stevieb (Canon) on Jul 17, 2015 at 13:45 UTC

    You need to escape the @ symbol in a regex: /\@{3}/</strike>.

    Hadn't had coffee yet... was thinking string interpolation.

      oh well, the splitting part works as you see. The problem is the overwritten hash in this case. But your tip also makes my code better, thanks for that!

        D'oh! Late waking up this morning. That's because hashes can only have unique keys... you can create a hash of arrays to fix this issue:

        #!/usr/bin/perl use warnings; use strict; use Data::Dumper; my %h; my @a; while (<DATA>){ chomp; my @a = split /@@@/; push @{ $h{$a[0]} }, $a[1]; } print Dumper \%h __DATA__ CustomerA@@@DomainA CustomerA@@@DomainB CustomerA@@@DomainC CustomerA@@@DomainD CustomerA@@@DomainE CustomerB@@@DomainA CustomerB@@@DomainB CustomerB@@@DomainC CustomerB@@@DomainD CustomerB@@@DomainE __END__ $VAR1 = { 'CustomerB' => [ 'DomainA', 'DomainB', 'DomainC', 'DomainD', 'DomainE' ], 'CustomerA' => [ 'DomainA', 'DomainB', 'DomainC', 'DomainD', 'DomainE' ] };

        Then access everything like this:

        for my $key (keys %h){ for my $elem (@{ %h{$key} }){ print "$key: $elem\n"; } }