Perl hash when both the keys and the values are array references - perl

I have a problem where pairs of numbers map to other pairs of numbers. For instance, (1,2)->(12,97). Some pairs may map to multiple other pairs, so what I really need is the ability to map a pair into a list of lists, like (1,2)->((12,97),(4,1)). At the end of the day I want to process each of the values (i.e., each list of lists) separately.
In Python, I could do this by simply saying:
key = ( x, y )
val = [ a, b ]
if (x,y) not in my_dict:
my_dict[ (x,y) ] = []
my_dict[ (x,y) ].append( [a,b] )
However, in Perl, I have to use refs for the keys and values. So I can certainly say:
$keyref = [ x1, y1 ]
$valref = [ a, b ]
%my_hash = { $keyref => $valref }
But what happens when another pair (x2,y2) comes along? Even if x2==x1 and y2==y1, $keyref=[x2,y2] will differ from the previous keyref generated, so I do not see a way to do the lookup. Of course, I could compare (x2,y2) with each dereferenced hash key, but after all, God gave us hash tables precisely to avoid the need to do so.
Is there a Perl solution?
Thanks,
-W.

In Perl, all hash keys are strings, or are "stringified" before lookup. Using an array reference as a key is usually the wrong approach.
What about using a "two-dimensional" hash?
$hash{$x1}{$y1} = [ $a, $b ];
# or
%hash = ( $x1 => { $y1 => [ $a, $b ] } );
($x2,$y2)=($x1,$y1);
print #{$hash{$x2}{$y2}}; # will print $a and $b

Like most things in Perl, TMTOWTDI.
Option 1: Use multidimensional array emulation
$hash{$x,$y} = [$a, $b];
See also the documentation for the built-in variable $;.
Option 2: Use the Hash::MultiKey module
tie %hash, 'Hash::MultiKey';
$hash{[$x, $y]} = [$a, $b];
Option 3: Use a HoH (hash of hashes) instead
$hash{$x}{$y} = [$a, $b];

I ended up using Socket Puppet's solution (in the form of Michael Carmen's Option 3). FYI, here is a little Perl script that carries out all the operations I need in my app.
Printed lines 2:,3: and 4:,5: just use different syntax to do the same thing, and lines 0: and 1: were just intended as sanity checks along the way.
What this this adds to the suggested solution is the use of an array of arrays as the value that goes along with a key.
#k1 = ( 12, 13 );
$aref = [ 11, 22 ];
$bref = [ 33, 44 ];
%h = {};
if( not exists $h{$k1[0]}{$k1[1]} ) {
print "initializing\n";
$h{$k1[0]}{$k1[1]} = [];
}
push #{$h{$k1[0]}{$k1[1]}}, $aref;
push #{$h{$k1[0]}{$k1[1]}}, $bref;
print "0: ", join ':', #{$h{$k1[0]}{$k1[1]}}, "\n";
print "1: ", join ':', ${$h{$k1[0]}{$k1[1]}}[0], "\n";
print "2: ", join ':', #{${$h{$k1[0]}{$k1[1]}}[0]}, "\n";
print "3: ", join ':', #{${$h{$k1[0]}{$k1[1]}}[1]}, "\n";
print "4: ", join ':', #{$h{$k1[0]}{$k1[1]}->[0]}, "\n";
print "5: ", join ':', #{$h{$k1[0]}{$k1[1]}->[1]}, "\n";
P.S. I would have added this as a comment but it was too long, and I thought it made sense to include a worked example.

Related

Why I can use #list to call an array, but can't use %dict to call a hash in perl? [duplicate]

