Multi dimensional hash sort - Perl [closed] - perl

It's difficult to tell what is being asked here. This question is ambiguous, vague, incomplete, overly broad, or rhetorical and cannot be reasonably answered in its current form. For help clarifying this question so that it can be reopened, visit the help center.
Closed 10 years ago.
I really need some help in understanding this hash and processing it with sort.
Here is the hash:
$VAR1 = {
Key1:Key1_si => {
'KeyA' => {
Keya => 'abcd, defg',
keyb => '1000',
keyc => '80%',
keyd => '2011.10.09',
keye => '1234-UR-DDDD',
keyf => 'rwh',
keyg => '600',
keyh => 'red',
keyi => '900',
keyj => '',
keyk =>'int4678_tt',
},
'KeyB' => {
Keya => 'abcd, defg',
keyb => '2000',
keyc => '100%',
keyd => '2011.11.09',
keye => '1234-UR-DDDD',
keyf => 'rwh',
keyg => '500',
keyh => 'red',
keyi => '400',
keyj => '',
keyk =>'int4678_tt',
},
},
};
Question: I want to sort this hash on the basis of 'keyc' whose value is varied. So, I want to sort on the basis like below:
Key1:Key1_si->KeyB->Keyc
Key1:Key1_si->KeyA->keyc
Also, I want to have the output with the sorted values along with the rest of attributes like :
Print:
Key1:Key1_si KeyB Keya keyd Keyc keyf
Key1:Key1_si KeyA keya keyd Keyc keyf
Can somebody please help me with the code in perl to perform sort as mentioned above. I will really appreciate your time and efforts.

You want to sort a list of key pairs, so you have to start by building a list of key pairs. A reference to an array is the obvious answer. Once you've figured this out, everything is straight forward.
Building the list of keys:
my #unsorted_keys;
for my $k1 (keys(%$VAR1)) {
for my $k2 (keys(%{ $VAR1->{$k1} })) {
push #unsorted_keys, [ $k1, $k2 ];
}
}
Sorting those key:
my #sorted_keys = sort {
my ($a_k1, $a_k2) = #$a;
my ($b_k1, $b_k2) = #$b;
( my $a_pc = $VAR1->{$a_k1}{$a_k2}{keyc} ) =~ s/%//;
( my $b_pc = $VAR1->{$b_k1}{$b_k2}{keyc} ) =~ s/%//;
$a_pc <=> $b_pc
} #unsorted_keys;
Iterating over the sorted keys:
for (#sorted_keys) {
my ($k1, $k2) = #$_;
my $hash = $VAR1->{$k1}{$k2};
... do stuff with %$hash ...
}

Related

Inserting one hash into another using Perl

I've tried many different versions of using push and splice, but can't seem to combine two hashes as needed. Trying to insert the second hash into the first inside the 'Item' array:
(
ItemData => { Item => { ItemNum => 2, PriceList => "25.00", UOM => " " } },
)
(
Alternate => {
Description => "OIL FILTER",
InFile => "Y",
MfgCode => "FRA",
QtyAvailable => 29,
Stocked => "Y",
},
)
And I need to insert the second 'Alternate' hash into the 'Item' array of the first hash for this result:
(
ItemData => {
Item => {
Alternate => {
Description => "OIL FILTER",
InFile => "Y",
MfgCode => "FRA",
QtyAvailable => 29,
Stocked => "Y",
},
ItemNum => 2,
PriceList => "25.00",
UOM => " ",
},
},
)
Can someone suggest how I can accomplish this?
Assuming you have two hash references, this is straight-forward.
my $item = {
'ItemData' => {
'Item' => {
'PriceList' => '25.00',
'UOM' => ' ',
'ItemNum' => '2'
}
}
};
my $alt = {
'Alternate' => {
'MfgCode' => 'FRA',
'Description' => 'OIL FILTER',
'Stocked' => 'Y',
'InFile' => 'Y',
'QtyAvailable' => '29'
}
};
$item->{ItemData}->{Item}->{Alternate} = $alt->{Alternate};
The trick here is not to actually merge $alt into some part of $item, but to only take the specific part you want and put it where you want it. We take the Alternate key from $alt and put it's content into a new Alternate key inside the guts of $item.
Adam Millerchip pointed out in a hence deleted comment that this is not a copy. If you alter any of the keys inside of $alt->{Alternative} after sticking it into $item, the data will be changed inside of $item as well because we are dealing with references.
$item->{ItemData}->{Item}->{Alternate} = $alt->{Alternate};
$alt->{Alternate}->{InFile} = 'foobar';
This will actually also change the value of $item->{ItemData}->{Item}->{Alternate}->{InFile} to foobar as seen below.
$VAR1 = {
'ItemData' => {
'Item' => {
'ItemNum' => '2',
'Alternate' => {
'Stocked' => 'Y',
'MfgCode' => 'FRA',
'InFile' => 'foobar',
'Description' => 'OIL FILTER',
'QtyAvailable' => '29'
},
'UOM' => ' ',
'PriceList' => '25.00'
}
}
};
References are supposed to do that, because they only reference something. That's what's good about them.
To make a real copy, you need to dereference and create a new anonymous hash reference.
# create a new ref
# deref
$item->{ItemData}->{Item}->{Alternate} = { %{ $alt->{Alternate} } };
This will create a shallow copy. The values directly inside of the Alternate key will be copies, but if they contain references, those will not be copied, but referenced.
If you do want to merge larger data structures where more than the content of one key needs to be merged, take a look at Hash::Merge instead.

