Using an element in a Hash of Arrays to access the key - arrays

I have a hash of arrays like this:
my #array1 = ("one", "two", "three", "four", "five");
my #array2 = ("banana", "pear", "apple");
my %hash = (
numbers => \#array1,
fruit => \#array2
);
I would like to use an element of the array to access the key. So for example, if I have "banana", I would like to print "fruit".
However when I do print $hash{banana} I get "use of unitialized value in print". How do I properly access this?

As already mentioned by Borodin in the comments, there is no direct way to accomplish this. But you could do it in the following way:
sub getKeyByValue {
my ($hashref, $val) = #_; # get sub arguments
for (keys %$hashref) {
# find key by value and give back the key
return $_ if grep $_ eq $val, #{$hashref->{$_}};
}
return undef; # value not found
}
my $key = getKeyByValue(\%hash, 'banana');
print $key;
Output: fruit
Just give the hash reference and your desired value to the subroutine getKeyByValue() and it will return the corresponding key. If the value can't be found, the subroutine will return the undefined value undef. If your data structure is really big, this trivial search is obviously not the most efficient solution.
Note: If the value banana is stored several times (under more than one key), then this subroutine will of course only return the first random match (key). You have to modify the subroutine if you are interested in all the keys under which banana is possibly stored.
There are many ways to do this, like most of the time in Perl. For example you could reverse the hash and create a new one (see example in perlfaq4).

You could create two distinct hashes:
my %hash1 = map { $_ => "numbers" } #array1;
my %hash2 = map { $_ => "fruit" } #array2;
and concatenate them:
my %hash = (%hash1, %hash2);

Related

How do I copy list elements to hash keys in perl?