This question already has answers here:
Why do you need $ when accessing array and hash elements in Perl?
(9 answers)
Closed 8 years ago.
Today I start my perl journey, and now I'm exploring the data type.
My code looks like:
#list=(1,2,3,4,5);
%dict=(1,2,3,4,5);
print "$list[0]\n"; # using [ ] to wrap index
print "$dict{1}\n"; # using { } to wrap key
print "#list[2]\n";
print "%dict{2}\n";
it seems $ + var_name works for both array and hash, but # + var_name can be used to call an array, meanwhile % + var_name can't be used to call a hash.
Why?
#list[2] works because it is a slice of a list.
In Perl 5, a sigil indicates--in a non-technical sense--the context of your expression. Except from some of the non-standard behavior that slices have in a scalar context, the basic thought is that the sigil represents what you want to get out of the expression.
If you want a scalar out of a hash, it's $hash{key}.
If you want a scalar out of an array, it's $array[0]. However, Perl allows you to get slices of the aggregates. And that allows you to retrieve more than one value in a compact expression. Slices take a list of indexes. So,
#list = #hash{ qw<key1 key2> };
gives you a list of items from the hash. And,
#list2 = #list[0..3];
gives you the first four items from the array. --> For your case, #list[2] still has a "list" of indexes, it's just that list is the special case of a "list of one".
As scalar and list contexts were rather well defined, and there was no "hash context", it stayed pretty stable at $ for scalar and # for "lists" and until recently, Perl did not support addressing any variable with %. So neither %hash{#keys} nor %hash{key} had meaning. Now, however, you can dump out pairs of indexes with values by putting the % sigil on the front.
my %hash = qw<a 1 b 2>;
my #list = %hash{ qw<a b> }; # yields ( 'a', 1, 'b', 2 )
my #l2 = %list[0..2]; # yields ( 0, 'a', 1, '1', 2, 'b' )
So, I guess, if you have an older version of Perl, you can't, but if you have 5.20, you can.
But for a completist's sake, slices have a non-intuitive way that they work in a scalar context. Because the standard behavior of putting a list into a scalar context is to count the list, if a slice worked with that behavior:
( $item = #hash{ #keys } ) == scalar #keys;
Which would make the expression:
$item = #hash{ #keys };
no more valuable than:
scalar #keys;
So, Perl seems to treat it like the expression:
$s = ( $hash{$keys[0]}, $hash{$keys[1]}, ... , $hash{$keys[$#keys]} );
And when a comma-delimited list is evaluated in a scalar context, it assigns the last expression. So it really ends up that
$item = #hash{ #keys };
is no more valuable than:
$item = $hash{ $keys[-1] };
But it makes writing something like this:
$item = $hash{ source1(), source2(), #array3, $banana, ( map { "$_" } source4()};
slightly easier than writing:
$item = $hash{ [source1(), source2(), #array3, $banana, ( map { "$_" } source4()]->[-1] }
But only slightly.
Arrays are interpolated within double quotes, so you see the actual contents of the array printed.
On the other hand, %dict{1} works, but is not interpolated within double quotes. So, something like my %partial_dict = %dict{1,3} is valid and does what you expect i.e. %partial_dict will now have the value (1,2,3,4). But "%dict{1,3}" (in quotes) will still be printed as %dict{1,3}.
Perl Cookbook has some tips on printing hashes.

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}

Inconsistent (silly?) data access in Perl 5 (also confusing me regarding use of sigils)

This question is about asking for some explanation of what's going on in the Perl system for I don't implicitly see the point though I'm coding for more than 25 years now. So here comes the story ...
On trying to work with Cyrus::IMAP::Admin instances in Perl5 I've tried to get and print a list of quotas resulting in a somewhat strangely structured data returned.
my %quotas = $client->listquota(#list[0]);
if ( $client->error ) {
printf STDERR "Error: " . $client->error . "\n";
exit 1;
}
print "root: " . $list[0] . "\n";
foreach my $quota ( keys %quotas ) {
print( $quota, " ", $quotas{$quota}[0], "/", $quotas{$quota}[1], " KiB\n" );
}
This code is actually working as desired by printing out stuff like
root: user.myuser
STORAGE: 123/4567 KiB
This code was taken from Cyrus::IMAP::Shell reading similar to this:
my %quota = $$cyrref->listquota(#nargv);
foreach my $quota (keys %quota) {
$lfh->[1]->print(" ", $quota, " ", $quota{$quota}[0], "/", $quota{$quota}[1]);
if ($quota{$quota}[1]) {
$lfh->[1]->print(" (", $quota{$quota}[0] * 100 / $quota{$quota}[1], "%)");
}
}
This code looks somewhat silly to me for using $quota{$quota}[0]. In my case I renamed variables a bit for rejecting that mixed use of differently typed but equivalently named variables.
Prior to taking the code from Cyrus::IMAP::Admin I tried to understand its specification and to process the result by code written myself. It looked like this:
my %quotas = $client->listquota(#list[0]);
if ( $client->error ) {
printf STDERR "Error: " . $client->error . "\n";
exit 1;
}
print "root: " . $list[0] . "\n";
foreach my $quota ( keys %quotas ) {
my #sizes = #quotas{$quota};
print( $quota, " ", $sizes[0], "/", $sizes[1], "\n" );
}
However, this code didn't work and I didn't find any plausible explanation myself. My understanding here is that transferring this last code example to the initially posted form would require to have source of assignment in line 11 substituted into usages in line 12 and change the sigil of quotas from # to $ for I'm trying to get a scalar result finally. This last code was printing an array reference before slash and nothing after it. So I had to fix my code like this to get it working:
my %quotas = $client->listquota(#list[0]);
if ( $client->error ) {
printf STDERR "Error: " . $client->error . "\n";
exit 1;
}
print "root: " . $list[0] . "\n";
foreach my $quota ( keys %quotas ) {
my #sizes = #quotas{$quota};
print( $quota, " ", $sizes[0][0], "/", $sizes[0][1], "\n" );
}
This additional dereferencing in line 12 is what I'm confused about now. Why is #sizes containing an array storing another array in its sole first element? For being confused I already tried alternative code in line 11 to no avail. These tests included
my #sizes = $quotas{$quota};
(for its equivalence with original code posted above) and
my $sizes = #quotas{$quota};
(for I don't know why). Switching sigils don't seem to change semantics of assignment here at all. But using this assignment seems to open different view on data structure contained in %quotas originally. What sigils are required to have #sizes matching content and structure of $quotas{$quota} as used in top-most code fragment?
$quotas{$quota} accesses a single scalar element in %quotas. #quotas{$quota} is a hash slice which selects a list of one element from the hash.
$collection[...] or $collection{...}: Single scalar element
my #array = (1, 2, 3, 4);
my $elem = $array[1]; #=> 2
#collection[...] or #collection{...}: list of elements
my #array = (1, 2, 3, 4);
my #slice = #array[1, 3]; #=> (2, 4)
%collection[...] or %collection{...}: key-value list, not yet available except in “blead”.
my #array = (1, 2, 3, 4);
my %kvs = %array[1, 3]; #=> (1 => 2, 3 => 4)
Using another sigil when referencing an element in a collection does not dereference the element!
Now what does or single-element list #quotas{$quota} contain? It is an array reference [...].
When you assign this to a scalar, the list is evaluated in scalar context and gives the last element:
my $sizes = #quotas{$quota};
my $sizes = ([...]);
my $sizes = [...];
[...]
Accessing an element in that array reference then looks like $sizes->[0].
When you assign this to an array, you create an array that holds as a single element this array reference:
my #sizes = #quotas{$quota};
my #sizes = ([...]);
([...])
Accessing an element in that array reference then looks like $sizes[0][0], because you first have to get at that array reference inside the array #sizes.
… and incidentally the same happens here when you do $quotas{$quota}, but for slightly different reasons.
If you want to dereference an array reference to an array, use curly braces:
my #foo = #{$array_refernce} which copies the contents
my $elem = ${$array_reference}[0] which accesses an element, the same as $array_reference->[0].
So you could do
my #sizes = #{ $quotas{$quota} };
to dereference the array and copy it to an array variable. You can then access an element like $sizes[0].
I believe you want this on your line 11:
my #sizes = #{ $quotas{$quota} };
Also, recommend you start using Data::Dumper everywhere.
E.g.
use Data::Dumper;
print 'Data structure of \%quotas: ' . Dumper(\%quotas) . qq(\n);
That way you can be sure what structure you are dealing with.

Perl forming string random string combination

I have a file with around 25000 records, each records has more than 13 entries are drug names. I want to form all the possible pair combination for these entries. Eg: if a line has three records A, B, C. I should form combinations as 1) A B 2) A C 3)B C. Below is the code I got from internet, it works only if a single line is assigned to an array:
use Math::Combinatorics;
my #n = qw(a b c);
my $combinat = Math::Combinatorics->new(
count => 2,
data => [#n],
);
while ( my #combo = $combinat->next_combination ) {
print join( ' ', #combo ) . "\n";
}
The code I am using, it doesn't produce any output:
open IN, "drugs.txt" or die "Cannot open the drug file";
open OUT, ">Combination.txt";
use Math::Combinatorics;
while (<IN>) {
chomp $_;
#Drugs = split /\t/, $_;
#n = $Drugs[1];
my $combinat = Math::Combinatorics->new(
count => 2,
data => [#n],
);
while ( my #combo = $combinat->next_combination ) {
print join( ' ', #combo ) . "\n";
}
print "\n";
}
Can you please suggest me a solution to this problem?
You're setting #n to be an array containing the second value of the #Drugs array, try just using data => \#Drugs in the Math::Combinatorics constructor.
Also, use strict; use warnings; blahblahblah.
All pairs from an array are straightforward to compute. Using drugs A, B, and C as from your question, you might think of them forming a square matrix.
AA AB AC
BA BB BC
CA CB CC
You probably do not want the “diagonal” pairs AA, BB, and CC. Note that the remaining elements are symmetrical. For example, element (0,1) is AB and (1,0) is BA. Here again, I assume these are the same and that you do not want duplicates.
To borrow a term from linear algebra, you want the upper triangle. Doing it this way eliminates duplicates by construction, assuming that each drug name on a given line is unique. An algorithm for this is below.
Select in turn each drug q on the line. For each of these, perform steps 2 and 3.
Beginning with the drug immediately following q and then for each drug r in the rest of the list, perform step 3.
Record the pair (q, r).
The recorded list is the list of all unique pairs.
In Perl, this looks like
#! /usr/bin/env perl
use strict;
use warnings;
sub pairs {
my #a = #_;
my #pairs;
foreach my $i (0 .. $#a) {
foreach my $j ($i+1 .. $#a) {
push #pairs, [ #a[$i,$j] ];
}
}
wantarray ? #pairs : \#pairs;
}
my $line = "Perlix\tScalaris\tHashagra\tNextium";
for (pairs split /\t/, $line) {
print "#$_\n";
}
Output:
Perlix Scalaris
Perlix Hashagra
Perlix Nextium
Scalaris Hashagra
Scalaris Nextium
Hashagra Nextium
I've answered something like this before for someone else. For them, they had a question on how to combine a list of letters into all possible words.
Take a look at How Can I Generate a List of Words from a group of Letters Using Perl. In it, you'll see an example of using Math::Combinatorics from my answer and the correct answer that ikegami had. (He did something rather interesting with regular expressions).
I'm sure one of these will lead you to the answer you need. Maybe when I have more time, I'll flesh out an answer specifically for your question. I hope this link helps.

Perl hash value without key name

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" ]
);