How to copy a nested hash - perl

How to copy a multi level nested hash(say, %A) to another hash(say, %B)? I want to make sure that the new hash does not contain same references(pointers) as the original hash(%A).
If I change anything in the original hash (%A), it should not change
anything in the new hash(%B).
I want a generic way do it. I know I can do it by reassigning by value
for each level of keys(like, %{ $b{kb} } = %a;).
But, there should be a solution which would work irrespective of the number of key levels(hash of hash of hash of .... hash of hash)
PROBLEM EXAMPLE
use Data::Dumper;
my %a=(q=>{
q1=>1,
q2=>2,
},
w=>2);
my %b;
my %c;
%{ $b{kb} } = %a;
print "\%b=[".Data::Dumper::Dumper (%b)."] ";
%{ $c{kc} } = %a; # $b{kb} = \%a;
print "\n\%c=[".Data::Dumper::Dumper (%c)."] ";
# CHANGE THE VALUE OF KEY IN ORIGINAL HASH %a
$a{q}{q1} = 2; # $c{kc} = \%a;
print "\n\%b=[".Data::Dumper::Dumper (%b)."] ";
print "\n\%c=[".Data::Dumper::Dumper (%c)."] ";
Appreciate your help

What you want is commonly known as a "deep copy", where as the assignment operator does a "shallow copy".
use Storable qw( dclone );
my $copy = dclone($src);

Related

Perl: Passing by reference does not modify the hash

My understanding was that in Perl we pass hashes to functions by reference
Consider the following example, where we modify the hash in the modifyHash function
#!/usr/local/bin/perl
my %hash;
$hash{"A"} = "1";
$hash{"B"} = "2";
print (keys %hash);
print "\n";
modifyHash(\%hash);
print (keys %hash);
print "\n";
sub modifyHash {
my $hashRef = #_[0];
my %myHash = %$hashRef;
$myHash{"C"} = "3";
print (keys %myHash);
print "\n";
}
The output of this script is:
AB
ABC
AB
I would have expected it to be:
AB
ABC
ABC
...as we pass the hash by reference.
What concept am I missing here about passing hashes to functions?
That's because when you do my %myHash = %$hashRef;, you're taking a copy of the dereferenced $hashref and putting it into %myHash which is the same thing as my %myHash = %hash;, so you're not working on the referenced hash at all.
To work on the hash specified by the reference, try this...
sub modifyHash {
my $hashRef = $_[0];
$hashRef->{"C"} = "3";
print (keys %$hashRef);
print "\n";
}
As pointed out by ThisSuitIsBlackNot in the comments below, #_[0] is better written as $_[0]. You should always be using use strict; and use warnings;, as this would have been caught. Because you're sending in a reference, you could also have used my $hashRef = shift;.
The problem is with the assignment:
my %myHash = %$hashRef;
This is akin to saying:
$x = 5;
$y = $x;
You're not setting $y to reference the same spot in memory, you're just giving the value of $x to $y. In your example, you're creating a new hash (%myHash) and giving it the value of the hash stored at $hashRef. Any future changes are to the new hash, not the original.
If you want to manipulate the original, you should do something like:
${$hashRef}{"C"} = "3";
or
$hashRef->{"D"} = 4;
There might be a more elegant way of doing it, but as far as I know you want to work with the hash reference.

How to print the array of arrays in perl - reference variable

