Perl hash value without key name - perl

Is it possible in Perl to access a value of a hash, if it has just one key, without using key value?
Let's say, %h has just 'key_name' => 'value'.
Can I access the 'value' only via $h->{key_name}?
Or, is possible to access this 'value' without key name?

The values builtin function for hashes will return a list of all the hash values. You can use this to get or set any values with aliasing list constructs such as foreach, map, and grep:
for my $value (values %hash) {
say $value; # prints the value
$value++; # adds one to the value
}
Or you can store the values in an array:
my #vals = values %hash;
The order of the returned values is effectively random, but it will be the same order as the corresponding keys function.
Hashes themselves are lists, so you can access any odd element of the hash in list context to get at the value, but this method is less efficient since the whole hash needs to be taken apart to form the list, not just the values.
The techniques above work with hashes of any size. If you only have one key / value pair:
my %hash = qw(foo bar);
Then they reduce to:
{my ($x) = values %hash; say $x} # bar
{my (undef, $x) = %hash; say $x} # bar
{my $x = (values %hash)[0]; say $x} # bar
{my $x = (%hash)[1]; say $x} # bar

There are many ways to do this. For example:
my %h=("key_name"=>"value"); print values(%h)
or
my %h=("key_name"=>"value"); print( (%h)[1])
But in my opinion that doesn't look pretty...

You've got two options here - you can either optimize for space, or optimize for time. If you need to get the key from that value and you don't care about how long it takes, you can iterate over each entry in the associative array:
while(($key, $value) = each(%h))
{
if($value eq 'value')
{
return $key;
}
}
But if you don't mind having two copies, the most time-efficient solution is to hold a backwards and forwards associative array -- that is: %h_by_name and %h_by_value.
In this case, if you have multiple keys with the same value, your %h_by_value should contain an array. That is if:
%h_by_name = (
"a" => "1",
"b" => "1",
"c" => "1",
"d" => 2"
);
Then you would want to construct your %h_by_value such that it was:
%h_by_value = (
"1" => [ "a", "b", "c" ],
"2" => [ "d" ]
);

Related

Is it possible to push a key-value pair directly to hash in perl?