Perl- Get Hash Value from Multi level hash

I have a 3 dimension hash that I need to extract the data in it. I need to extract the name and vendor under vuln_soft-> prod. So far, I manage to extract the "cve_id" by using the following code:
foreach my $resultHash_entry (keys %hash){
my $cve_id = $hash{$resultHash_entry}{'cve_id'};
}
Can someone please provide a solution on how to extract the name and vendor. Thanks in advance.
%hash = {
'CVE-2015-6929' => {
'cve_id' => 'CVE-2015-6929',
'vuln_soft' => {
'prod' => {
'vendor' => 'win',
'name' => 'win 8.1',
'vers' => {
'vers' => '',
'num' => ''
}
},
'prod' => {
'vendor' => 'win',
'name' => 'win xp',
'vers' => {
'vers' => '',
'num' => ''
}
}
},
'CVE-2015-0616' => {
'cve_id' => 'CVE-2015-0616',
'vuln_soft' => {
'prod' => {
'name' => 'unity_connection',
'vendor' => 'cisco'
}
}
}
}
First, to initialize a hash, you use my %hash = (...); (note the parens, not curly braces). Using {} declares a hash reference, which you have done. You should always use strict; and use warnings;.
To answer the question:
for my $resultHash_entry (keys %hash){
print "$hash{$resultHash_entry}->{vuln_soft}{prod}{name}\n";
print "$hash{$resultHash_entry}->{vuln_soft}{prod}{vendor}\n";
}
...which could be slightly simplified to:
for my $resultHash_entry (keys %hash){
print "$hash{$resultHash_entry}{vuln_soft}{prod}{name}\n";
print "$hash{$resultHash_entry}{vuln_soft}{prod}{vendor}\n";
}
because Perl always knows for certain that any deeper entries than the first one is always a reference, so the deref operator -> isn't needed here.

How can I look and search for a key inside a heavily nested hash?

I am trying to check if a BIG hash has any keys from small hash and see if they exist, and if they do modify the BigHash with updated values from small hash.
So the lookup hash would look like this :
configure =(
CommonParameter => {
'SibSendOverride' => 'true',
'SibOverrideEnabledFlag' => 'true',
'SiPosition' => '8',
'Period' => '11'
}
)
But the BigHash is very very nested.. The key/hash CommonParameter from the small hash configure is there in the BigHash.
Can somebody help/suggest some ideas for me please?
Here is an example BigHash :
%BigHash = (
'SibConfig' => {
'CELL' => {
'Sib9' => {
'HnbName' => 'HnbName',
'CommonParameter' => {
'SibSendOverride' => 'false',
'SibMaskOverrideEnabledFlag' => 'false',
'SiPosition' => '0',
'Period' => '8'
}
}
}
},
)
I hope I was clear in my question. Trying to modify values of heavily nested BigHash based on Lookup Hash if those keys exist.
Can somebody help me? I am not approaching this in the right way. Is there a neat little key lookup fucntion or something available perhaps?
Give Data::Search a try.
use Data::Search;
#results = Data::Search::datasearch(
data => $BigHash, search => 'keys',
find => 'CommonParameter',
return => 'hashcontainer');
foreach $result (#results) {
# result is a hashref that has 'CommonParameter' as a key
if ($result->{CommonParameter}{AnotherKey} ne $AnotherValue) {
print STDERR "AnotherKey was ", $result->{CommonParameter}{AnotherKey},
" ... fixing\n";
$result->{CommonParameter}{AnotherKey} = $AnotherValue;
}
}

How do I access certain keys in a Perl nested hash?

I dumped a data structure:
print Dumper($bobo->{'issues'});
and got:
$VAR1 = {
'155' => {
'name' => 'Gender',
'url_name' => 'gender'
}
};
How can I extract 155?
How about if I have:
$VAR1 = {
'155' => {'name' => 'Gender', 'url_name' => 'gender'},
'11' => {'name' => 'Toddler', 'url_name' => 'toddler'},
'30' => {'name' => 'Lolo', 'url_name' => 'lolo'}
};
I want to print one key, i.e. the first or second to see the value of the key?
So, based on the example you posted, the hash looks like this:
$bobo = {
issues => {
155 => {
name => 'Gender',
url_name => 'gender',
},
},
};
'155' is a key in your example code. To extract a key, you would use keys.
my #keys = keys %{$bobo->{issues}};
But to get the value that 155 indexes, you could say:
my $val = $bobo->{issues}{155};
Then $val would contain a hashref that looks like this:
{
name => 'Gender',
url_name => 'gender'
}
Have a look at perldoc perlreftut.
It is a key in the hash referenced by $bobo->{'issues'}. So you would iterate through
keys %{$bobo->{'issues'}}
to find it.