#! /usr/bin/perl
use strict;
use warnings;
use File::stat;
my $file_name = 0;
my $info = 0;
my $ret_mode = 0;
my $size;
my $last_mod;
my #array_file;
my $index = 0;
my #array_mode;
my #array_size;
my #array_last_mod;
foreach(#ARGV){
$file_name = $_;
$info = stat($file_name);
$size = $info->size;
$last_mod = scalar(localtime($info->mtime));
$ret_mode = $info->mode;
$ret_mode = $ret_mode & 0777;
$array_file[$index] = ($file_name);
$array_mode[$index] = ($ret_mode);
$array_size[$index] = ($size);
$array_last_mod[$index] = ($last_mod);
$ret_mode = 0;
$index++;
}
my #array_arrays = (#array_file, #array_mode, #array_size, #array_last_mod);
my $array_ref = \#array_arrays;
my $i = 0;
for(#$array_ref){
print "#$array_ref[$i]\n";
$i++;
}
I have created an array of arrays and I want to print the filename,mmode,size and last access time from the array of arrays created. Its not printing any values with,
for(#$array_ref){
print "#$array_ref[$i]\n";
$i++;
}
my #array_arrays = (#array_file, #array_mode, #array_size, #array_last_mod);
This statement does not create an array of arrays. Instead, It flattens the various arrays into one big flat list and then assigns that to #array_arrays. You want to assign array references. Obtain those with the reference operator \:
my #array_arrays = (\#array_file, \#array_mode, \#array_size, \#array_last_mod);
or with the shortcut
my #array_arrays = \(#array_file, #array_mode, #array_size, #array_last_mod);
Even then, your last foreach-loop is wrong. You probably meant
for my $i (0 .. $#{ $array_arrays[0] }) {
for my $aref (#array_arrays) {
print $aref->[$i], "\n";
}
}
or something similar.
Your code style could be improved.
Please don't declare all your variables at the top. Declare them in the tightest scope possible. Try to declare them at the point of initialization, e.g.
for my $file_name (#ARGV) {
my $info = stat($file_name);
my size = $info->size;
...
}
Don't prefix your array names with array_. The # sigil and/or subscripting with the [...] operator makes it clear that these are arrays.
$ret_mode & 0777 – The result should be $ret_mode itself: 0777 is 0b111111111. I.e. this removes all but the last 9 bits – you wouldn't care if there were more to the left.
$last_mod = scalar(localtime($info->mtime)); – due to the scalar assignment, localtime is already executed in scalar context. No need to make this explicit.
my $index = 0; ... for (...) { $array[$index] = ...; $index++ }. Please not. Just use push: for (...) { push #array, ... }. Don't maintain indices yourself unless you have to.
$ret_mode = 0; Why? you assign a new value during the next iteration anyway. Note that you should declare this variables inside the loop (see my point about tight scopes), which would create a new variable in each iteration, making this even more useless.
my $array_ref = \#array_arrays; .. #$array_ref[$i]. Isn't this a bit backwards? $array_arrays[$i] would work just as well. Note that in your defeferencing, # is probably the wrong sigil. You meant $$array_ref[$i].
Let's try a little something different.
First off, there's a nice syntax using the -> for referenced arrays and hashes. Here I am going to make a array of people. I'll make a hash of %person that contains all that person's information:
my %person;
my $person{NAME} = "Bob";
my $person{JOB} = "Programmer";
my $person{PHONE} = "555-1234";
Now, I'll put it into an array:
my #array
my $array[0] = \%person;
I could reference the person in the array this way:
print ${$array[0]}{NAME} . "\n"; #Prints Bob
print ${$array[0]}{JOB} . "\n"; #Prints Porgrammer
But, Perl gives me a nice clean way to do this:
print $array[0]->{NAME} . "\n"; #Prints Bob
print $array[0]->{JOB} . "\n"; #Prints Progammer
In fact, I could skip the hash all together. Here I am adding Jill to my array:
$array[1]->{NAME} = "Jill";
$array[1]->{JOB} = "DBA";
$array[1]->{PHONE} = "555-5555";
You can see this is a much simpler way to use references. It's easier to see what is going on and takes fewer lines of code.
You can refer to an array of an array like this:
$myarray[1]->[3] = 42;
Or have a hash which stores an array. In this day and age, who has only a single phone number?:
$person[1]->{PHONE}->[0] = "555-4567";
$person[1]->{PHONE}->[1] = "555-4444";
Or, to make it even more complex we could have a hash of a hash of an array:
$person[1]->{PHONE}->{CELL}->[0] = "555-1111";
$person[1]->{PHONE}->{CELL}->[1] = "555-2222";
$person[1]->{PHONE}->{HOME}->[0] = "555-3333";
$person[1]->{PHONE}->{JOB}->[0] = "555-4444";
$person[1]->{PHONE}->{JOB}->[1] = "555-5555";
Using this syntax will really help clean up a lot of your code. You won't have to store the information into individual structures that are then only used to make references. Instead, you can simple setup your structure the way you want without intermediary steps.
Now to your problem: You're trying to store a bunch of information about files into a series of arrays. What you're hoping is that $array_mode[1] goes with $array_file[1] and you have to keep all of these arrays in sync. This is a pain and it is complex.
The entire purpose of using references is to eliminate this need for multiple variables. If you're going to use references, why not simply store your entire file structure into a single array.
What you really, really want is an array of hash references. And, that hash reference will be keyed based upon your file attributes. Here is your code restructured into using an array of hash references. I didn't even bother to check the rest of it. For example, I'm not sure how your localtime thing will work:
use strict;
use warnings;
use feature qw(say);
use File::stat;
my #files;
for my $file ( #ARGV ) {
my $info = stat( $file );
my $file = {}; #This will be a reference to a hash
$file->{NAME} = $file;
$file->{SIZE} = $info->size;
$file->{RET_MODE} = $info->mode & 0777;
$file->{LAST_MOD} = localtime $info->mtime; #Does this work?
push #files, $file #Pushes the hash reference onto the array
}
That's way shorter and cleaner. Plus, you know that $files[0]->{NAME} goes with $files[1]->{SIZE}, and if you remove $files[0] from your array, or transfer it to another variable, all of the attributes of that file go together.
Here's how you'd print it out:
for my $file ( #files ) {
say "File Name: " . $file->{NAME};
say "File Size: " . $file->{SIZE};
say "Last Modified: " . $file->{LAST_MOD};
say "File Mode: " . $file->{RET_MODE};
}
Simple and easy to do.
However, I would argue what you really want is a hash of hashes. Let your file name be the key to your main hash, and let {SIZE}, {LAST_MOD}, and {RET_MODE} be the keys to your sub hash:
my %files = {}; #This is a hash of hashes
for my $file_name ( #ARGV ) {
my $info = stat( $file );
$files{$file_name}->{SIZE} = $info->size;
$files{$file_name}->{RET_MODE} = $info->mode & 0777;
$files{$file_name}->{LAST_MOD} = localtime $info->mtime; #Does this work?
}
Now if someone asks, "When was foo.txt last modified?", you can say:
say "File 'foo.txt' was last modified on " . $file{foo.txt}->{LAST_MOD};
And to print out your entire structure:
for my $file_name ( sort keys %files ) {
say "File: $file_name";
for my attribute ( sort keys %{ $file_name } ) {
say " $attribute: " . $files{$file_name}->{$attribute};
}
}
Next step is to learn about Object Oriented Perl! Object Oriented Perl uses these types of references, but will greatly simplify the handling of these references, so you make fewer programming mistakes.

How do I return an array and a hashref?

I want to make a subroutine that adds elements (keys with values) to a previously-defined hash. This subroutine is called in a loop, so the hash grows. I don’t want the returning hash to overwrite existing elements.
At the end, I would like to output the whole accumulated hash.
Right now it doesn’t print anything. The final hash looks empty, but that shouldn’t be.
I’ve tried it with hash references, but it does not really work. In a short form, my code looks as follows:
sub main{
my %hash;
%hash=("hello"=>1); # entry for testing
my $counter=0;
while($counter>5){
my(#var, $hash)=analyse($one, $two, \%hash);
print ref($hash);
# try to dereference the returning hash reference,
# but the error msg says: its not an reference ...
# in my file this is line 82
%hash=%{$hash};
$counter++;
}
# here trying to print the final hash
print "hash:", map { "$_ => $hash{$_}\n" } keys %hash;
}
sub analyse{
my $one=shift;
my $two=shift;
my %hash=%{shift #_};
my #array; # gets filled some where here and will be returned later
# adding elements to %hash here as in
$hash{"j"} = 2; #used for testing if it works
# test here whether the key already exists or
# otherwise add it to the hash
return (#array, \%hash);
}
but this doesn’t work at all: the subroutine analyse receives the hash, but its returned hash reference is empty or I don’t know. At the end nothing got printed.
First it said, it's not a reference, now it says:
Can't use an undefined value as a HASH reference
at C:/Users/workspace/Perl_projekt/Extractor.pm line 82.
Where is my mistake?
I am thankful for any advice.
Arrays get flattened in perl, so your hashref gets slurped into #var.
Try something like this:
my ($array_ref, $hash_ref) = analyze(...)
sub analyze {
...
return (\#array, \#hash);
}
If you pass the hash by reference (as you're doing), you needn't return it as a subroutine return value. Your manipulation of the hash in the subroutine will stick.
my %h = ( test0 => 0 );
foreach my $i ( 1..5 ) {
do_something($i, \%h);
}
print "$k = $v\n" while ( my ($k,$v) = each %h );
sub do_something {
my $num = shift;
my $hash = shift;
$hash->{"test${num}"} = $num; # note the use of the -> deference operator
}
Your use of the #array inside the subroutine will need a separate question :-)

Can a Hash have duplicate keys or values

Can a Hash have duplicate keys or values?
it can have duplicate values but not keys.
For both hashes and arrays, only one scalar can be stored at a given key. ("Keys are unique.") If they weren't, you couldn't do
$h{a} = 1;
$h{a} = 2;
$val = $h{a}; # 2
$a[4] = 1;
$a[4] = 2;
$val = $a[4]; # 2
If you wanted to associate multiple values with a key, you could place a reference to an array (or hash) at that key, and add the value to that array (or hash).
for my $n (4,5,6,10) {
if ($n % 2) {
push #{ $nums{odd} }, $n;
} else {
push #{ $nums{even} }, $n;
}
}
say join ', ', #{ $nums{even} };
See perllol for more on this.
As for values, multiple elements can have the same value in both hashes and arrays.
$counts{a} = 3;
$counts{b} = 3;
$counts[5] = 3;
$counts[6] = 3;
Assuming talking about a "%hash"
Then:
Duplicate keys not allowed.
Duplicate values allowed.
This is easy to reason about because it is a mapping of a particular Key to a particular Value where the Value plays no part in the look-up and is thus independent upon other Values.
Please try and run this code, it executes without errors.
I hope this is what you were asking!
#!/usr/bin/perl
use strict;
use warnings;
my %hash = ('a' => 1, 'a' => 2, 'b' => 4 );
print values %hash, "\n\n";
print keys %hash, "\n\n";
You can try to use Hash::MultiKey module from CPAN.
(I used Data::Dumper to show how hash is exactly looks - it is not necessary here)
use Data::Dumper;
use Hash::MultiKey;
tie my %multi_hash, 'Hash::MultiKey';
$multi_hash{['foo', 'foo', 'baz']} = "some_data";
for (keys %multi_hash) {
print #$_,"\n";
};
print Dumper\%multi_hash;
And the output shoud be () :
foofoobaz
$VAR1 = {
'ARRAY(0x98b6978)' => 'some_data'
};
So technically speaking Hash::MultiKey let you create reference as a hash key.
Yes a hash can have duplicate keys as I demonstrate below...
Key example: BirthDate|LastNameFirst4Chars|FirstNameInitial|IncNbr
"1959-12-19|Will|K|1" ... "1959-12-19|Will|K|74".
Note: This might be a useful Key for record look ups if someone did not remember their Social Security Nbr
#-- CODE SNIPPET:
#Offsets=(); #-- we will build an array of Flat File record "byte offsets" to random access
#-- for all records matching this ALT KEY with DUPS
for ($i=1; $i<=99; $i++) {
$KEY=$BirthDate . "|" . $LastNameFirst4Chars . "|" . $FirstNameInitial . "|" . $i;
if (exists $Hash{$KEY}) {
push #Offsets, $Hash{$KEY}; #-- add another hash VALUE to the end of the array
}
}

How to access data stored in Hash

I have this code:
$coder = JSON::XS->new->utf8->pretty->allow_nonref;
%perl = $coder->decode ($json);
When I write print %perl variable it says HASH(0x9e04db0). How can I access data in this HASH?
As the decode method actually returns a reference to hash, the proper way to assign would be:
%perl = %{ $coder->decode ($json) };
That said, to get the data from the hash, you can use the each builtin or loop over its keys and retrieve the values by subscripting.
while (my ($key, $value) = each %perl) {
print "$key = $value\n";
}
for my $key (keys %perl) {
print "$key = $perl{$key}\n";
}
JSON::XS->decode returns a reference to an array or a hash. To do what you are trying to do you would have to do this:
$coder = JSON::XS->new->utf8->pretty->allow_nonref;
$perl = $coder->decode ($json);
print %{$perl};
In other words, you'll have to dereference the hash when using it.
The return value of decode isn't a hash and you shouldn't be assigning it to a %hash -- when you do, you destroy its value. It's a hash reference and should be assigned to a scalar. Read perlreftut.
You need to specify the particular key of hash, Then only you will be able to access the data from the hash .
For example if %perl hash has key called 'file' ;
You suppose to access like below
print $perl{'file'} ; # it would print the file key value of the %perl hash
Lots of ways, you can use a foreach loop
foreach my $key (%perl)
{
print "$key is $perl{$key}\n";
}
or a while loop
while (my ($key, $value) = each %perl)
{
print "$key is $perl{$key}\n";
}