Pushing a hash to an existing hash in Perl - perl

I'm new to Perl, so you'll have to forgive my code.
I'm reading a file that tree structured (like xml, just not actually) and I want to foreach through the tree and if a certain "node" doesn't have a child node, I want to insert it. Simple enough.
Here's my code:
foreach $key ( #{$struct->{'transferBatch'}->{'callEventDetails'} } ) {
foreach ( keys %{$key} ) {
if ( $_ eq "mobileTerminatedCall" ) {
if ( defined $key->{$_}->{'basicServiceUsedList'} ) {
if ( defined $key->{$_}->{'basicServiceUsedList'}[0]->{'chargeInformationList'} ) {
if ( not defined $key->{$_}->{'basicServiceUsedList'}[0]->{'chargeInformationList'}[0]->{'CallTypeGroup'} ) {
$CallTypeGroup = {
"CallTypeLevel1:" => "0",
"CallTypeLevel2:" => "0",
"CallTypeLevel3:" => "0"
};
#Doesn't work!
$key->{$_}->{'basicServiceUsedList'}[0]->{'chargeInformationList'}[0]{'CallTypeGroup'} = $CallTypeGroup;
}
}
}
}
}
}
The iteration is working fine, but my push call fails saying it's not an ARRAY reference. I feel like I'm close, but I need that line to insert the $CallTypeGroup hash as a child to the current spot.
Any help is appreciated!

$key->{$_}->{'basicServiceUsedList'}[0]->{'chargeInformationList'}[0]
contains a reference to a hash, as created here (if not earlier):
if ( not defined
$key->{$_}{basicServiceUsedList}[0]
->{chargeInformationList}[0]
->{CallTypeGroup} )
You did not indicate what data structure you want, so I'm not sure how we can help other than explain the message. I think you simply want to change that if to
if ( not defined
$key->{$_}{basicServiceUsedList}[0]
->{chargeInformationList}[0] )
btw, you really should use variables to hold the intermediate references instead of having such long "names".
btw, I'm highly skeptical of all those hardcoded zero indexes.

Related