Array of hashes

In perl , i have an array of hashes
like
0 HASH(0x98335e0)
'title' => 1177
'author' => 'ABC'
'quantity' => '-100'
1 HASH(0x832a9f0)
'title' => 1177
'author' => 'ABC'
'quantity' => '100'
2 HASH(0x98335e0)
'title' => 1127
'author' => 'DEF'
'quantity' => '5100'
3 HASH(0x832a9f0)
'title' => 1277
'author' => 'XYZ'
'quantity' => '1030'
Now I need to accumulate the quantity where title and author are same.
In the above structure for hash with title = 1177 and author ='ABC' quantity can be accumulated into one and the entire structure should looks like below
0 HASH(0x98335e0)
'title' => 1177
'author' => 'ABC'
'quantity' => 0
1 HASH(0x98335e0)
'title' => 1127
'author' => 'DEF'
'quantity' => '5100'
2 HASH(0x832a9f0)
'title' => 1277
'author' => 'XYZ'
'quantity' => '1030'
What is the best way i can do this accumulation so that it is optimised? Number of array elements can be very large. I dont mind adding an extra key to the hash to aid the same , but i dont want n lookups . Kindly advise
my %sum;
for (#a) {
$sum{ $_->{author} }{ $_->{title} } += $_->{quantity};
}
my #accumulated;
foreach my $author (keys %sum) {
foreach my $title (keys %{ $sum{$author} }) {
push #accumulated => { title => $title,
author => $author,
quantity => $sum{$author}{$title},
};
}
}
Not sure whether map makes it look nicer:
my #accumulated =
map {
my $author = $_;
map { author => $author,
title => $_,
quantity => $sum{$author}{$_},
},
keys %{ $sum{$author} };
}
keys %sum;
If you don't want N lookups, then you need a hash function -- however you need to store them with that hash function. By the time you have them in a list (or array), it's too late. You either get lucky, all the time, or you're going to have N lookups.
Or insert them into the hash abovebelow. A hybrid solution is to store a locator as item 0 in the list/array.
my $lot = get_lot_from_whatever();
my $tot = $list[0]{ $lot->{author} }{ $lot->{title} };
if ( $tot ) {
$tot->{quantity} += $lot->{quantity};
}
else {
push #list, $list[0]{ $lot->{author} }{ $lot->{title} } = $lot;
}
previous
First of all we'll reformat that to make it readable.
[ { title => 1177, author => 'ABC', quantity => '-100' }
, { title => 1177, author => 'ABC', quantity => '100' }
, { title => 1127, author => 'DEF', quantity => '5100' }
, { title => 1277, author => 'XYZ', quantity => '1030' }
]
Next, you need to break down the problem. You want quantities of things grouped
by author and title. So you need those things to uniquely identify those lots.
To repeat, you want a combination of names to identify entities. Thus, you
will need a hash that identifies things by names.
Since we have two things, a double hash is a good way to do it.
my %hash;
foreach my $lot ( #list ) {
$hash{ $lot->{author} }{ $lot->{title} } += $lot->{quantity};
}
# consolidated by hash
To turn this back into a list, we need to unbundle the levels.
my #consol
= sort { $a->{author} cmp $b->{author} || $a->{title} cmp $b->{title} }
map {
my ( $a, $titles ) = #$_; # $_ is [ $a, {...} ]
map { +{ title => $_, author => $a, quantity => $titles->{$_} }
keys %$titles;
}
map { [ $_ => $hash{$_} ] } # group and freeze a pair
keys %hash
;
# consolidated in a list.
And there you have it back, I even sorted it for you. Of course you could also
sort this by--publishers being what they are--descending quantities.
sort { $b->{quantity} <=> $a->{quantity}
|| $a->{author} cmp $b->{author}
|| $a->{title} cmp $b->{title}
}
I think it is important to step back and consider the source of the data. If the data are coming from a database, then you should write the SQL query so that it gives you one row for each author/title combination with the total quantity in the quantity field. If you are reading the data from a file, then you should either read it directly into a hash or use Tie::IxHash if order is important.
Once you have the data in an array of hashrefs like you do, you will have to create an auxiliary data structure and do a whole bunch of lookups, the cost of which may well dominate the running time of your program (not in a way it matters if it is run for 15 minutes once a day) and you might run into memory issues.