I know pushing is only passible to array, not hash. But it would be much more convenient to allow pushing key-value pair directly to hash (and I am still surprise it is not possible in perl). I have an example:
#!/usr/bin/perl -w
#superior words begin first, example of that word follow
my #ar = qw[Animals,dog Money,pound Jobs,doctor Food];
my %hash;
my $bool = 1;
sub marine{
my $ar = shift if $bool;
for(#$ar){
my #ar2 = split /,/, $_;
push %hash, ($ar2[0] => $ar2[1]);
}
}
marine(\#ar);
print "$_\n" for keys %hash;
Here I have an array, which has 2 words separately by , comma. I would like to make a hash from it, making the first a key, and the second a value (and if it lacks the value, as does the last Food word, then no value at all -> simply undef. How to make it in perl?
Output:
Possible attempt to separate words with commas at ./a line 4.
Experimental push on scalar is now forbidden at ./a line 12, near ");"
Execution of ./a aborted due to compilation errors.
I might be oversimplyfing things here, but why not simply assign to the hash rather than trying to push into it?
That is, replace this unsupported expression:
push %hash, ($ar2[0] => $ar2[1]);
With:
$hash{$ar2[0]} = $ar2[1];
If I incoporate this in your code, and then dump the resulting hash at the end, I get:
$VAR1 = {
'Food' => undef,
'Money' => 'pound',
'Animals' => 'dog',
'Jobs' => 'doctor'
};
Split inside map and assign directly to a hash like so:
my #ar = qw[Animals,dog Money,pound Jobs,doctor Food];
my %hash_new = map {
my #a = split /,/, $_, 2;
#a == 2 ? #a : (#a, undef)
} #ar;
Note that this can also handle the case with more than one comma delimiter (hence splitting into a max of 2 elements). This can also handle the case with no commas, such as Food - in this case, the list with the single element plus the undef is returned.
If you need to push multiple key/value pairs to (another) hash, or merge hashes, you can assign a list of hashes like so:
%hash = (%hash_old, %hash_new);
Note that the same keys in the old hash will be overwritten by the new hash.
We can assign this array to a hash and perl will automatically look at the values in the array as if they were key-value pairs. The odd elements (first, third, fifth) will become the keys and the even elements (second, fourth, sixth) will become the corresponding values. check url https://perlmaven.com/creating-hash-from-an-array
use strict;
use warnings;
use Data::Dumper qw(Dumper);
my #ar;
my %hash;
#The code in the enclosing block has warnings enabled,
#but the inner block has disabled (misc and qw) related warnings.
{
#You specified an odd number of elements to initialize a hash, which is odd,
#because hashes come in key/value pairs.
no warnings 'misc';
#If your code has use warnings turned on, as it should, then you'll get a warning about
#Possible attempt to separate words with commas
no warnings 'qw';
#ar = qw[Animals,dog Money,pound Jobs,doctor Food];
# join the content of array with comma => Animals,dog,Money,pound,Jobs,doctor,Food
# split the content using comma and assign to hash
# split function returns the list in list context, or the size of the list in scalar context.
%hash = split(",", (join(",", #ar)));
}
print Dumper(\%hash);
Output
$VAR1 = {
'Animals' => 'dog',
'Money' => 'pound',
'Jobs' => 'doctor',
'Food' => undef
};

How to store values of hash in array in perl?

I have a hash having duplicate values and unique keys.I have to store keys in array of size 5, if more keys are there new array should be created and stored in it.
The keys stored in 1 array should have same value.
Note: I have to read those values from excel sheet and generate c source file.
Ex:
%hash = (a=>1,b=>2,c=>1,d=>1,e=>3,f=>4,g=>4,h=>1,i=>1,j=>1);
output in c file:
datatype arr1[]={a,c,d,h,i};
datatype arr2[]={j};
datatype arr3[]={b};
datatype arr4[]={e};
datatype arr5[]={f,g};
So you need to find keys that have the same values?
So we need to kind of revert the array, but being a bit smart to handle that the original values are not unique. Som instead of just transforming 'key' => 'value' pairs to 'value' => 'key', we need to store the keys in arrays.
my %hash = ...;
my %transposed;
for my $key (keys %hash) {
my $value = $hash{$key};
$transposed{$value} = [] unless defined $transposed{$value};
push #{ $transposed{$value} }, $key;
}
Then you have a hash of arrays, where each key is a value in the original hash and the elements of the arrays are the keys. The next step is to iterate over the keys and spilt each list into lines of 5 elements:
for my $key (sort keys %transposed) {
while (#{ $transposed{$key} }) {
my #list = splice #{ $transposed{$key} }, 0, 5;
say join ", ", #list;
}
}
The main parts is the while loop iterating as long as there are elements in the current list and the splice removes and returns up to 5 element from the list each iteration. Adding the exact C code is left as an exercise for the interested reader... :-)
You might need to read up on references: http://perldoc.perl.org/perlreftut.html
The line setting a hash value to a reference to an empty array is not necessary as perl will automatically create a arrayref when you tries to push a value to it. I have included it to make it clearer what is going on.

Perl Hash of Hash

So I am trying to use a Perl HoH and push some values into an array from the HoH.
Here is a portion of the code to better explain;
my $hoh = (
antenna_included=>{
"1" => '1 MultiBand Antenna',
"2" =>'2 Multiband Antennas',
"3" =>'1 MultiBand Antenna & 2 WiFi Antennas',
"4" =>'2 Multiband Cellular Antennas & 2 WiFi Antennas',
"N" =>'No Antennas Included',
},
ip_rating=>{
I6 => 'IP 64',
CD => 'Intrinsically Safe, Class 1 Div 2, IP 64',
NI => 'No',
});
foreach $group ( sort keys %hoh ) {
foreach $spec ( sort keys %{ $hoh{$group} } ) {
print "$spec=>$hoh{$group}{$spec}\n";
}
print "what part is it: ";
my $input = <STDIN>;
chomp $input;
if ( exists $hoh{$group} ) {
print "$hoh{$spec}\n"; #this is the problematic line.
}
else {
print "not a match!\n";
}
}
Basically the goal of this script is to loop through the HoH, but throughout each block of hash it gives STDIN, then you type in the key, and then then I want to push the value of that element into an array. (Right now the code just says print for debugging).
I have tried
$hoh{$group}{$spec}
$hoh{$group}
$hoh{$group}->{$spec}
For $hoh{$group} I get HASH(0x6ff920) and all of the other values it is just blank, no error.
Any thoughts? Thank you
Use the -> operator.
Everything underneath the HoH is a hashref, and accessing elements of those requires the -> operator. (Technically, you have a "hash of hashref" not a "hash of hash," since such a thing isn't possible.)
See the perlref docs for details about references.
$hoh{$group}->{$spec}
UPDATE
Here's a modified version of your program. The differences are:
The initial declaration of %hoh is done with the hash variable %hoh, not the scalar variable, $hoh. Assigning the initializing list to $hoh would result in the last element of the list being assigned to the variable, in this case, the hashref corresponding to ip_rating. This is almost certainly not what you want.
Attempting to access an element of $hoh{$group} requires the -> operator, like `$hoh{$group}->{$spec}.
The problem area probably needs to be checking for the existence of $hoh{$group}->{$input}. If it doesn't exist, the input isn't valid, which is what the message suggests.
You just need to understand the difference between a hash and a hashref (similar to the difference between an array and an arrayref) and realize that hashes and arrays cannot be elements of other hashes or arrays. Hashrefs and arrayrefs can be elements of other data structures, because only scalars can be contained within another data structure. Hashes and arrays are not scalars, but references are scalars (including hashref, arrayrefs, coderefs, and even scalarrefs) and are necessary to create any sort of complex, nested data structure.
my %hoh = (
antenna_included=>{
"1" => '1 MultiBand Antenna',
"2" =>'2 Multiband Antennas',
"3" =>'1 MultiBand Antenna & 2 WiFi Antennas',
"4" =>'2 Multiband Cellular Antennas & 2 WiFi Antennas',
"N" =>'No Antennas Included',
},
ip_rating=>{
I6 => 'IP 64',
CD => 'Intrinsically Safe, Class 1 Div 2, IP 64',
NI => 'No',
});
foreach $group ( sort keys %hoh ) {
foreach $spec ( sort keys %{ $hoh{$group} } ) {
print "$spec=>$hoh{$group}->{$spec}\n";
}
print "what part is it: ";
my $input = <STDIN>;
chomp $input;
if ( exists $hoh{$group}->{$input} ) {
print $hoh{$group}->{$input}."\n";
}
else {
print "not a match!\n";
}
}
Braces {...} and parentheses (...) are very diffferent. The first defines an anonymous hash, which you would assign to a scalar variable, while the second defines a list, which you would assign to a hash variable. You have a mixture of the two, and it isn't clear which you meant.
If you have a scalar variable $hoh then you need to initialise it with
my $hoh = { ... }
and access the elements with the indirection operator
$hoh->{$group}->{$spec}
or
$hoh->{$group}{$spec}
If you have a hash variable then you need to initialise it with
my %hoh = ( ... )
and access the elements directly
$hoh{$group}->{$spec}
or
$hoh{$group}{$spec}

How to alter the hash values with new values in perl?

How can i reset the hash values in perl
use warnings;
use strict;
my %hash = qw(one 1 two 2 three 3 four 4);
my #key = keys(%hash);
my #avz = (9..12);
my %vzm;
print "Original hash and keys : ",%hash,"\n";
for(my $i = 0; $i<=scalar #avz; $i++){
my #new = "$key[$i] $avz[$i] ";
push(%vzm , #new);
}
print "modified hash and keys",%vzm,"\n";
I tried to alter the keys of original hash with another keys. How can i do it
This program give the error is:
Original hash and keys : three3one1two2four4
Not an ARRAY reference at key.pl line 10.
I expect the output is
Original hash and keys : three3one1two2four4
modified hash and keys : three11one9two10four12
How can i do it
Ok, first off - you're doing something nasty in your code:
You're trying to take an ordered data structure - an array - and push it into a keyed data structure, which has no particular ordering defined.
This isn't going to work very well - it technically works, because internally perl treats arrays and hashes similarly.
But for example your first assignment - what you're actually getting is:
my %hash = (
one => 1,
two => 2,
three => 3,
four => 4
);
You can access the keys (in no particular order) via keys(). And the values via values(). But to try and treat it like an array is undefined behaviour.
To add elements to your array:
$hash{'nine'} = 9;
To delete elements from your array:
delete ( $hash{'one'} );
You can iterate on keys or values - and combined with sort even do them in some sort of order. (Just bear in mind for sorting alphanumeric numbers you'll have a custom sort job).
foreach my $key ( sort keys %hash ) {
print "$key => $hash{$key}\n";
}
(Note - this is sorting by alphanumeric string, so gives:
four => 4
one => 1
three => 3
two => 2
If you want to sort by value:
foreach my $key ( sort { $hash{$a} <=> $hash{$b} } keys %hash ) {
print "$key => $hash{$key}\n";
}
And so you'll get:
one => 1
two => 2
three => 3
four => 4
So the real question remains - what are you actually trying to accomplish? The point of a hash is to give you an unordered mini-database of key-value pairs. Treating one like an array doesn't make an awful lot of sense. Either you're iterating hash elements in arbitrary order, or you're applying a specific sort to it - but one where you're relying on getting elements in a particular order is a bad plan - it may work, but it's not guaranteed to work, and that makes for bad code.
You have to keep the order of the keys in some array, or take it from original list
my #tmp = qw(one 1 two 2 three 3 four 4);
my %hash = #tmp;
# 'one', 'two', ..
my #key = #tmp[ grep !($_%2), 0 .. $#tmp ];
# ..
for my $i (0 .. $#avz) {
$vzm{ $key[$i] } = $avz[$i];
}
or using hash slice as more perlish approach,
#vzm{ #key } = #avz;
You can't do what you want (replace the values for keys in the hash in the order they originally were added) without keeping track of that order separately, since the hash doesn't have any particular order. In other words, this:
my #key = keys(%hash);
needs to be this:
my #key = ( 'one', 'two', 'three', 'four' );
Once you have that, you can just assign the values all at once with a hash slice:
my %vzm;
#vzm{#key} = #avz;
To create a hash element, you use assignment to $var{$key}.
for (my $i = 0; $i < scalar #avz; $i++) {
$vzm{$key[$i]} = $avz[$i];
}
Note also that the loop condition should be <, not <=. List/array indexes end at scalar #avz - 1.

Inserting Array References into Perl Heap

I am inserting 2-dimensional array references into my heap in Perl.
How should I define the 'elements' attribute when constructing my heap so that I can properly use my comparator function?
my $heap = Heap::Simple->new( order => \&byNumOrStr,
elements => [Array => 0]
);
sub byNumOrStr
{
my ( $a, $b ) = #_;
$b->[0] <=> $a->[0] #0-th element is a number.
||
$a->[1] cmp $b->[1]; #1-st element is a number
}
I keep getting back this error:
Can't use string ("2.55") as an ARRAY ref while "strict refs" in use ... (This means I might actually have to compare my "number string" numerically)
Well, it's likely that either $a or $b is being passed in as a string. Try printing out this variables after the assignment.
From what I can see from the documentation, when you pass elements => [ Array => 0 ], unless the 0th item in the array is an array then you'll only be comparing the values in the first slot of the array.
[Array => $index]
Indicates that the elements are array references, with the key at index $index. So now the element can be not just the key, but also associated data.
This means that if 2.55 is in the array like [ 2.55, ... ] then that's what's being passed in as $a or $b.
The elements entry tells H::S how you want to derive the key. For a completely generic way, it says that you can pass [Function => $code_ref_for_key]. You could make it like this:
sub first_two_slots {
my $array_ref = shift;
return [ #$array_ref[0,1] ];
}
And then with the order as specified, it would pass that array into your order and specify
my $heap = Heap::Simple->new( order => \&byNumOrStr,
elements => [Function => \&first_two_slots]
);
Original comment left in place: (It's not relevant to how Heap::Simple calls order).
if byNumOrStr is called from sort DON'T assign $a and $b in it. Those values are set by sort. If there is something coming in #_ it's probably not what you want.
Sorting a two-dimensional array doesn't really make sense -- when you sort something, there is a defined order. Having two sort criteria doesn't make it a two-dimensional list... do you mean that you have data that are a list of two elements? e.g.:
my $element = [ '0', 'string' ];
I think Example 1 in the documentation ("where key and value are kept separate") applies here -- you want to sort the references, not the values themselves. So try declaring with elements => "Any", and then adjust your sort method to match:
(I was wrong.. it looks like elements => [Array => 0] is correct, since these are just plain old arrayrefs being sorted.
my $heap = Heap::Simple->new( order => \&byNumOrStr,
elements => [Array => 0],
);
sub byNumOrStr
{
my ( $val1, $val2 ) = #_;
my $result =
$val1->[0] <=> $val2->[0] # the 0th element is a number
||
$val1->[1] cmp $val2->[1]; # the 1st element is a string
# The docs say "this should return a true value if $key1 is smaller than $key2 and a false value otherwise."
return $result == -1;
}
PS. As discussed in Secondary Order in Heap::Simple, the comparison function in Heap::Simple does not want a return value of -1, 0, or 1, but rather true or false. You need to convert the comparison result before returning from the function.