perl hash ref return: { 'a' =>1, % { sub() } } [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
{ 'a' =>1, % { sub() } }
Is this best for including a hash returned as ref into other?
%$sub() does not work.
As far I new {} makes new hash and returns reference, probably new optimized away this case, but not sure.
I'm guessing that you have some subroutine that returns a hash reference, and that you want to include all of those keys in a larger hash:
my $hash_ref = some_sub(...);
my $big_hash = { a => 1 };
There are a couple of ways that you can do. In you question, it looks like you are trying to do it inline by dereferencing the return value. That can be reasonable. The circumfix notation or postfix dereference can do this:
# general circumfix (old way)
my $big_hash = { a => 1, %{ some_sub() } };
# postfix dereference (new v5.24 hotness)
my $big_hash = { a => 1, some_sub()->%* };
However, I tend to not like to do this so directly. Typically I'm doing this when there are default values that might be overridden by whatever some_sub() is:
my %defaults = ( ... );
my %big_hash = ( %defaults, some_sub()->%* );
But, I usually go a step further by making the thing that produces the defaults into another subroutine so I can give subclassers a way to override it:
sub defaults {
my %defaults = ( ... );
return \%defaults;
}
my %big_hash = ( defaults()->%*, some_sub()->%* );
There are many other ways to merge hashes. There's How can I combine hashes in Perl? on StackOverflow, but also How do I merge two hashes in perlfaq4.
But, there's another thing to consider. Simply mashing two hashes together to get a new one might be expensive. What if the first hash is very, very large?
my %grand_hash = ( %very_big_hash, %new_hash );
People often do this and assign back to the starting hash, mostly because it's easy to type:
my %grand_hash = ( %grand_hash, %new_hash );
You're telling Perl to unroll %grand_hash, combine another list with it, then re-hash the huge list.
Although a bit more unwieldy, a better way is to add the new keys and values.
foreach my $new_key ( keys %new_hash ) {
$grand_hash{$new_key} = $new_hash{$new_key};
}
That's nice when you need to do something else, such as skipping keys that you already have in the hash:
foreach my $new_key ( keys %new_hash ) {
next if exists $grand_hash{$new_key};
$grand_hash{$new_key} = $new_hash{$new_key};
}
or maybe adding to a value that is already there instead of replacing it:
foreach my $new_key ( keys %new_hash ) {
$grand_hash{$new_key} += $new_hash{$new_key};
}
If you just need to add it and don't care about replacing values, a hash slice is nice for assigning multiple keys and values at the same time:
#grand_hash{ keys %new_hash } = values %new_hash;
In your case, you'd call the subroutine once and store the result so you don't have to construct the hash again. You then dereference that hash is the slice (or wherever you want to use it:
my $new_hash = some_sub(...);
#grand_hash{ keys %$new_hash } = values %$new_hash;
Yes, to dereference a hash reference and get a list of key/value pairs, wrap %{ ... } around the expression that generates the hash reference.
Or, since perl 5.24, you can use the postfix dereference syntax sub()->%*

how to combine two next if together

I have a script that run on the list of files to do some of the changes, each file of them has a call event and the call event details contains 4 elements so i just want to do the changes on 2 of them only here I'm stuck with how to combine two next if in one loop, here I have used 2 loops to do the job but it takes more time , is there any idea about how to do that ?
my $calleventtag = $struct->{'transferBatch'}->{'callEventDetails'};
my #indexes = reverse (grep { exists $calleventtag->[$_]->{'supplServiceEvent'} } 0..$#$calleventtag);
my $sup_event_cnt = $#indexes;
foreach my $index (#indexes)
{
splice (#$calleventtag , $index,1);
}
foreach (0..$#$calleventtag)
{
next if ( ! exists $calleventtag->[$_]->{'mobileOriginatedCall'}) ;
if ( exists $calleventtag->[$_]->{'mobileOriginatedCall'}->{'basicCallInformation'}->{'destinationNetwork'} )
{
delete $calleventtag->[$_]->{'mobileOriginatedCall'}->{'basicCallInformation'}->{'destinationNetwork'};
}
if ( exists $calleventtag->[$_]->{'mobileOriginatedCall'}->{'basicCallInformation'}->{'chargeableSubscriber'}->{'simChargeableSubscriber'}->{'msisdn'}
&& $calleventtag->[$_]->{'mobileOriginatedCall'}->{'basicCallInformation'}->{'chargeableSubscriber'}->{'simChargeableSubscriber'}->{'msisdn'} !~ m/^96279/
)
{
delete $calleventtag->[$_]->{'mobileOriginatedCall'}->{'basicCallInformation'}->{'chargeableSubscriber'}->{'simChargeableSubscriber'}->{'msisdn'};
}
}
foreach (0..$#$calleventtag)
{
next if ( ! exists $calleventtag->[$_]->{'gprsCall'});
if ( exists $calleventtag->[$_]->{'gprsCall'}->{'gprsBasicCallInformation'}->{'gprsDestination'}->{'accessPointNameOI'} )
{
delete $calleventtag->[$_]->{'gprsCall'}->{'gprsBasicCallInformation'}->{'gprsDestination'}->{'accessPointNameOI'};
}
}
for (...) {
next if ...;
...
}
can also be written as
for (...) {
if (!...) {
...
}
}
You could use the following:
use Data::Diver qw( Dive );
my $call_event_details = Dive($struct, qw( transferBatch callEventDetails ));
for my $call_event_detail (#$call_event_details) {
next if !$call_event_detail->{supplServiceEvent};
if ( my $bci = Dive($call_event_detail, qw( mobileOriginatedCall basicCallInformation )) ) {
delete $bci->{destinationNetwork};
if ( my $scs = $bci->{simChargeableSubscriber} ) {
my $msisdc = $scs->{msisdn};
delete $scs->{msisdn} if $msisdc && $msisdc !~ /^96279/;
}
}
if ( my $dest = Dive($call_event_detail, qw( gprsCall gprsBasicCallInformation gprsDestination )) ) {
delete $dest->{accessPointNameOI};
}
}
Notes:
The quotes around string literals aren't needed in hash indexes if the string is valid valid identifier. For example, $hash->{'foo'} can be written as $hash->{foo}.
-> isn't needed between two indexes. For example, $hash->{foo}->{bar} can be written as $hash->{foo}{bar}.
If a hash element is either a reference or doesn't exist, you don't need to use exists to check if you have a reference; you can use a simple truth test since references are always true.
[BUG FIX] $hash->{foo}{bar} can autovivify $hash->{foo} (cause a reference to be assigned to it), so your tests to check if stuff exists could actually be causing things to be created. To fix this, you can replace
if ($hash->{foo}{bar})
with
if ($hash->{foo} && $hash->{foo}{bar})
or
if (Dive($hash, qw( foo bar )))
Using the same long chain of indexes (->{foo}{bar}{baz}) repeatedly is error prone.
It's best to use plural names for arrays. First, it's more descriptive, but it also makes choosing names for loop variables easier.
Speaking of variable names, why would use $calleventtag for the name of the variable containing callEventDetails nodes?
You don't need to check if a hash element exists before trying to delete it; delete can be passed an element that doesn't exist.
No need to loop over the indexes of an array if you don't need the indexes.
grep was a good choice, but splice was not. You should have used: $calleventtag = [ grep { ... } #$calleventtag ];. I moved the check into the loop.

how to copy(insert) hash reference to another hash reference in perl?

recently started doing perl. I'm reading old code and trying to rewrite some stuff. I've got a question here about hash references.
#declar anon hash ref that will be returned
my $store_hash = {};
foreach my $item (#list)
{
#this will iterate based on list
my $ret_hash = ops_getval($item, $user)
#do some magic here to map $ret_hash into $store_hash
}
ops_getval is a function that returns a type of ref hash. I want to insert those values into $store_hash. How should I approach this? Can I directly do
$store_hash = ops_getval($var1,$var2)
Much appreciated!
I think the standard way to do this is:
#$store_hash{ keys %$ret_hash } = values %$ret_hash;
This merges all of the hashes returned by all of the calls to ops_getval into $store_hash.
An alternate approach that might be clearer to the eye, possibly at the cost of a lot of redundant data copying:
%$store_hash = (%$store_hash, %$ret_hash);
You would do something like:
$store_hash->{$item} = $ret_hash
In general:
$hashref->{$key} = $value
See here for more: http://perldoc.perl.org/perlref.html#Using-References
To be clear, you can use a loop and get this done.
foreach ( keys%{ $ret_hash } ){
$store_hash->{ $_ } = $ret_hash->{ $_ } ;
}

Perl nesting hash of hashes

I'm having some trouble figuring out how to create nested hashes in perl based on the text input.
i need something like this
my % hash = {
key1 => \%inner-hash,
key2 => \%inner-hash2
}
However my problem is I don't know apriori how many inner-hashes there would be. To that end I wrote the following piece of snippet to test if a str variable can be created in a loop and its reference stored in an array and later dereferenced.
{
if($line =~ m/^Limit\s+$mc_lim\s+$date_time_lim\s+$float_val\s+$mc\s+$middle_junk\s+$limit \s+$value/) {
my $str = $1 . ' ' . $2 . ' ' . $7;
push (#test_array_reference, \$str);
}
}
foreach (#test_array_reference) {
say $$_;
}
Perl dies with a not a scalar run-time error. I'm a bit lost here. Any help will be appreciated.
To answer your first (main?) question, you don't need to know how many hashes to create if you walk through the text and create them as you go. This example uses words of a string, delimited by spaces, as keys but you can use whatever input text for your purposes.
my $text = 'these are just a bunch of words';
my %hash;
my $hashRef = \%hash; # create reference to initial hash
foreach (split('\s', $text)){
$hashRef->{$_} = {}; # create anonymous hash for current word
$hashRef = $hashRef->{$_}; # walk through hash of hashes
}
You can also refer to any arbitrary inner hash and set the value by,
$hash{these}{are}{just}{a}{bunch}{of}{words} = 88;
$hash{these}{are}{just}{a}{bunch}{of}{things} = 42;
$hash{these}{things} = 33;
To visualize this, Data:Dumper may help,
print Dumper %hash;
Which generates,
$VAR1 = 'these';
$VAR2 = {
'things' => 33,
'are' => {
'just' => {
'a' => {
'bunch' => {
'of' => {
'things' => 42,
'words' => 88
}
}
}
}
}
};
my $hashref = { hash1 => { key => val,... },
hash2 => { key => val,..} };
also you may want to use the m//x modifier with your regex, its barely readable as it is.
Creating a hash of hashes is pretty simple:
my %outer_hash = {};
Not entirely necessary, but this basically means that each element of your hash is a reference to another hash.
Imagine an employee hash keyed by employee number:
$employee{$emp_num}{first} = "Bob";
$employee{$emp_num}{last} = "Smith";
$employee{$emp_num}{phones}{cell} = "212-555-1234";
$employee{$emp_num}{phones}{desk} = "3433";
The problem with this notation is that it gets rather hard to read after a while. Enter the arrow notation:
$employee{$emp_num}->{first} = "Bob";
$employee{$emp_num}->{last} = "Smith";
$employee{$emp_num}->{phones}->{cell} = "212-555-1234";
$employee{$emp_num}->{phones}->{desk} = "3433";
The big problem with complex structures like this is that you lose the use strict ability to find errors:
$employee{$emp_num}->{Phones}->{cell} = "212-555-1234";
Whoops! I used Phones instead of phones. When you start using this type of complex structure, you should use object oriented syntax. Fortunately, the perlobj tutorial is pretty easy to understand.
By the way, complex data structure handling and the ability to use object oriented Perl puts you into the big leagues. It's the first step into writing more powerful and complex Perl.

Perl hash key determined by array

I have an array and I am making a hash instance from it.
For instance, if array is:
#folders=(temp,usr,bin);
then i want to fill in hash:
$the_path{$folders[0]}{$folders[1]}{$folders[2]}="somevalue";
But if the array is only:
#folders=(bin);
then i want the path to be:
$the_path{$folders[0]}="somevalue";
The problem is I dont know beforehand how long the array is gonna be, and I would really like to avoid making x if statements for that solution scales terribly.
How do I do this?
First, that's not how you define an array in Perl. You probably want to say
my #folders = ( 'temp', 'usr', 'bin' );
There's an old trick for making nested hash keys from a list:
my %the_path;
my $tmp = \%the_path;
foreach my $item( #folders ) {
$tmp->{$item} = { };
$tmp = $tmp->{$item};
}
This will result in a structure like the following:
$VAR1 = {
'temp' => {
'usr' => {
'bin' => {}
}
}
};
If you want to replace the empty hashref at the bottom-most level with a string, you can keep track of a count variable inside the loop.