Updated: Initialise and Clear multiple hash in one line - perl

How can I Initialise and clear multiple hash in one line.
Ex:
my %hash1 = ();
my %hash2 = ();
my %hash3 = ();
my %hash4 = ();
to
my ( %hash1, %hash2, %hash3, %hash4 ) = ?

It appears (from your comments) that you really want to empty hashes that already have stuff in them. You can do it like this:
(%hash1,%hash2,%hash3) = ();
Complete example:
use strict;
use warnings;
my %hash1 = ('foo' => 1);
my %hash2 = ('bar' => 1);
my %hash3 = ('baz' => 1);
(%hash1,%hash2,%hash3) = ();
print (%hash1,%hash2,%hash3);
A variable declaration always gives you an empty variable, so there is no need to set it to empty. This is true even in a loop:
for (0..100)
{
my $x;
$x++;
print $x;
}
This will print 1 over and over; even though you might expect $x to retain its value, it does not.
Explanation: Perl allows list assignment like ($foo,$bar) = (1,2). If the list on the right is shorter, any remaining elements get assigned undef. Thus assigning the empty list to a list of variables makes them all undefined.
Another useful way to set a bunch of things is the x operator:
my ($x,$y,$z) = (100)x3;
This sets all three variables to 100. It doesn't work so well for hashes, though, because each one needs a list assigned to it.

It's as simple as doing
my ( %hash1, %hash2, %hash3, %hash4 );
and they will not contain any keys or values at that point.
The same technique applies to scalars and arrays.
To undef multiple hashes, you could do
undef %$_ for ( \%hash1, \%hash2 );

You can initialize it as:
my %hash1 = %hash2 = %hash3 = %hash4 = ();

You do not need to assign anything to a new variable in order to assure it is empty. All variables are empty, if nothing has been assigned to them.
my %hash; # hash contains nothing
%hash = () # hash still contains nothing
The only time it would be useful to assign the empty list to a hash is if you want to remove previously assigned values. And even then, that would only be a useful thing to do if it could not already be solved by applying the correct scope restriction to the hash.
my (%hash1, %hash2);
while (something) {
# some code
(%hash1,%hash2) = (); # delete old values
}
Emptying the hashes. Better written as:
while (something) {
my (%hash1, %hash2); # all values are now local to the while loop
# some code
}

Related

How to reuse a variable, which is a reference to a hash?

