perl hash of hashes - for eacy key group inner keys and respective values - perl

I have a hash of hashes retrieved from json and I am trying to flatten hash, group and print values. That is, for each key1 group all keys and respective values.Trying to achieve this in perl using below code but, it is printing individual values each ID and names but not grouped values. Could you please help me how to group and print keys and values.
Data:
VAR1= {
'ID1' => {'Name1' => {'Address1' => {'Mod1' => ['pol1']}},
Name2' => {'Address1' => {'Mod2' => ['pol2']}}},
'ID2' => {'Name3' => {'Address3' => {'Mod3' => ['pol3']}}},
'ID3' => {'Name4' => {'Address4' => {'Mod4' => ['pol1, pol2, pol3']}}}
}
Expected Output:
ID1 => [Name1,Address1,mod1(pol1), Name2,Address1,mod2(pol2)],
ID2 => [Name3,Address3,mod3(pol3)],
ID3 => [Name4,Address4,mod4(pol1,pol2,pol3)]
Code1:
for my $id (#id_list) {
foreach my $item (sort (slurp(\%{$new{$id}}))) {
my $data = join(',', #$item);
print "data.. $data\n";
push(#results,$data);
}
}
print map {"$_"} #results;
Code2:
foreach my $id (sort keys %new){
foreach my $name (keys %{$new{$id} }) {
foreach my $address (keys %{$new{$id}{$name}}) {
foreach my $mod (keys %{$new{$id}{$name}{$address}) {
foreach my $value (#{$new{$id}{$name}{$address}{$mod}}) {
my $sp = ":";
my $reasons = join(',',$id,$name,$address,$mod,$value.$sp);
push (#rea, $reasons);
}
}
}
}
}

With such a nested structure you want to go systematically, like in your "Code2".
use warnings;
use strict;
use feature qw(say);
my %id_list = (
ID1 => {
Name1 => {'Address1' => {'Mod1' => ['pol1']}},
Name2 => {'Address1' => {'Mod2' => ['pol2']}}
},
ID2 => { 'Name3' => {'Address3' => {'Mod3' => ['pol3']}} },
ID3 => { 'Name4' => {'Address4' => {'Mod4' => ['pol1, pol2, pol3']}} }
);
my (%flattened, #ar, $modvals);
foreach my $id (sort keys %id_list){
foreach my $name (keys %{$id_list{$id} }) {
foreach my $address (keys %{$id_list{$id}{$name}}) {
foreach my $mod (keys %{$id_list{$id}{$name}{$address}}) {
$modvals = "$mod(" .
join(',', #{$id_list{$id}{$name}{$address}{$mod}})
. ')';
$modvals =~ s/,$//; # strip the last comma
push #ar, $name, $address, $modvals;
}
}
}
$flattened{$id} = [ #ar ];
#ar = ();
}
say "$_ => #{$h{$_}}" for sort keys %flattened;
Output
ID1 => Name1 Address1 Mod1(pol1) Name2 Address1 Mod2(pol2)
ID2 => Name3 Address3 Mod3(pol3)
ID3 => Name4 Address4 Mod4(pol1, pol2, pol3)
This generates the string for mod3 as (pol1, ...) -- as given in input, as a single string. If that is chaged to a list of pol in input, the spaces between pol's will be gone in output.
A hash with id keys and array ref with respective entries is generated.

Related

How to dereference only hash of hash of array in Perl?

{
'A' => ['B'], //hash of array
'C' => {'D' => [ 'E']} //hash of hash of array
}
When try to parse hash of hash of array. getting "Not a HASH reference" error. Even I tried used exists and defined keywords to avoid this error. But result is same error.
From above i need to only iterate hash of hash of array.
foreach my $keys (keys %$hash){
print "$keys";
if (defined $hash->{$keys}->{maptype}){
foreach my $array_element ( #{$hash->{$keys}->{'D'}} ) {
print "$array_element");
}
}
}
Not sure if this is best practice, but:
my $hash = { A => ['B'], C => {D => ['E'], F => [qw(G H I)]}, J=>42 };
for my $key (keys %$hash) {
if (ref $hash->{$key} eq 'HASH') {
for my $subkey (keys %{$hash->{$key}}) {
say "$key => $subkey => [", join(",", #{$hash->{$key}{$subkey}}), "]";
}
}
}
outputs
C => D => [E]
C => F => [G,H,I]

Print the value in Hash of Hash for two different keys in Perl

Below is a hash in Perl:
my %hash = (
'episode1' => {
'when' => '08.13.97',
'airdate' => '08.13.97',
'episodenumber' => '101',
'id' => '103511',
'title' => 'Cartman Gets an Anal Probe',
'available' => 'true'
},
'episode2' => {
'when' => '08.20.97',
'airdate' => '08.20.97',
'episodenumber' => '102',
'id' => '1035156',
'title' => 'Weight Gain 4000',
'available' => 'true'
}
);
I want to print the "id" of both episodes,but the below code is not working:
foreach my $key1 ( keys %hash ) {
foreach my $key2 ( keys %{$hash{$key1}} ) {
print "$hash{$key1}{$key2}{id}\n";
}
}
Please help.
The problem is that you're trying to print something that doesn't exist: There is no value that matches $hash{$key1}{$key2}{id}.
Try this code, which prints out the value in the hash of hashes that has the key "id":
use strict;
use warnings;
for my $episode (keys %hash){
print "$hash{$episode}{id}\n";
}
103511
1035156
Try using map:
my #ids = map { $hash{$_}{"id"} } sort keys %hash;
Or if you still need the results as a hash:
my %ids_by_key = map { ($_, $hash{$_}{"id"}) } keys %hash;
Try this:
foreach my $key1 ( keys %hash ) {
print "$hash{$key1}{id}\n";
}
or
foreach my $key1 ( keys %hash ) {
foreach my $key2 ( keys %{$hash{$key1}} ) {
print "$hash{$key1}{$key2}\n" if($key2 eq 'id');
}
}

Converting HoA to HoH with counting

Have this code:
use 5.020;
use warnings;
use Data::Dumper;
my %h = (
k1 => [qw(aa1 aa2 aa1)],
k2 => [qw(ab1 ab2 ab3)],
k3 => [qw(ac1 ac1 ac1)],
);
my %h2;
for my $k (keys %h) {
$h2{$k}{$_}++ for (#{$h{$k}});
}
say Dumper \%h2;
produces:
$VAR1 = {
'k1' => {
'aa2' => 1,
'aa1' => 2
},
'k3' => {
'ac1' => 3
},
'k2' => {
'ab1' => 1,
'ab3' => 1,
'ab2' => 1
}
};
Is possible to write the above code with "another way"? (e.g. simpler or more compact)?
Honestly, I don't like the number of times $h2{$k} is evaluated.
my %h2;
for my $k (keys %h) {
my $src = $h{$k};
my $dst = $h2{$k} = {};
++$dst->{$_} for #$src;
}
A subroutine can help make the intent more obvious. Maybe.
sub counts { my %c; ++$c{$_} for #_; \%c }
$h2{$_} = counts(#{ $h{$_} }) for keys %h;
That can be simplified if you do the change in-place.
sub counts { my %c; ++$c{$_} for #_; \%c }
$_ = counts(#$_) for values %h;

Perl: Sorting hash of hash by value descending order

data :
%HoH => (
abc => {
value => "12",
},
xyz => {
number => "100",
},
pqr => {
digit => "5",
}
)
How do I sort the hash of hash by value in descending order?
Output
100
12
5
You can't sort a hash, it won't hold the order. If you wanted to keep them sorted, you'll have to sort the keys based on the number and store the keys in an array.
#!/usr/bin/perl
use strict;
use warnings;
my %HoH = (
abc => { value => 12 },
xyz => { value => 100},
pqr => { value => 5},
def => { value => 15},
hij => { value => 30},
);
my #sorted_keys = map { $_->[0] }
sort { $b->[1] <=> $a->[1] } # use numeric comparison
map { my $temp;
if ( exists $HoH{$_}{'value'} ) {
$temp = $HoH{$_}{'value'};
} elsif ( exists $HoH{$_}{'number'} ) {
$temp = $HoH{$_}{'number'};
} elsif ( exists $HoH{$_}{'digit'} ) {
$temp = $HoH{$_}{'digit'};
} else {
$temp = 0;
}
{[$_, $temp]} }
(keys %HoH);
for my $key (#sorted_keys) {
my $temp;
if ( exists $HoH{$key}{'value'} ) {
$temp = $HoH{$key}{'value'};
} elsif ( exists $HoH{$key}{'number'} ) {
$temp = $HoH{$key}{'number'};
} elsif ( exists $HoH{$key}{'digit'} ) {
$temp = $HoH{$key}{'digit'};
} else {
$temp = 0;
}
print $key . ":" . $temp ."\n";
}
Output:
xyz:100
hij:30
def:15
abc:12
pqr:5
This technique to do the sorting is called Schwartzian Transform.
Given you're not actually using the keys for anything, you can flatten the data structure into a single array and then sort it:
use strict;
use warnings;
my %HoH = (
abc => {value => "12",},
xyz => {number => "100",},
pqr => {digit => "5",},
);
my #numbers = sort {$b <=> $a} map {values %$_} values %HoH;
print "$_\n" for #numbers;
Outputs:
100
12
5
However, if you want to use the additional key information, then you'll need fold your Hash of Hash into an array, and then you can sort however you like:
my #array;
while (my ($k, $ref) = each %HoH) {
while (my ($k2, $v) = each %$ref) {
push #array, [$k, $k2, $v];
}
}
#array = sort {$b->[2] <=> $a->[2]} #array;
use Data::Dump;
dd \#array;
Outputs:
[
["xyz", "number", 100],
["abc", "value", 12],
["pqr", "digit", 5],
]
I came up with this solution
#!/usr/bin/perl
use strict;
use warnings;
my %HoH = (
abc => {
value => "12",
},
xyz => {
number => "100",
},
pqr => {
digit => "5",
}
);
my %rever;
for my $TopKey(keys %HoH){
for my $value(values %{ $HoH{$TopKey} }){
push #{ $rever{$value} }, $TopKey;
}
}
my #nums = sort {$b <=> $a} (keys(%rever));
print $_, "\n" for #nums;
I reversed the values in case you still needed to use the key names.
This is how it looks after using Dumper.
$VAR1 = '100';
$VAR2 = [
'xyz'
];
$VAR3 = '12';
$VAR4 = [
'abc'
];
$VAR5 = '5';
$VAR6 = [
'pqr'
];

Printing Hash of Hash into a Matrix Table in Perl

I have a data structure like this:
#!/usr/bin/perl -w
my $hash = {
'abTcells' => {
'mesenteric_lymph_node' => {
'Itm2a' => '664.661',
'Gm16452' => '18.1425',
'Sergef' => '142.8205'
},
'spleen' => {
'Itm2a' => '58.07155',
'Dhx9' => '815.2795',
'Ssu72' => '292.889'
}
}
};
What I want to do is to print it out into this format:
mesenteric_lymph_node spleen
Itm2a 664.661 58.07155
Gm16452 18.1425 NA
Sergef 142.8205 NA
Dhx9 NA 815.2795
Ssu72 NA 292.889
What's the way to do it.
I'm currently stuck with the following code https://eval.in/44207
foreach my $ct (keys %{$hash}) {
print "$ct\n\n";
my %hash2 = %{$hash->{$ct}};
foreach my $ts (keys %hash2) {
print "$ts\n";
my %hash3 = %{$hash2{$ts}};
foreach my $gn (keys %hash3) {
print "$gn $hash3{$gn}\n";
}
}
}
Use Text::Table for output. Beautify to taste.
#!/usr/bin/env perl
use strict;
use warnings;
use Text::Table;
my $hash = {
'abTcells' => {
'mesenteric_lymph_node' => {
'Itm2a' => '664.661',
'Gm16452' => '18.1425',
'Sergef' => '142.8205'
},
'spleen' => {
'Itm2a' => '58.07155',
'Dhx9' => '815.2795',
'Ssu72' => '292.889'
}
}
};
my $struct = $hash->{abTcells};
my #cols = sort keys %{ $struct };
my #rows = sort keys %{ { map {
my $x = $_;
map { $_ => undef }
keys %{ $struct->{$x} }
} #cols } };
my $tb = Text::Table->new('', #cols);
for my $r (#rows) {
$tb->add($r, map $struct->{$_}{$r} // 'NA', #cols);
}
print $tb;
Output:
mesenteric_lymph_node spleen
Dhx9 NA 815.2795
Gm16452 18.1425 NA
Itm2a 664.661 58.07155
Sergef 142.8205 NA
Ssu72 NA 292.889
Now, the order of the rows above is different than the one you show because I wanted it to be consistent. If you know the set of all possible rows, then you can specify another order obviously.
First thing would be to separate out the two hashes:
my %lymph_node = %{ $hash->{abTcells}->{mesenteric_lymph_node} };
my %spleen = %{ $hash->{abTcells}->{spleen} };
Now, you have two separate hashes that contains the data you want.
What we need is a list of all the keys. Let's make a third hash that contains your keys.
my %keys;
map { $keys{$_} = 1; } keys %lymph_node, keys %spleen;
Now, we can go through all your keys and print the value for each of the two hashes. If one of the hashes doesn't have the data, we'll set it to NA:
for my $value ( sort keys %keys ) {
my $spleen_value;
my $lymph_nodes_value;
$spleen_value = exists $spleen{$value} ? $spleen{$value} : "NA";
$lymph_node_value = exists $lymph_node{$value} ? $lymph_node{$value} : "NA";
printf "%-20.20s %-9.5f %-9.5f\n", $key, $lymph_node_value, $spleen_value;
}
The printf statement is a nice way to tabularize data. You'll have to create the headings yourself. The ... ? ... : ... statement is an abbreviated if/then/else If the statement before the ? is true, then the value is the value between the ? and the :. Else, the value is the value after the :.
Both of your inner hashes have the same keys, So do a foreach on one of the hashes to get the key, and then print both.