I am writing a Perl script to do some mathematical operations on a hash. This hash contains the values as given in the sample below. I have written the code below. If I execute this code for an array value separately without using a foreach loop, the output is fine. But if I run this using a foreach loop on the array values, the sum for values in A are good, but from B the output add the previous values.
Hash Sample:
$VAR1 = 'A';
$VAR2 = {
'"x"' => [values],
'"y"' => [values],
and so on...
$VAR3 = 'B';
$VAR4 = {
'"x"' => [values],
'"y"' => [values],
and so on...
$VARn....
Code:
#!/usr/bin/perl -w
use strict;
use List::Util qw(sum);
my #data;
my #count;
my $total;
my #array = ("A", "B", "C", "D");
foreach my $v (#array) {
my %table = getV($v); #getV is a subroutine returing a hash.
for my $h (sort keys %table) {
for my $et (sort keys %{ $table{$h} } ) {
for $ec ($table{$h}{$et}) {
push #data, $ec;
#count = map { sum(#{$_}) } #data;
$total = sum(#count);
}
}
print "sum of $v is $total\n";
}
I think the issue is with this line. It is storing all the previous values and hence adding all the values in next foreach loop.
push #data, $ec;
So, here I have two issues:
1) How can I refresh the array (#data) in each foreach loop iteration?
2) How can I add the values in the array ref ($ec) and store them in an array? Because when I use the following code:
for $ec ($table{$h}{$et}) {
#count = map { sum(#{$_}) } #$ec;
$total = sum(#count);
}
The output gives me the same values for #count and $total.
Please provide me with suggestions.
If I understand you correctly, just a small change in your code. Make an empty array (#data) at the beginning of for loop. Hope this helps.
for my $h (sort keys %table) {
my #data;
1) Declare the #data array at the top of the loop body where you want to start with a fresh, empty array. Or maybe you mean to be saying #data = #$ec, not push #data, $ec?
2) To add the values in the array referred to by $ec, you would just say sum(#$ec); no map required.
It's not completely clear what your data structure is or what you are trying to do with it.
It would help to see what a sample %table looks like and what results you expect from it.
Related
The goal: I want to check if all entries in a hash are equal in some manner (here it's the count).
My nasty solution:
# initialize some fake data
my #list1 = (2, 3);
my #list2 = (1, 2);
my %hash = (12 => \#list1, 22 => \#list2);
# here starts the quest for the key
my $key;
foreach my $a (keys %hash) {
$key = $a;
}
# some $key is set
# here starts the actual comparision
my $count = scalar(#{%hash{$key}});
foreach my $arr_ref (%hash) {
my $actcount = scalar(#$arr_ref);
print "some warning" if $actcount != $count;
}
I know I could also store the size in the first iteration of the loop, then I would not need to get the key in advance. But it will cause me a conditional statement in eacht iteration. Thus I like to avoid it.
The question:
What is a proper way to get a key from the hash?
Addition:
There should be something possible like
(keys %hash)[0]
my $key = (keys %hash)[0] should work, or you can force list context by enclosing the scalar you assign to in parentheses:
my ($key) = keys %hash;
Another way would be to use each in scalar context:
my $key = each %hash;
In the testing loop, you are only interested in values, so don't iterate over the keys, too:
for my $arr_ref (values %hash) {
I tried to loop through a hash using each on a for each loop. Looks like the $k $v is not updated. Can anyone explain?
%hash = (a=>5,b=>6);
foreach( my ($k,$v) = each %hash){
print "\neach loop : $k $v\n";
}
output :
each loop : a 5
each loop : a 5
foreach takes a list of values, and executes its loop body once per value, assigning some variable ($_ if otherwise unspecified) each time:
foreach ( 1, 2, 3 ) {
print "The value is $_\n";
}
In your case, you gave it a list of two things, being the first key and value taken from the hash. Additionally, you also assigned those two new variables, $key and $value to be the key and value. Thus, your loop executed twice, with those variables remaining constant throughout.
A better way to iterate keys and values from a hash is to iterate on the list of keys, taking a value each time:
foreach my $key ( keys %hash ) {
my $value = $hash{$key};
...
}
Alternatively, you might enjoy the pairs function from List::Util version 1.39:
foreach my $pair ( pairs %hash ) {
my $key = $pair->key;
my $value = $pair->value;
}
Use the while loop.
#!/usr/bin/perl
use strict;
use warnings;
my %hash = (a=>5,b=>6);
while (my ($key, $value) = each %hash) {
print "Key is $key, value is $value\n";
}
Demo
Also see: Is perl's each function worth using?
You need to do while instead of foreach:
my %hash = (a=>5,b=>6);
while( my ($k,$v) = each %hash){
print "\neach loop : $k $v\n";
}
However, each() has gotachas that you need to be aware of, so I prefer just using keys instead, like this:
for my $k (keys %hash) { my $v = $hash{$k}; }
foreach my %hash (%myhash1,%myhash2,%myhash3)
{
while (($keys,$$value) = each %hash)
{
#use key and value...
}
}
Why doesn't this work :
it says synta error on foreach line.
Pls tell me why is it wrong.
This is wrong because you seem to think that this allows you to access each hash as a separate construct, whereas what you are in fact doing is, besides a syntax error, accessing the hashes as a mixed-together new list. For example:
my %hash1 = qw(foo 1 bar 1);
my %hash2 = qw(abc 1 def 1);
for (%hash1, %hash2) # this list is now qw(foo 1 bar 1 abc 1 def 1)
When you place a hash (or array) in a list context statement, they are expanded into their elements, and their integrity is not preserved. Some built-in functions do allow this behaviour, but normal Perl code does not.
You also cannot assign a hash as the for iterator variable, that can only ever be a scalar value. What you can do is this:
for my $hash (\%myhash1, \%myhash2, \%myhash3) {
while (my ($key, $value) = each %$hash) {
...
Which is to say, you create a list of hash references and iterate over them. Note that you cannot tell the difference between the hashes with this approach.
Note also that I use my $hash because this variable must be a scalar.
The syntax should be like:
my $hash1 = {'a'=>1};
my $hash2 = {'b'=>1};
my #arr2 = ($hash1, $hash2);
foreach $hash (#arr2)
{
while(($key, $value) = each %$hash)
{
print $key, $value;
}
}
you need to reference and then dereference the hash.
In perl 5.10.1 is it ok to add new members to a hash while iterating through it with the each operator?
Smth. like in this code (preparing data for Google charts), where I have a hash of hashes of arrays and I am trying to move the last element of each array:
sub split_aclr($) {
my $csvData = shift;
while (my ($csv, $href) = each %$csvData) {
next unless $csv =~ /_ACLR_/i;
my $len = scalar #{$href->{MEASUREMENT}};
if ($len > 1 && $href->{MEASUREMENT}->[$len - 1] eq 'CARRIER_CHANNEL') {
while (my ($key, $arr) = each %$href) {
my $moved = pop #$arr;
$csvData{$csv . '_CARRIER_CHANNEL'} = {$key => [ $moved ]};
}
}
}
}
It is not generally safe to add or remove entries of a hash while iterating it using each. If you want to do so, it might be best to save an explicit list of keys you want to iterate to an array, and loop over that.
Instead of
while (my $k, $v) = each %hash) {
$hash{"foo_$k"} = $v;
}
do
my #keys = keys %hash;
for my $k (#keys) {
my $v = $hash{$k};
$hash{"foo_$k"} = $v;
}
It isn't safe. If you do it, you will get a warning: "Use of each() on hash after insertion without resetting hash iterator results in undefined behavior."
The documentation for each() is pretty clear on this.
If you add or delete elements of a hash while you're iterating over
it, you may get entries skipped or duplicated, so don't.
What is the best practise to solve this?
if (... )
{
push (#{$hash{'key'}}, #array ) ;
}
else
{
$hash{'key'} ="";
}
Is that bad practise for storing one element is array or one is just double quote in hash?
I'm not sure I understand your question, but I'll answer it literally as asked for now...
my #array = (1, 2, 3, 4);
my $arrayRef = \#array; # alternatively: my $arrayRef = [1, 2, 3, 4];
my %hash;
$hash{'key'} = $arrayRef; # or again: $hash{'key'} = [1, 2, 3, 4]; or $hash{'key'} = \#array;
The crux of the problem is that arrays or hashes take scalar values... so you need to take a reference to your array or hash and use that as the value.
See perlref and perlreftut for more information.
EDIT: Yes, you can add empty strings as values for some keys and references (to arrays or hashes, or even scalars, typeglobs/filehandles, or other scalars. Either way) for other keys. They're all still scalars.
You'll want to look at the ref function for figuring out how to disambiguate between the reference types and normal scalars.
It's probably simpler to use explicit array references:
my $arr_ref = \#array;
$hash{'key'} = $arr_ref;
Actually, doing the above and using push result in the same data structure:
my #array = qw/ one two three four five /;
my $arr_ref = \#array;
my %hash;
my %hash2;
$hash{'key'} = $arr_ref;
print Dumper \%hash;
push #{$hash2{'key'}}, #array;
print Dumper \%hash2;
This gives:
$VAR1 = {
'key' => [
'one',
'two',
'three',
'four',
'five'
]
};
$VAR1 = {
'key' => [
'one',
'two',
'three',
'four',
'five'
]
};
Using explicit array references uses fewer characters and is easier to read than the push #{$hash{'key'}}, #array construct, IMO.
Edit: For your else{} block, it's probably less than ideal to assign an empty string. It would be a lot easier to just skip the if-else construct and, later on when you're accessing values in the hash, to do a if( defined( $hash{'key'} ) ) check. That's a lot closer to standard Perl idiom, and you don't waste memory storing empty strings in your hash.
Instead, you'll have to use ref() to find out what kind of data you have in your value, and that is less clear than just doing a defined-ness check.
I'm not sure what your goal is, but there are several things to consider.
First, if you are going to store an array, do you want to store a reference to the original value or a copy of the original values? In either case, I prefer to avoid the dereferencing syntax and take references when I can:
$hash{key} = \#array; # just a reference
use Clone; # or a similar module
$hash{key} = clone( \#array );
Next, do you want to add to the values that exist already, even if it's a single value? If you are going to have array values, I'd make all the values arrays even if you have a single element. Then you don't have to decide what to do and you remove a special case:
$hash{key} = [] unless defined $hash{key};
push #{ $hash{key} }, #values;
That might be your "best practice" answer, which is often the technique that removes as many special cases and extra logic as possible. When I do this sort of thing in a module, I typically have a add_value method that encapsulates this magic where I don't have to see it or type it more than once.
If you already have a non-reference value in the hash key, that's easy to fix too:
if( defined $hash{key} and ! ref $hash{key} ) {
$hash{key} = [ $hash{key} ];
}
If you already have non-array reference values that you want to be in the array, you do something similar. Maybe you want an anonymous hash to be one of the array elements:
if( defined $hash{key} and ref $hash{key} eq ref {} ) {
$hash{key} = [ $hash{key} ];
}
Dealing with the revised notation:
if (... )
{
push (#{$hash{'key'}}, #array);
}
else
{
$hash{'key'} = "";
}
we can immediately tell that you are not following the standard advice that protects novices (and experts!) from their own mistakes. You're using a symbolic reference, which is not a good idea.
use strict;
use warnings;
my %hash = ( key => "value" );
my #array = ( 1, "abc", 2 );
my #value = ( 22, 23, 24 );
push(#{$hash{'key'}}, #array);
foreach my $key (sort keys %hash) { print "$key = $hash{$key}\n"; }
foreach my $value (#array) { print "array $value\n"; }
foreach my $value (#value) { print "value $value\n"; }
This does not run:
Can't use string ("value") as an ARRAY ref while "strict refs" in use at xx.pl line 8.
I'm not sure I can work out what you were trying to achieve. Even if you remove the 'use strict;' warning, the code shown does not detect a change from the push operation.
use warnings;
my %hash = ( key => "value" );
my #array = ( 1, "abc", 2 );
my #value = ( 22, 23, 24 );
push #{$hash{'key'}}, #array;
foreach my $key (sort keys %hash) { print "$key = $hash{$key}\n"; }
foreach my $value (#array) { print "array $value\n"; }
foreach my $value (#value) { print "value $value\n"; }
foreach my $value (#{$hash{'key'}}) { print "h_key $value\n"; }
push #value, #array;
foreach my $key (sort keys %hash) { print "$key = $hash{$key}\n"; }
foreach my $value (#array) { print "array $value\n"; }
foreach my $value (#value) { print "value $value\n"; }
Output:
key = value
array 1
array abc
array 2
value 22
value 23
value 24
h_key 1
h_key abc
h_key 2
key = value
array 1
array abc
array 2
value 22
value 23
value 24
value 1
value abc
value 2
I'm not sure what is going on there.
If your problem is how do you replace a empty string value you had stored before with an array onto which you can push your values, this might be the best way to do it:
if ( ... ) {
my $r = \$hash{ $key }; # $hash{ $key } autoviv-ed
$$r = [] unless ref $$r;
push #$$r, #values;
}
else {
$hash{ $key } = "";
}
I avoid multiple hash look-ups by saving a copy of the auto-vivified slot.
Note the code relies on a scalar or an array being the entire universe of things stored in %hash.