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.
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.
Is there a simple way to validate a hash of hash element comparsion ?
I need to validate a Perl hash of hash element $Table{$key1}{$key2}{K1}{Value} compare to all other elements in hash
third key will be k1 to kn and i want comprare those elements and other keys are same
if ($Table{$key1}{$key2}{K1}{Value} eq $Table{$key1}{$key2}{K2}{Value}
eq $Table{$key1}{$key2}{K3}{Value} )
{
#do whatever
}
Something like this may work:
use List::MoreUtils 'all';
my #keys = map "K$_", 1..10;
print "All keys equal"
if all { $Table{$key1}{$key2}{$keys[1]}{Value} eq $Table{$key1}{$key2}{$_}{Value} } #keys;
I would use Data::Dumper to help with a task like this, especially for a more general problem (where the third key is more arbitrary than 'K1'...'Kn'). Use Data::Dumper to stringify the data structures and then compare the strings.
use Data::Dumper;
# this line is needed to assure that hashes with the same keys output
# those keys in the same order.
$Data::Dumper::Sortkeys = 1;
my $string1= Data::Dumper->Dump($Table{$key1}{$key2}{k1});
for ($n=2; exists($Table{$key1}{$key2}{"k$n"}; $n++) {
my $string_n = Data::Dumper->Dump($Table{$key1}{$key2}{"k$n"});
if ($string1 ne $string_n) {
warn "key 'k$n' is different from 'k1'";
}
}
This can be used for the more general case where $Table{$key1}{$key2}{k7}{value} itself contains a complex data structure. When a difference is detected, though, it doesn't give you much help figuring out where that difference is.
A fairly complex structure. You should be looking into using object oriented programming techniques. That would greatly simplify your programming and the handling of these complex structures.
First of all, let's simplify a bit. When you say:
$Table{$key1}{$key2}{k1}{value}
Do you really mean:
my $value = $Table{$key1}->{$key2}->{k1};
or
my $actual_value = $Table{$key1}->{$key2}->{k1}->{Value};
I'm going to assume the first one. If I'm wrong, let me know, and I'll update my answer.
Let's simplify:
my %hash = %{$Table{$key1}->{$key2}};
Now, we're just dealing with a hash. There are two techniques you can use:
Sort the keys of this hash by value, then if two keys have the same value, they will be next to each other in the sorted list, making it easy to detect duplicates. The advantage is that all the duplicate keys would be printed together. The disadvantage is that this is a sort which takes time and resources.
Reverse the hash, so it's keyed by value and the value of that key is the key. If a key already exists, we know the other key has a duplicate value. This is faster than the first technique because no sorting is involved. However, duplicates will be detected, but not printed together.
Here's the first technique:
my %hash = %{$Table{$key1}->{$key2}};
my $previous_value;
my $previous_key;
foreach my $key (sort {$hash{$a} cmp $hash{$b}} keys %hash) {
if (defined $previous_key and $previous_value eq $hash{$key}) {
print "\$hash{$key} is a duplicate of \$hash{$previous_key}\n";
}
$previous_value = $hash{$key};
$previous_key = $key;
}
And the second:
my %hash = %{$Table{$key1}->{$key2}};
my %reverse_hash;
foreach $key (keys %hash) {
my $value = $hash{$key};
if (exists $reverse_hash{$value}) {
print "\$hash{$reverse_hash{$value}} has the same value as \$hash{$key}\n";
}
else {
$reverse_hash{$value} = $key;
}
}
Alternative approach to the problem is make utility function which will compare all keys if has same value returned from some function for all keys:
sub AllSame (&\%) {
my ($c, $h) = #_;
my #k = keys %$h;
my $ref;
$ref = $c->() for $h->{shift #k};
$ref ne $c->() and return for #$h{#k};
return 1
}
print "OK\n" if AllSame {$_->{Value}} %{$Table{$key1}{$key2}};
But if you start thinking in this way you can found this approach much more generic (recommended way):
sub AllSame (#) {
my $ref = shift;
$ref ne $_ and return for #_;
return 1
}
print "OK\n" if AllSame map {$_->{Value}} values %{$Table{$key1}{$key2}};
If mapping operation is expensive you can make lazy counterpart of same:
sub AllSameMap (&#) {
my $c = shift;
my $ref;
$ref = $c->() for shift;
$ref ne $c->() and return for #_;
return 1
}
print "OK\n" if AllSameMap {$_->{Value}} values %{$Table{$key1}{$key2}};
If you want only some subset of keys you can use hash slice syntax e.g.:
print "OK\n" if AllSame map {$_->{Value}} #{$Table{$key1}{$key2}}{map "K$_", 1..10};
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.