I'm trying to add a few hashes to the array, reusing the same variable (in my real program, I'm doing this in a loop, that's why reusing the var). This is the code:
my #items;
my %x;
$x{'aa'} = 'bb';
push(#items, \%x);
%x = (); # I think the error is here, I'm not resetting the reference :(
$x{'cc'} = 'dd';
push(#items, \%x);
use Data::Dumper;
print Dumper \#items;
However, what I see is not what I expect:
$VAR1 = [
{
'cc' => 'dd'
},
$VAR1->[0]
];
What do I misunderstand?
ps. This is how it looks in the loop:
my #items;
my %x;
foreach ... {
%x = ();
$x{'aa'} = some_random_number();
push(#items, \%x);
}
Reusing a hash reference that way is not possible, it will always point to the same hash. And if you reset the same hash over and over and add new values to it, of course the beginning of the hash will be deleted.
You are correct that %x = () is the problem, because that is where you delete the content in %x (the 'aa' key).
What you want is to create a new hash reference for each value you want to store.
use strict;
use warnings;
my #items = qw( .... );
foreach ... {
my %x; # use a lexically scoped variable, which will be new each iteration
$x{'aa'} = some_random_number();
push(#items, \%x);
}
Or even:
my %x = (aa => some_random());
Or better yet, use a hash reference right away
my $x = { aa => some_random() };
push #items, $x;
Or again, a bit quicker:
push #items, { aa => some_random() };
{ ... } creates an anonymous hash ref. Reusing variables is not a good idea, hardly ever. Unless you are using so many variables that you are afraid of memory issues. Use the lexical scope and anonymous references to your advantage to encapsulate your code and avoid confusion.
But since you are using different keys, and pushing them onto an array, I feel like you are confused about Perl data structures. You could just use the same hash, if your keys are different:
my %x;
foreach ... {
$x{$key} = some_random_number();
}
You probably want a variable to keep track of the key name there, not a constant.
In the comments you describe a practice of adding values to the hash, and then adding it to the array to start over. This is exactly why you should use a lexically scoped hash. For example:
....
for my $table (#tables) {
my %hash; # <--- new variable for each $table
for my $stuff (#stuff) {
$hash{$stuff} = something();
}
....
push #array, \%hash;
}
If you put an enclosure around the hash, it will be reset automatically, and then use the "same" name space the following iterations, except it will point to a different memory location (a new data reference).
Or if, as you say, you cannot use a loop outside, you can just put a block around the variable.
{ # start of a block
my %hash;
for my $stuff (#stuff) {
$hash{$stuff} = something();
}
....
push #array, \%hash;
} # end of a block
my %hash; # this "%hash" is a different variable from the previous

How to define or set multiple hash values to zero for each undefined hash in perl

In the code below there are multiple occupations whose value is zero but are not defined in the hash. Example: Plumber is not defined in %data but I need to set $data{'Plumber'} to zero. There may be more than one occupation with no values. I did this with with an if-else, but is there a better way?
#!/usr/bin/perl
use Data::Dumper;
%data = ('Teacher' => 45, 'Doctor' => 40,'Driver' => '10');
if (exists($data{'Plumber'})) {
print "Number of doctor is $data{'Plumber'}\n";
}
else {
$data{'Plumber'} = 0;
print "Number of doctor is $data{'Plumber'}\n";
}
print Dumper \%data;
Is there any way to set all the undefined values of %data to zero?
The condition is - first check what values in the hash are undefined and set zero to those respective keys not all.
Please see my actual piece of code below: [question edited on 30th March'17]
my %map = (
blk => 'Block',
hrd => 'Hard',
sft => 'Soft',
success => 'Success',
unk => 'Unknown',
);
my %report;
for ( `$stats` ) {
my ($val, $type) = split;
$report{$map{$type}} = $val;
}
\%report;
$_ //= 0 for values %report;
print Dumper \%report;
I am not able to set 0 for all unassigned value within hash %report,
per below comment, I used $_ //= 0 for values %report; to achieve this but here I got confused when I am mapping two hashes as I am a very beginner in Perl
FYI.. $stats is a query which I am using to fetch some values from the logs and below the sample output.
10 blk
20 sft
50 success
any idea how to do with this would be highly appreciated
You can just use a default:
print "Plumber number: ", ($data{'Plumber'} || 0), "\n";
This will use 0 instead of any of Perl's non-true values, e.g., "", 0, undef. If you have such values and do not want to use 0 instead, use the // operator instead of '||.
You're probably used to something like Ruby's Hash.new(0). Perl has several libraries to implement this, Hash::DefaultValue is from a good author. Trouble is they rely on tying, Perl's odd way of letting an object act like a data type, which is slow and has caveats.
Instead, there's several techniques. Simplest is to apply the default yourself, as in #Robert's answer, but use the defined-or // operator instead of the logical-or || operator.
my $value = $data{'Plumber'} // 0;
The difference is subtle, but it protects you from a false value like "" from being turned into a 0. Instead, the 0 default will only be used if $data{'Plumber'} contains the undefined value or does not exist.
Second, if the set of keys is fixed, you can explicitly set all the values to 0. Again, using the defined-or //= version of ||=.
for my $key (#keys) {
$data{$key} //= 0;
}
There's a few options for encapsulating this default. Simplest is to wrap up your hash in a little class to do this for you.
package MyStuff;
sub new {
my $class = shift;
my $self = shift;
return bless $self, $class;
}
sub get {
my $self = shift;
my $key = shift;
return $self->{$key} // 0;
}
Then...
my $stuff = MyStuff->new(\%data);
my $val = $stuff->get("Plumber");
There will be the extra overhead of the method call, and users have to adapt to the object interface, but it's faster than tying.
Your use case allows to just preset all values of the hash with 0. Add this after my %report;:
#report{values %map} = (0) x keys %map;
Why this works:
#report{...} sets multiple values of your hash. The values of %map are used as keys for %report. (0) x ... produces a list of zeros. Since the x operator expects a scalar on its right side, keys %map is executed in scalar context and produces the number 5. Therefore %report is filled with its default values and later only the values present in your $stats are overwritten.

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 :-)

How to parse through many hashes using foreach?

foreach my %hash (%myhash1,%myhash2,%myhash3)
{
while (($keys,$$value) = each %hash)
{
#use key and value...
}
}
Why doesn't this work :
it says synta error on foreach line.
Pls tell me why is it wrong.
This is wrong because you seem to think that this allows you to access each hash as a separate construct, whereas what you are in fact doing is, besides a syntax error, accessing the hashes as a mixed-together new list. For example:
my %hash1 = qw(foo 1 bar 1);
my %hash2 = qw(abc 1 def 1);
for (%hash1, %hash2) # this list is now qw(foo 1 bar 1 abc 1 def 1)
When you place a hash (or array) in a list context statement, they are expanded into their elements, and their integrity is not preserved. Some built-in functions do allow this behaviour, but normal Perl code does not.
You also cannot assign a hash as the for iterator variable, that can only ever be a scalar value. What you can do is this:
for my $hash (\%myhash1, \%myhash2, \%myhash3) {
while (my ($key, $value) = each %$hash) {
...
Which is to say, you create a list of hash references and iterate over them. Note that you cannot tell the difference between the hashes with this approach.
Note also that I use my $hash because this variable must be a scalar.
The syntax should be like:
my $hash1 = {'a'=>1};
my $hash2 = {'b'=>1};
my #arr2 = ($hash1, $hash2);
foreach $hash (#arr2)
{
while(($key, $value) = each %$hash)
{
print $key, $value;
}
}
you need to reference and then dereference the hash.

Put an array into a hash of hashes, transforming it into a Hash itself

my %hash = #array;
transforms an array into a hash, but how can do the same with $hash{something}?
$hash{something} = { #array };
The {} around it creates a hashref.
When you write:
my %hash = #array;
What Perl sees is:
my %hash = ($array[0], $array[1], ... $array[$#array]);
So the #array is expanded into a list, and that list is assigned to the plural %hash. The list must have an even number of elements, or you will get a warning (assuming you are using use warnings; in your script, which you always should. use strict; as well).
Broken down even more, it is:
my %hash;
$hash{$array[0]} = $array[1];
$hash{$array[2]} = $array[3];
...
$hash{$array[$#array - 1]} = $array[$#array];
So with the translation from #array to %hash explained, to insert this hash into a hash of hashes at a particular key:
$HoH{key} = \%hash;
Where the \ character takes a reference to %hash. If %hash is not needed elsewhere you could constrain it with a block:
{
my %hash = #array;
$HoH{key} = \%hash;
}
Using a do {...} makes it a little shorter:
$HoH{key} = do {my %hash = #array; \%hash};
But not much shorter. If the above seems tedious to you, it should. In Perl, the above construct can be reduced to:
$HoH{key} = { #array };
Where the {...} denotes the anonymous hash reference constructor, equivalent to
do {my %hash = (...); \%hash}
In addition to {...}, Perl provides the [...] construct to create anonymous array references. The ... of both constructs sees list context.