I have found a couple of ways to copy the elements of a list to the keys of a hash, but could somebody please explain how this works?
#!/usr/bin/perl
use v5.34.0;
my #arry = qw( ray bill lois shirly missy hank );
my %hash;
$hash{$_}++ for #arry; # What is happening here?
foreach (keys %hash) {
say "$_ => " . $hash{$_};
}
The output is what I expected. I don't know how the assignment is being made.
hank => 1
shirly => 1
missy => 1
bill => 1
lois => 1
ray => 1
$hash{$_}++ for #array;
Can also be written
for (#array) {
$hash{$_}++;
}
Or more explicitly
for my $key (#array) {
$hash{$key}++;
}
$_ is "the default input and pattern-searching space"-variable. Often in Perl functions, you can leave out naming an explicit variable to use, and it will default to using $_. for is an example of that. You can also write an explicit variable name, that might feel more informative for your code:
for my $word (#words)
Or idiomatically:
for my $key (keys %hash) # using $key variable name for hash keys
You should also be aware that for and foreach are exactly identical in Perl. They are aliases for the same function. Hence, I always use for because it is shorter.
The second part of the code is the assignment, using the auto-increment operator ++
It is appended to a variable on the LHS and increments its value by 1. E.g.
$_++ means $_ = $_ + 1
$hash{$_}++ means $hash{$_} = $hash{$_} + 1
...etc
It also has a certain Perl magic included, which you can read more about in the documentation. In this case, it means that it can increment even undefined variables without issuing a warning about it. This is ideal when it comes to initializing hash keys, which do not exist beforehand.
Your code will initialize a hash key for each word in your #arry list, and also count the occurrences of each word. Which happens to be 1 in this case. This is relevant to point out, because since hash keys are unique, your array list may be bigger than the list of keys in the hash, since some keys would overwrite each other.
my #words = qw(foo bar bar baaz);
my %hash1;
for my $key (#words) {
$hash{$key} = 1; # initialize each word
}
# %hash1 = ( foo => 1, bar => 1, baaz => 1 );
# note -^^
my %hash2; # new hash
for my $key (#words) {
$hash{$key}++; # use auto-increment: words are counted
}
# %hash2 = ( foo => 1, bar => 2, baaz => 1);
# note -^^
Here is another one
my %hash = map { $_ => 1 } #ary;
Explanation: map takes an element of the input array at a time and for each prepapres a list, here of two -- the element itself ($_, also quoted because of =>) and a 1. Such a list of pairs then populates a hash, as a list of an even length can be assigned to a hash, whereby each two successive elements form a key-value pair.
Note: This does not account for possibly multiple occurences of same elements in the array but only builds an existance-check structure (whether an element is in the array or not).
$hash{$_}++ for #arry; # What is happening here?
It is iterating over the array, and for each element, it's assigning it as a key to the hash, and incrementing the value of that key by one. You could also write it like this:
my %hash;
my #array = (1, 2, 2, 3);
for my $element (#array) {
$hash{$element}++;
}
The result would be:
$VAR1 = {
'2' => 2,
'1' => 1,
'3' => 1
};
$hash{$_}++ for #arry; # What is happening here?
Read perlsyn, specifically simple statements and statement modifiers:
Simple Statements
The only kind of simple statement is an expression evaluated for its side-effects. Every simple statement must be terminated with a semicolon, unless it is the final statement in a block, in which case the semicolon is optional. But put the semicolon in anyway if the block takes up more than one line, because you may eventually add another line. Note that there are operators like eval {}, sub {}, and do {} that look like compound statements, but aren't--they're just TERMs in an expression--and thus need an explicit termination when used as the last item in a statement.
Statement Modifiers
Any simple statement may optionally be followed by a SINGLE modifier, just before the terminating semicolon (or block ending). The possible modifiers are:
if EXPR
unless EXPR
while EXPR
until EXPR
for LIST
foreach LIST
when EXPR
[...]
The for(each) modifier is an iterator: it executes the statement once for each item in the LIST (with $_ aliased to each item in turn). There is no syntax to specify a C-style for loop or a lexically scoped iteration variable in this form.
print "Hello $_!\n" for qw(world Dolly nurse);

How to check whether key exist in hasharray and have specific value - Perl

I have hash array:
my %hash = (
'DISK' => 1,
'MEMORY' => 1,
'CPU' => 0,
'SSL' => 1,
'PROCESS' => 1,
);
Function:
sub fun
{
my $self = shift;
my #ret = ();
my $index = 0;
my ($alarm, $hash) = #_;
my $key = "CPU";
print "Key: $_ and Value: $hash->{$_}\n" foreach (keys %{$hash});
foreach my $name (keys %{$self->{ServerParameters}})
{
my $par = uc $name;
$par =~ s/\d$//g;
if (!defined ${${$self->{ServerParameters}}{$name}}{$alarm})
{
next;
}
if (${${$self->{ServerParameters}}{$name}}{$alarm} eq "1")
{
if (exists $hash{$par}){
if ($hash{$par} == 0){
print "VALUE 0\n";
}
}
my $param = ${$self->{ServerParameters}}{$name}->getValues();
$ret[$index++] = "Alarm parameter: $par : $param";
}
}
return #ret;
}
I'm passing hash array to above function:
fun("Red", \%hash);
At first I'm trying to check whether specific key exist and then if value of it is 0. When trying to run above code I receiving:
Global symbol "%hash" requires explicit package name
How can I do similiar check using e.g grep ? Thank for any asistance.
When you do this:
fun("Red", \%hash);
You're sending in a hash reference, not a hash. There's two ways to fix this.
One, the easiest, send in the hash. I would recommend using the second approach below, unless you've got a lot of code/many subroutines to update.
fun("Red", %hash);
...then change:
my ($alarm, $hash) = #_;
to:
my ($alarm, %hash) = #_;
That'll alleviate any other code changes.
Second, instead of changing the call and the assignment of the parameter(s) per the example above, you will have to change all uses to the hash within your code to a hash reference. For example:
if (exists $hash->{$par}){
if ($hash->{$par} == 0){
print "VALUE 0\n";
}
}
There's no real right or wrong way, it really depends what you're doing with the code inside of the subroutine.
Sending in a hash makes a copy (ie. you now have two instances of the hash floating around, which may be problematic depending on the size of the hash. In this case, it's negligible to say the least). Changing the data in the hash within the sub does not change the outer-scoped hash.
Sending in a reference does not make a copy which reduces memory footprint, but any changes to the hash reference within the sub will change the original hash outside of the sub.

Index of element of one array in another array avoiding list::utils

Aim: To match two array and find the index position of matched element.
Tricky part: As per the example given below, the elements of each array are not exactly similar to each other but the comprise of some part and I want to match that.
Sample of how the arrays are:
array1=('adam west', 'daric dalon','tom helic','todd nick','riley remer');
array2=('adam west 12', 'daric dalon mr.','tom helic (fads)','todd nick (456)','riley remer','john steve','dim madz 12');
I have tried using List::MoreUtils qw(first_index); method but this method isn't giving what I want.
The following I have tried:
Try 1
for my $name (#Names) {
$count;
for $matchList (#org_name) {
if ( index( $matchList, $name ) != -1 ) {
push (#matched,$matchList);
$count++;
}#print $org_name[$count];
}
} print "Those which are matched #matched\n";
Try 2
The next method I have copied from online but not very much useful.
use List::MoreUtils qw(first_index);
#indexes;
foreach my $place (#allNames) {
push #indexes, first_index { $_ eq $place } #org_name;
}
use Data::Dumper qw(Dumper);
print Dumper \#indexes;
I know this is going to be a very easy thing which I am missing out. But please let me know what I can do.
I think you need something like this:
use Data::Dumper
my #indexes = ();
#create hash of "first_index" elemets in #org_name
my %tmp = map { $org_name[$_] => $_ } reverse 0..$#org_name;
for my $t (#allNames) {
if(exists($tmp{$t})) {
push #indexes, $tmp{$t};
}
}
print Dumper #indexes;
if ( index( $matchList, $name ) != -1 ) {
You haven't explained what you mean by "match two arrays". I think you might be looking for elements that are in both arrays. But the line above isn't testing for equality of two strings, it is testing if $name appears as a substring of $matchList. Is that what you want?
If you want to test for equality, you should use this instead:
if ($matchList eq $name) {
But the substring check should still work if your strings are equal. If they aren't matching, then it sounds like your arrays don't contain what you think they contain. Perhaps one array contains elements that still have newlines attached - or something like that.
If you're actually trying to find the elements that appear in both arrays, then (as it often the case) the Perl FAQ will be useful. You're looking for the intersection of two sets and perlfaq4 contains this:
How do I compute the difference of two arrays? How do I compute the
intersection of two arrays?
Use a hash. Here's code to do both and
more. It assumes that each element is unique in a given array:
my (#union, #intersection, #difference);
my %count = ();
foreach my $element (#array1, #array2) { $count{$element}++ }
foreach my $element (keys %count) {
push #union, $element;
push #{ $count{$element} > 1 ? \#intersection : \#difference }, $element;
}
Note that this is the symmetric difference, that is, all elements in either A or in B but not in both. Think of it as an xor operation.
Update: Having seen what you want, most of what I said above is completely off-topic. Please try to be clearer when asking questions.
What you actually want is something like this:
#!/usr/bin/perl
use strict;
use warnings;
use feature 'say';
my #arr1 = ('adam west', 'daric dalon', 'tom helic','todd nick', 'riley remer');
my #arr2 = ('adam west 12', 'daric dalon mr.','tom helic (fads)',
'todd nick (456)', 'riley remer', 'john steve', 'dim madz 12');
# Match elements from array1 ...
for my $e1 (#arr1) {
# ... against indexes in array2
for my $i2 (0 .. $#arr2) {
if ($arr2[$i2] =~ /^\Q$e1/) {
say "$e1 -> $i2";
}
}
}
Or you can use List::MoreUtils::firstidx like this:
#!/usr/bin/perl
use strict;
use warnings;
use List::MoreUtils 'firstidx';
use feature 'say';
my #arr1 = ('adam west', 'daric dalon', 'tom helic','todd nick', 'riley remer');
my #arr2 = ('adam west 12', 'daric dalon mr.','tom helic (fads)',
'todd nick (456)', 'riley remer', 'john steve', 'dim madz 12');
# Match elements from array1 ...
for my $e1 (#arr1) {
# ... against indexes in array2
if ((my $i2 = firstidx { /^\Q$e1/ } #arr2) == -1) {
say "$e1 not found in \#arr2";
} else {
say "$e1 -> $i2";
}
}

Perl: Load file into hash

I'm struggling to understand logic behind hashes in Perl. Task is to load file in to hash and assign values to keys which are created using this file.
File contains alphabet with each letter on its own line:
a
b
c
d
e
and etc,.
When using array instead of hash, logic is simple: load file into array and then print each element with corresponding number using some counter ($counter++).
But now my question is, how can I read file into my hash, assign automatically generated values and sort it in that way where output is printed like this:
a:1
b:2
c:3
I've tried to first create array and then link it to hash using
%hash = #array
but it makes my hash non-sortable.
There are a number of ways to approach this. The most direct would be to load the data into the hash as you read through the file.
my %hash;
while(<>)
{
chomp;
$hash{$_} = $.; #Use the line number as your autogenerated counter.
}
You can also perform simliar logic if you already have a populated array.
for (0..$#array)
{
$hash{$array[$_]} = $_;
}
Although, if you are in that situation, map is the perlier way of doing things.
%hash = map { $array[$_] => $_ } #array;
Think of a hash as a set of pairs (key, value), where the keys must be unique. You want to read the file one line at a time, and add a pair to the hash:
$record = <$file_handle>;
$hash{$record} = $counter++;
Of course, you could read the entire file into an array at once and then assign to your hash. But the solution is not:
#records = <$file_handle>;
%hash = #records;
... as you found out. If you think in terms of (key, value) pairs, you will see that the above is equivalent to:
$hash{a} = 'b';
$hash{c} = 'd';
$hash{e} = 'f';
...
and so on. You still are going to need a loop, either an explicit one like this:
foreach my $rec (#records)
{
$hash{$rec} = $counter++;
}
or an implicit one like one of these:
%hash = map {$_ => $counter++} #records;
# or:
$hash{$_} = $counter++ for #records;
This code should generate the proper output, where my-text-file is the path to your data file:
my %hash;
my $counter = 0;
open(FILE, "my-text-file");
while (<FILE>) {
chomp;
$counter++;
$hash{$_} = $counter;
}
# Now to sort
foreach $key (sort(keys(%hash))) {
print $key . ":" . $hash{$key} . "\n";
}
I assume you want to sort the hash aplhabetically. keys(%hash) and values(%hash) return the keys and values of %hash as an array, respectively. Run the program on this file:
f
a
b
d
e
c
And we get:
a:2
b:3
c:6
d:4
e:5
f:1
I hope this helps you.

Perl: mapping to lists' first element

Task: to build hash using map, where keys are the elements of the given array #a, and values are the first elements of the list returned by some function f($element_of_a):
my #a = (1, 2, 3);
my %h = map {$_ => (f($_))[0]} #a;
All the okay until f() returns an empty list (that's absolutely correct for f(), and in that case I'd like to assign undef). The error could be reproduced with the following code:
my %h = map {$_ => ()[0]} #a;
the error itself sounds like "Odd number of elements in hash assignment". When I rewrite the code such that:
my #a = (1, 2, 3);
my $s = ()[0];
my %h = map {$_ => $s} #a;
or
my #a = (1, 2, 3);
my %h = map {$_ => undef} #a;
Perl does not complain at all.
So how should I resolve this — get first elements of list returned by f(), when the returned list is empty?
Perl version is 5.12.3
Thanks.
I've just played around a bit, and it seems that ()[0], in list context, is interpreted as an empty list rather than as an undef scalar. For example, this:
my #arr = ()[0];
my $size = #arr;
print "$size\n";
prints 0. So $_ => ()[0] is roughly equivalent to just $_.
To fix it, you can use the scalar function to force scalar context:
my %h = map {$_ => scalar((f($_))[0])} #a;
or you can append an explicit undef to the end of the list:
my %h = map {$_ => (f($_), undef)[0]} #a;
or you can wrap your function's return value in a true array (rather than just a flat list):
my %h = map {$_ => [f($_)]->[0]} #a;
(I like that last option best, personally.)
The special behavior of a slice of an empty list is documented under “Slices” in perldata:
A slice of an empty list is still an empty list. […] This makes it easy to write loops that terminate when a null list is returned:
while ( ($home, $user) = (getpwent)[7,0]) {
printf "%-8s %s\n", $user, $home;
}
I second Jonathan Leffler's suggestion - the best thing to do would be to solve the problem from the root if at all possible:
sub f {
# ... process #result
return #result ? $result[0] : undef ;
}
The explicit undef is necessary for the empty list problem to be circumvented.
At first, much thanks for all repliers! Now I'm feeling that I should provide the actual details of the real task.
I'm parsing a XML file containing the set of element each looks like that:
<element>
<attr_1>value_1</attr_1>
<attr_2>value_2</attr_2>
<attr_3></attr_3>
</element>
My goal is to create Perl hash for element that contains the following keys and values:
('attr_1' => 'value_1',
'attr_2' => 'value_2',
'attr_3' => undef)
Let's have a closer look to <attr_1> element. XML::DOM::Parser CPAN module that I use for parsing creates for them an object of class XML::DOM::Element, let's give the name $attr for their reference. The name of element is got easy by $attr->getNodeName, but for accessing the text enclosed in <attr_1> tags one has to receive all the <attr_1>'s child elements at first:
my #child_ref = $attr->getChildNodes;
For <attr_1> and <attr_2> elements ->getChildNodes returns a list containing exactly one reference (to object of XML::DOM::Text class), while for <attr_3> it returns an empty list. For the <attr_1> and <attr_2> I should get value by $child_ref[0]->getNodeValue, while for <attr_3> I should place undef into the resulting hash since no text elements there.
So you see that f function's (method ->getChildNodes in real life) implementation could not be controlled :-) The resulting code that I have wrote is (the subroutine is provided with list of XML::DOM::Element references for elements <attr_1>, <attr_2>, and <attr_3>):
sub attrs_hash(#)
{
my #keys = map {$_->getNodeName} #_; # got ('attr_1', 'attr_2', 'attr_3')
my #child_refs = map {[$_->getChildNodes]} #_; # got 3 refs to list of XML::DOM::Text objects
my #values = map {#$_ ? $_->[0]->getNodeValue : undef} #child_refs; # got ('value_1', 'value_2', undef)
my %hash;
#hash{#keys} = #values;
%hash;
}

Resources