I am trying to create a hash of hash of - the nesting depth depends on the number of arguments passed into #aGroupByFields array.
In the below implementation, I am getting the desired hash structure.But I have hard coded the fields [ example - $phBugRecord->{createdBy} ] instead of deriving it from the array.
I am not sure how to dynamically create this.
my (#aGroupByFields) = ['createdBy','status','devPriority'];
# In real case,these are passed in as arguments
my (%hTemp);
# This is the final hash which will be structured according to above fields
# %hBugDetails is the hash containing details of all bugs
foreach my $phBugRecord ( #{ $hBugDetails{records} } ) {
# The below statement needs to be generated dynamically as
# opposed to the hard-coded values.
push(
#{
$hTemp{ $phBugRecord->{createdBy} }{ $phBugRecord->{status} }
{ $phBugRecord->{devPriority} }
},
$phBugRecord
);
}
Any pointer will be a great help.Thanks.
Here is a working implementation with Data::Diver.
use strict;
use warnings;
use Data::Diver 'DiveVal';
use Data::Printer;
my %hBugDetails = (
records => [
{
createdBy => 'created_by1',
status => 'status1',
devPriority => 'dev_priority1',
foo => 'foo1',
bar => 'bar1',
},
{
createdBy => 'created_by1',
status => 'status2',
devPriority => 'dev_priority2',
foo => 'foo',
bar => 'bar',
},
],
);
# we want to group by these fields
my #group_by = ( 'createdBy', 'status', 'devPriority' );
my $grouped_bugs = {}; # for some reason we need to start with an empty hashref
foreach my $bug ( #{ $hBugDetails{records} } ) {
# this will auto-vivify the hash for us
push #{ DiveVal( $grouped_bugs, map { $bug->{$_} } #group_by ) }, $bug;
}
p $grouped_bugs;
The output looks like this.
\ {
created_by1 {
status1 {
dev_priority1 [
[0] {
bar "bar1",
createdBy "created_by1",
devPriority "dev_priority1",
foo "foo1",
status "status1"
}
]
},
status2 {
dev_priority2 [
[0] {
bar "bar",
createdBy "created_by1",
devPriority "dev_priority2",
foo "foo",
status "status2"
}
]
}
}
}
Note that I renamed your variables. It was very hard to read the code like that. It makes more sense to just use speaking names instead of cryptic abbreviations for the type of variable. The sigil already does that for you.
This code will do what you need
my #aGroupByFields = qw/ createdBy status devPriority /;
my %hTemp;
for my $phBugRecord ( #{ $hBugDetails{records} } ) {
my $hash = \%hTemp;
for my $field ( #aGroupByFields ) {
my $key = $phBugRecord->{$field};
if ( $field eq $aGroupByFields[-1] ) {
push #{ $hash->{ $key } }, $phBugRecord;
}
else {
$hash = $hash->{ $key } //= {};
}
}
}
Related
I have hash which contains some data.
I want my final %hash to be printed like this:
'UGroup=1' => [ 'C72', 'C73', 'C71' ]
Here is my script:
use Data::Dumper;
my %h = (
'C72' => [ 'S=2-1' ],
'C73' => [ 'S=3-1' ],
'C71' => [ 'S=91-1'],
'UGroup=1' => [ 'S=1-1',
'S=2-1',
'S=3-1',
'S=91-1'],
);
print Dumper(\%h);
foreach my $C (sort keys %h) {
next unless $C =~ /UGroup/;
for my $f (#{$h{$C}}){
print "\tf:$f\n";
#This is not correct, but wanted to do something like this.
push #{$hash{$C}}, $f if(exists $h{$f});
}
}
print Dumper(\%hash);
Here in example input hash I need to check if S=91-1 has any key? If yes then associate that key to value for %hash with its original key.
How can I do that?
You didn't name the things, so
S=91-1 shall be a snake,
C71 shall be a cow, and
UGroup=1 shall be a group.
Start by building this hash:
my %cows_by_snake = (
'S=91-1' => [ 'C71' ],
'S=2-1' => [ 'C72' ],
'S=3-1' => [ 'C73' ],
);
Just ignore the keys that of %h that are groups when you do so.
Once you built a hash, it's simply a question of doing the following:
Create an empty result hash.
For each group,
Create an empty collection of cows.
For each snake associated the the group,
Add the cows associated with the snake to the collection.
Eliminate the duplicates in the collection of cows.
Add the group and the associated cows to the result hash.
my #groups;
my #cows;
for my $cow_or_group (keys(%h)) {
if ($cow_or_group =~ /^UGroup=/) {
push #groups, $cow_or_group;
} else {
push #cows, $cow_or_group;
}
}
my %cows_by_snake;
for my $cow (#cows) {
for my $snake (#{ $h{$cow} }) {
push #{ $cows_by_snake{$snake} }, $cow;
}
}
my %results;
for my $group (#groups) {
my %group_cows;
for my $snake (#{ $h{$group} }) {
for my $cow (#{ $cows_by_snake{$snake} }) {
++$group_cows{$cow};
}
}
$results{$group} = [ sort keys %group_cows ];
}
I have below hash structure.
$VAR1 = {
'USA' => {
'Alabama' => {
'ISO3' => 'ISO3:3166-2:US',
'ISO2' => 'ISO2:4166-23:US',
'UNI' => 'UNIABR-A',
'UNDP' => 'UNDP-ZXC-1',
'FAOSTAT' => 'STAT20.98',
'GAUL' => 'UL-SD-20/40'
},
'Washington' => {
'ISO3' => 'ISO3:40-166-2:US',
'ISO2' => 'ISO2:30-23:US',
'UNI' => 'UNIISO-B',
'UNDP' => 'UNDP-YXC-2',
'FAOSTAT' => 'STAT30.98.78',
'GAUL' => 'UL-SD-30/60'
}
}
};
What i would like to achieve is to iterate through the above hash and get the statename and country name for value inside hash "ISO2:4166-23:US". what i have tried to do is:
I can get the required output with the below code.
my $find = "ISO2:4166-23:US";
my $statename;
while ( my ($country, $states) = each (%$VAR1) ) {
while (my ($states, $otherkeys) = each (%$states) ) {
while (my ($otherkeys, $value) = each %$otherkeys) {
$statename = $states if ($value eq $find);
}
}
}
print "State name for value [$find] is :: $statename \n"; ### Output : Alabama
Is there any way I can get -
Get the top-level key "USA" and second-level key "Alabama" if $value is equal to ISO2:4166-23:US. What i know is the value inside hash for which I need to get the output, key corresponding to my search value doesn't matter.
with one-liner grep command from above hash?
Any pointers in the right direction would be useful. Thanks.
Your variables are poorly named. Reusing $states for the state name? ouch.
my $find = "ISO2:4166-23:US";
my $found_state_name;
while ( my ($country_name, $states) = each(%$VAR1) ) {
while (my ($state_name, $state) = each(%$states) ) {
while ( my ($key, $value) = each(%$state) ) {
if ($value eq $find) {
$found_state_name = $state_name;
}
}
}
}
Now, it would be nice to stop searching as soon as you a result is found. We can't do that while still using each(cause it'll screw up later keys/values/each on those hashes).
my $find = "ISO2:4166-23:US";
my $found_state_name;
FIND:
for my $country_name (keys(%$VAR1)) {
my $country = $VAR1->{$country_name};
for my $state_name (keys(%$country)) {
my $state = $country->{$state_name};
for my $key (keys(%$state)) {
if ($state->{$key} eq $find) {
$found_state_name = $state_name;
last FIND;
}
}
}
}
We never use $country_name or $key except to get the value.
my $find = "ISO2:4166-23:US";
my $found_state_name;
FIND:
for my $states (values(%$VAR1)) {
for my $state_name (keys(%$states)) {
my $state = $country->{$state_name};
for my $value (values(%$state)) {
if ($value eq $find) {
$found_state_name = $state_name;
last FIND;
}
}
}
}
If you know you're looking for an ISO2 value, this simplifies to the following:
my $find = "ISO2:4166-23:US";
my $found_state_name;
FIND:
for my $states (values(%$VAR1)) {
for my $state_name (keys(%$states)) {
my $state = $states->{$state_name};
if ($state->{ISO2} eq $find) {
$found_state_name = $state_name;
last FIND;
}
}
}
You want to use grep, eh? Since you want a state name as a result, we need to grep a list of state names.
my #state_names = ...;
my ($found_state_name) =
grep { ... }
#state_names;
We can obtain the list of state name using
my #state_names =
map { keys(%$_) }
values(%$VAR1);
But that's not quite enough to perform the check. (For now, I'm going to assume that only the ISO2 property needs to be checked.)
my #state_names =
map { keys(%$_) }
values(%$VAR1);
my ($found_state_name) =
grep { $VAR1->{???}{$_}{ISO2} eq $find }
#state_names;
There are two solutions. You can work with country-state pairs.
my #country_state_name_pairs =;
map {
my $country_name = $_;
map { [ $country_name, $_ ] }
keys(%{ $VAR1->{$country_name} )
}
keys(%$VAR1);
my ($found_state_name) =
map { $_->[1] }
grep {
my ($country_name, $state_name) = #$_;
$VAR1->{$country_name}{$state_name}{ISO2} eq $find
}
#country_state_name_pairs;
Or you can create a flat lists of states and search that.
my #states_with_name =
map { [ $_, $VAR1->{$_} ] }
values(%$VAR1);
my ($found_state_name) =
map { $_->[0] }
grep { $_->[1]{ISO2} eq $find }
#states_with_name;
Noting stops us from merging the two statements.
my ($found_state_name) =
map { $_->[0] } # Get the state name.
grep { $_->[1]{ISO2} eq $find } # Filter out undesireable states.
map { [ $_, $VAR1->{$_} ] } # $state_name => [ $state_name, $state ]
values(%$VAR1); # Get the countries.
This last one isn't too bad!
Finally, there are two ways to modify each of the above to search all the fields instead of just ISO2). (I'm going to only the modifications to the latter of the above two solutions.)
my ($found_state_name) =
map { $_->[0] } # Get the state name.
grep { # Filter out undesireable states.
grep { $_ eq $find } # Filter out undesireable properties of the state.
values(%{ $_->[1] }) # Get the state's property values.
}
map { [ $_, $VAR1->{$_} ] } # $state_name => [ $state_name, $state ]
values(%$VAR1); # Get the countries.
or
my ($found_state_name) =
map { $_->[0] } # Get the state name.
grep { $_->[1] eq $find } # Filter out undesireable states.
map { # $state_name => multiple [ $state_name, $value ]
my $state_name = $_;
map { [ $state_name, $_ ] } # value => [ $state_name, $value ]
values(%{ $VAR1->{$_} ) # Get the state's property values.
}
values(%$VAR1); # Get the countries.
These aren't readable. They are best avoided.
Finally, if you are going to perform many searches based on ISO2, it would be best if you organized your data in terms of ISO2.
my %by_iso2 = (
'ISO2:4166-23:US' => {
country_name => 'USA',
state_name => 'Alabama',
ISO2 => 'ISO2:4166-23:US',
ISO3 => 'ISO3:3166-2:US',
...
},
'ISO2:4166-23:US' => {
country_name => 'USA',
state_name => 'Washington',
ISO2 => 'ISO2:30-23:US',
ISO3 => 'ISO3:40-166-2:US',
...
},
...
);
No. Hashes are one-directional, so you have to loop over all the values to find one you are searching for.
It sounds like what you really want is some sort of database and that a hash is the wrong tool for your job.
You can build your hash the other way around, using the ISO2 as the key, e.g.
$VAR1 = {
"ISO2:4166-23:US" => { Country => 'USA', State => 'Alabama' },
"ISO2:4166-23:US" => { Country => 'USA', State => 'Washington' }
}
If you are planning to do these lookups a lot, it might be worth the while. Doing that can be automated too. Using a similar loop to build a new hash to use as lookup.
Speed-wise, there is nothing wrong with looping over all the hash keys. The difference between direct lookup and looping will be negligible, unless you have a truly huge hash.
I have a Perl data structurte like so
%myhash = (
k1 => v1,
kArray => [
{
name => "anonymous hash",
...
},
\&funcThatReturnsHash,
{
name => "another anonymous hash",
...
}
]
);
Elsewhere I iterate through the list in kArray which contains a bunch of hashes. I would like to either process the actual hash OR the hash returned by the function.
foreach my $elem( #{myhash{kArray}} ) {
if (ref($elem) == "CODE") {
%thisHash = &$elem;
}
else {
%thisHash = %$elem;
}
...
}
However ref ($elem) is always scalar or undefined. I tried func, &func, \&func, \%{&func}, in %myhash to no effect.
how do I extract the hash within the function in the main body?
Apart from the code sample you give being invalid Perl, the main problems seem to be that you are using == to compare strings instead of eq, and you are assigning a hash reference to a hash variable %thishash. I assure you that ref $elem never returns SCALAR with the data you show
It would help you enormously if you followed the common advice to use strict and use warnings at the top of your code
This will work for you
for my $elem ( #{ $myhash{kArray} } ) {
my $this_hash;
if ( ref $elem eq 'CODE' ) {
$this_hash = $elem->();
}
else {
$this_hash = $elem;
}
# Do stuff with $this_hash
}
or you could just use a map like this
use strict;
use warnings;
use 5.010;
use Data::Dump;
my %myhash = (
k1 => v1,
kArray => [
{
name => "anonymous hash",
},
\&funcThatReturnsHash,
{
name => "another anonymous hash",
}
]
);
for my $hash ( map { ref eq 'CODE' ? $_->() : $_ } #{ $myhash{kArray} } ) {
say $hash->{name};
}
sub funcThatReturnsHash {
{ name => 'a third anonymous hash' };
}
output
anonymous hash
a third anonymous hash
another anonymous hash
If you turn on strict and warnings, you'll see that:
foreach my $elem(#{mynahs{kArray}}) {
Isn't valid. You need at the very least a $ before mynahs.
But given something like this - your approach works - here's an example using map to 'run' the code references:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
sub gimme_hash {
return { 'fish' => 'paste' };
}
my $stuff =
[ { 'anon1' => 'value' },
\&gimme_hash,
{ 'anon2' => 'anothervalue' }, ];
my $newstuff = [ map { ref $_ eq "CODE" ? $_->() : $_ } #$stuff ];
print Dumper $newstuff;
Turns that hash into:
$VAR1 = [
{
'anon1' => 'value'
},
{
'fish' => 'paste'
},
{
'anon2' => 'anothervalue'
}
];
But your approach does work:
foreach my $element ( #$stuff ) {
my %myhash;
if ( ref $element eq "CODE" ) {
%myhash = %{$element -> ()};
}
else {
%myhash = %$element;
}
print Dumper \%myhash;
}
Gives:
$VAR1 = {
'anon1' => 'value'
};
$VAR1 = {
'fish' => 'paste'
};
$VAR1 = {
'anon2' => 'anothervalue'
};
I have a question I'm hoping you could help with?
This is the last part I need help with in understanding hash references
Code:
my $content_lengths; # this is at the top
foreach my $url ( # ... more stuff
# compare
if ( $mech->response->header('Content-Length') != $content_length ) {
print "$child_url: different content length: $content_length vs "
. $mech->response->header('Content-Length') . "!\n";
# store the urls that are found to have different content
# lengths to the base url only if the same url has not already been stored
$content_lengths->{$url}->{'different'}->{$child_url} = $mech->response->header('Content-Length');
} elsif ( $mech->response->header('Content-Length') == $content_length ) {
print "Content lengths are the same\n";
# store the urls that are found to have the same content length as the base
# url only if the same url has not already been stored
$content_lengths->{$url}->{'equal'}->{$child_url} = $mech->response->header('Content-Length');
}
What it looked like using Data::Dumper
$VAR1 = {
'http://www.superuser.com/' => {
'difference' => {
'http://www.superuser.com/questions' => '10735',
'http://www.superuser.com/faq' => '13095'
},
'equal' => {
'http://www.superuser.com/ ' => '20892'
}
},
'http://www.stackoverflow.com/' => {
'difference' => {
'http://www.stackoverflow.com/faq' => '13015',
'http://www.stackoverflow.com/questions' => '10506'
},
'equal' => {
'http://www.stackoverflow.com/ ' => '33362'
}
}
};
What I need help with:
I need help understanding the various ways of accessing the different parts in the hash reference and using them to do things, such as print them.
So for example how do I print all the $url from the hash reference (i.e from Data::Dumper that will be http://www.superuser.com/ and http://www.stackoverflow.com/)
and how do I print all the $child_url or a particular one/subset from $child_url and so on?
Your help with this is much appreciated,
thanks a lot
You can navigate your hashref thusly:
$hashref->{key1}{key2}{keyN};
For example, if you want the superuser equal branch:
my $urlArrayref = $hashref->{'http://www.superuser.com/'}{'equal'};
More to the point, to print the urls (first level key) of the hashref, you would do:
foreach my $key ( keys( %{$hashref} ) ) {
print( "key is '$key'\n" );
}
Then if you wanted the second level keys:
foreach my $firstLevelKey ( keys( %{$hashref} ) ) {
print( "first level key is '$firstLevelKey'\n" );
foreach my $secondLevelKey ( keys( %{$hashref->{$firstLevelKey}} ) ) {
print( "\tfirst level key is '$secondLevelKey'\n" );
}
}
And so forth...
----- EDIT -----
This is working sample code from your example above:
#!/usr/bin/perl
use strict;
use warnings;
my $content_lengths = {
'http://www.superuser.com/' => {
'difference' => {
'http://www.superuser.com/questions' => '10735',
'http://www.superuser.com/faq' => '13095'
},
'equal' => {
'http://www.superuser.com/ ' => '20892'
}
},
'http://www.stackoverflow.com/' => {
'difference' => {
'http://www.stackoverflow.com/faq' => '13015',
'http://www.stackoverflow.com/questions' => '10506'
},
'equal' => {
'http://www.stackoverflow.com/ ' => '33362'
}
}
};
foreach my $key1 ( keys( %{$content_lengths} ) ) {
print( "$key1\n" );
foreach my $key2 ( keys( %{$content_lengths->{$key1}} ) ) {
print( "\t$key2\n" );
foreach my $key3 ( keys( %{$content_lengths->{$key1}{$key2}} ) ) {
print( "\t\t$key3\n" );
}
}
}
Which results in this output:
http://www.superuser.com/
difference
http://www.superuser.com/questions
http://www.superuser.com/faq
equal
http://www.superuser.com/
http://www.stackoverflow.com/
difference
http://www.stackoverflow.com/faq
http://www.stackoverflow.com/questions
equal
http://www.stackoverflow.com/
I have a TT plugin that does the trivial unique ids:
sub get_unique_uid_tt {
my ( $classname, $o ) = #_;
my %h;
foreach my $item ( #{$o} ) {
unless ( exists $h{ $item->id } ) {
$h{ $item->id } = 1;
}
}
return keys %h;
}
where the template call is simply:
[% Namespace.get_unique_uid_tt( data.users ) %]
and "data" is an RDB Object, users being one of its relationships. I have verified that the ".users" returns a list in Perl directly, whether the relationship has one or many elements.
However, it appears that TT returns the element for single-element lists, while properly returning lists for multiple element.
I looked this up and found that you can force list context with ".list":
[% Namespace.get_unique_uid_tt( data.users.list ) %]
This does not work as intended for single-element lists, as a Data::Dumper revealed:
$VAR1 = [
{
'value' => 1,
'key' => '__xrdbopriv_in_db'
},
{
'value' => bless(
... snip ...
),
'key' => 'db'
},
{
'value' => '1',
'key' => 'id'
}
];
instead of the expected
$VAR1 = [
bless( {
'__xrdbopriv_in_db' => 1,
'id' => '1',
'db' => ... snip ...
}, 'DataClass' )
];
Is there any other simple way in TT to get a list of objects, even on single-element lists? (One approach is to rewrite the function, but one that does not would be preferable)
Found this on the TT mailing list:
http://lists.template-toolkit.org/pipermail/templates/2009-December/011061.html
seems like TT's ".list" has trouble converting objects to lists in general, not just RDBOs.
The suggestion is make a vmethod:
$Template::Stash::LIST_OPS->{ as_list } = sub {
return ref( $_[0] ) eq 'ARRAY' ? shift : [shift];
};
I added this to my context object (same idea):
$context->define_vmethod(
'list',
'as_list',
sub {
return ref( $_[0] ) eq 'ARRAY' ? shift : [shift];
},
);
It's not quite what you're after, but could you alter the TT plugin to handle both lists and single items?
sub get_unique_uid_tt {
my ( $classname, $o ) = #_;
my %h;
if (ref $o eq 'ARRAY') {
foreach my $item ( #{$o} ) {
unless ( exists $h{ $item->id } ) {
$h{ $item->id } = 1;
}
}
}
else {
return ($o->id);
}
return keys %h;
}