Search code examples
arraysperlhashperl-data-structures

How to print an $element of an @array that is used as a value of %hash?


I think this should be easy to solve for experienced people, for me though I was trying and searched on google about this but couldnt find anything that fits my situation. I just want to let $amountok and $amountko give out the values they have at index 0 and 1 but it is not working like this (see # marks).

print ERGEBNIS "ID;Amount;Amount OK;Amount KO\n";
foreach my $key (keys %elementhash){
  my $sum = 0;              
  foreach $valueofkey(@{$elementhash{$key}}){                   
     $sum += $valueofkey;           
  }

  #my $amountok= @{$elementhash{$key}[0]};
  #my $amountko= @{$elementhash{$key}[1]};

  print ERGEBNIS $key.";".$sum.";".$amountok.";".$amountko."\n";

}

this would give me out the error: "Cant use string "7" as an array ref.", this must be possible somehow but i just don't know how! thank you guys

this is the Dumper output of %elementhash:

 $VAR1 = {                '1000' => [7],               
                          '2000' => [5],                  
                          '3000' => [56, 12]              
         };                     

Solution

  • Your hash elements contain array references. Even if there is only one value in the array ref, it's still an array ref. You can use the -> operator for dereferencing.

    $elementhash{$key}->[0];
    

    Note that you first have a hash, not a hash reference, so there is no arrow before the {$key} part. Inside there is an array reference. In fact, you don't strictly need to put a -> in front of the [0] because Perl knows that nested data structures are built from references. It's a matter of preference that's been well discussed. I personally like the arrow, but it's not needed here1.

    What you tried to do with @{$elementhash{$key}[0]} was take the first element inside the $key, e. g. the 7 for key 1000, and deref that as an array. Of course you can't do that, because it's a number, and not an array reference.

    Because not all of the arrays in your data structure have two values, you should check if the second value exists. You can do that with the // defined-or operator. It allows 0 or other un-true values, but not undef.

    Finally, you were missing a my for $valueofkey.

    use strict;
    use warnings;
    
    my %elementhash = (
        '1000' => [7],
        '2000' => [5],
        '3000' => [ 56, 12 ]
    );
    
    print "ID;Amount;Amount OK;Amount KO\n";
    foreach my $key ( keys %elementhash ) {
        my $sum = 0;
        foreach my $valueofkey ( @{ $elementhash{$key} } ) {
            $sum += $valueofkey;
        }
    
        my $amountok = $elementhash{$key}->[0];
        my $amountko = $elementhash{$key}->[1] // 0;
    
        print $key. ";" . $sum . ";" . $amountok . ";" . $amountko . "\n";
    }
    

    Note that you can rewrite the last line to use join.

    print join( ';', $key, $sum, $amountok, $amountko ),  "\n";
    

    If your Perl is at least version 5.10, you can also turn on use feature 'say' so you don't need to print the "\n".

    say join ';', $key, $sum, $amountok, $amountko;
    

    If you have more complicated CSV, consider using Text::CSV or Text::CSV_XS instead.


    1) You do need one arrow if the variable that you are starting from is a reference. But you can omit the following ones.

    my $foo = { bar => [1, 2, 3] };
    say $foo->{bar}[2]; # works
    say $foo{bar}[2]; # complains that %foo needs explicit package name