Perl and Multimap - perl

Is there way to implement a C++ multimap in perl?

Use a hash of arrays.
my %students = ( # keys are IDs, values are enrollments
100023 => [qw(Geography Mining)],
100058 => [qw(Geography Geology Woodcraft)],
);

If by multimap you mean the C++ multimap, then the answer is yes. In Perl, a map corresponds to a hash. The value associated with a given key in the hash can be a reference to a hash. Perl also does not require you to use -> after the first indexing operation, so instead of saying $h{key1}->{key2} you can just say $h{key1}{key2} which gives you a convincing illusion of a multi-dimensional hash.
Here is an example:
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper;
my %h;
my $i;
for my $k (qw/one two three/) {
for my $j (qw/a b c/) {
$h{$k}{$j} = $i++;
}
}
print "one b should be 1: $h{one}{b}\n",
Dumper \%h;

Related

perl: Use map and foreach at once?

I was wondering if it is possible to make a hash assigning its keys and values at once. Or in general use map and for at one line:
#!/usr/bin/perl
%h = map {$_, $i} qw[a b c] for $i (1..3)
But unfortunatelly not => Number found where operator expected, meant number in the parenthesis. So my question is why am I not able to make double loop by this way? And how otherwise would someone assign hash keys to values (and I dont concern something like $h = {a=>1,b=>2,c=>3} but rather assigning %h = (#keys = #values) ... in other words, how to assign hash by:
2 arrays only (#keys,#values), no scalars
At once (at one line - without block)
Is it even possible in perl?
Populating a hash is simply a matter of assigning a list with alternating keys and values, so you just have to construct the list using the two arrays in an alternating fashion.
use strict;
use warnings;
my #keys = qw(a b c);
my #values = 1..3;
my %h = map { ($keys[$_], $values[$_]) } 0..$#keys;
List::UtilsBy provides a useful abstraction for this in zip_by.
use List::UtilsBy 'zip_by';
my %h = zip_by { #_ } \#keys, \#values;
But actually it's even easier to use slice assignment. Though you technically can't do this in the same statement as the declaration, it's by far the neatest option:
my %h;
#h{#keys} = #values;
Use List::MoreUtils 'zip' or add your own since that module is not a core module:
sub zip(\##){map{($_[0][$_-1],$_[$_])}1..#{$_[0]}}
my %h = zip #keys, #values;
Well, the question is not very clear on 'why?' -- same can be achieved with following code
use strict;
use warnings;
use Data::Dumper;
my $debug = 1;
my %h;
#h{qw(a b c)} = (1..3);
print Dumper(\%h) if $debug;

How do I declare and initialize a hash from two lists (keys and values)? [duplicate]

This question already has answers here:
Perl - built-in function to "zipper" together two arrays?
(4 answers)
Closed 5 years ago.
I seem to recall seeing some syntax recently that let you do something like this
my %hash; #hash{#keys} = #values;
in one statement. I tried the obvious
my %hash{#keys} = #values;
But that produced a syntax error. Was I just dreaming, or is there a new syntax for that?
my %hash = map +($keys[$_] => $values[$_]), 0 .. $#keys;
Or, you can use List::MoreUtils::zip, but I suspect #hash{#keys} = #values will be more efficient.
#!/usr/bin/env perl
use strict;
use warnings;
my #keys = ('a' .. 'z');
my #values = map ord, #keys;
my %map = map +($keys[$_] => $values[$_]), 0 .. $#keys;
use YAML::XS;
print Dump \%map;
Here is another way to do it ...
#!/usr/bin/env perl
use strict;
use warnings;
use feature 'say';
use List::MoreUtils qw(zip); # Here is another way to make a hash.
# If you forgot how to install the module if it didn't come with your system perl, it's easy ...
# cpan install List::MoreUtils
# Do that in your terminal.
# I decided to reverse the letters and the numbers so I could show you how to sort it the hash.
my #letters = reverse('A' .. 'Z');
my #numbers = reverse(1 .. 26);
my %Hash;
# You can make a hash like this from arrays of equal sizes.
#Hash{#letters} = #numbers;
say "\n Here is the hash formed standardly";
foreach(sort{$a cmp $b} keys %Hash)
{
say "$_ => $Hash{$_}";
}
%Hash = zip(#letters, #numbers);
say "\nHere is the hash formed using the zip function";
foreach(sort{$a cmp $b} keys %Hash)
{
say "$_ => $Hash{$_}";
}

Perl reference not printing the expected value

This is my program but why not it is printing my array values instead.
use strict;
use warnings;
use Data::Dumper;
my (#arr1,#arr2) = ([1,1,1,2,3,4],[5,5,5,6,9,87]);
my #arr3 = [\#arr1,\#arr2];
foreach (#arr3){
foreach (#$_){
print $_;
}
}
Output:
ARRAY(0x556414c6b908)ARRAY(0x556414c6b7e8)
but why not it is printing my array values instead.
Because the values are array references. To print the inner values, use dereference:
print #{ $array_ref };
For complex structures (arrays of arrays), you can use Data::Dumper:
use Data::Dumper;
print Dumper($array_ref);
But it still wouldn't work. You can't assign to several arrays at once. The first array gets all the values, the remaining arrays stay empty.
Documented in perlsub:
Do not, however, be tempted to do this:
(#a, #b) = upcase(#list1, #list2);
Like the flattened incoming parameter list, the return list is also
flattened on return. So all you have managed to do here is stored
everything in #a and made #b empty.
First of all, you weren't assigning anything to #arr2. You used something like the following to try to assign to #arr2:
(#arr1, #arr2) = ...;
However, Perl has no way to know how many scalars to assign to #arr1 and how many to assign to #arr2, so it assigns them all to #arr1. Use two different assignments instead.
Secondly, [ ] creates an array and returns a reference to it, so
my #arr1 = [1,1,1,2,3,4];
assigns a single scalar (a reference) to #arr1. This is what you are printing. You want
my #arr1 = (1,1,1,2,3,4);
Same goes for #arr2 and #arr3.
Therefore, your code should be
use strict;
use warnings;
use feature qw( say );
my #arr1 = (1,1,1,2,3,4);
my #arr2 = (5,5,5,6,9,87);
my #arr3 = (\#arr1,\#arr2);
for (#arr3) {
say join ", ", #$_;
}
or
use strict;
use warnings;
use feature qw( say );
my #arr3 = ([1,1,1,2,3,4],[5,5,5,6,9,87]);
for (#arr3) {
say join ", ", #$_;
}

Perl: How do I get the value of a variable in a loop if it is in a1 a2 a3 format

Basically i am trying to access the predefined variable in a perl program.
the variables are in the form a1 a2 a3 format.
I want to access them in a loop. In the loop I will increment postfix scalar value
#!/usr/bin/perl
use strict;
use warnings;
my ($a0,$a1,$a2,$a3)= (10,12,14,16);
for(my $i=0; $i<=3; $i++) {
my $var = ${a$i};
print $var;
}
WHAT I EXPECT:
When I print $var in loop, I need the values 10,12 .. defined earlier.
WHAT I CAN NOT DO:
I am aware that such situation can be handled with a hash. But I do not have any control over the variable naming, hence I can not use hash or change variable format.
I appreciate your help!
If you want to avoid turning off strict, you could use eval:
#!/usr/bin/perl
use strict;
use warnings;
my ($a0,$a1,$a2,$a3)= (10,12,14,16);
for(my $i=0; $i<=3; $i++) {
print eval "\$a$i";
}
Update: using more readable version suggested by Сухой27 in the comments
Use an array instead of multiple similarly named variables, as this is their main use case,
use strict;
use warnings;
my #a = (10,12,14,16);
for my $i (0 .. $#a) {
my $var = $a[$i];
print $var, "\n";
}
alternatively you can use array of scalar references
use strict;
use warnings;
my ($a0,$a1,$a2,$a3) = (10,12,14,16);
my #a = \($a0,$a1,$a2,$a3);
for my $i (0 .. $#a) {
my $var = ${ $a[$i] };
print $var, "\n";
}
What you are doing here is called a symbolic reference, and it is an EXTREMELY bad idea.
Please take a look through this article: http://perl.plover.com/varvarname.html
But the long and short of it is - using a variable as a variable name - which you're doing - is dangerous and unnecessary. It causes all sorts of potential problems in your code, including bugs in completely unrelated pieces of code. This is why strict won't let you do it.
More importantly - it's completely unnecessary, because perl has the hash as a native data type.
Instead of your code, consider instead:
my %h;
( $h{0}, $h{1}, $h{2}, $h{3} ) = ( 10, 12, 14, 16 );
foreach my $key ( sort keys %h ) {
print "$key = $h{$key}\n";
}
Now, it's added a few characters to your code, but by doing so - you've created a lexically scoped namespace called %h. (I'd suggest calling it something more meaningful, personally - and definitely avoid $a and $b because they have special meanings).
But there is no danger of this namespace trampling over other parts of your code, and for bonus points - you no longer need your 'for' loop, you can simply iterate on keys instead. (So you always have the right number).
(Or as another user has suggested - just use an array)
You can get round strict's restriction on dynamic variable names like this.
#!/usr/bin/perl
use strict;
use warnings;
{
no strict 'refs';
my ($a0,$a1,$a2,$a3)= (10,12,14,16);
for(my $i=0; $i<=3; $i++) {
my $var = ${a$i};
print $var;
}
}
I don't think this is a good idea, though!

How to use a 'subroutine reference' as a hash key

In Perl, I'm learning how to dereference 'subroutine references'. But I can't seem to use a subroutine reference as a hash 'key'.
In the following sample code,
I can create a reference to a subroutine ($subref) and then dereference it to run the subroutine (&$subref)
I can use the reference as a hash 'value' and then easily dereference that
But I cannot figure out how to use the reference as a hash 'key'. When I pull the key out of the hash, Perl interprets the key as a string value (not a reference) - which I now understand (thanks to this site!). So I've tried Hash::MultiKey, but that seems to turn it into an array reference. I want to treat it as a subroutine/code reference, assuming this is somehow possible?
Any other ideas?
use strict;
#use diagnostics;
use Hash::MultiKey;
my $subref = \&hello;
#1:
&$subref('bob','sue'); #okay
#2:
my %hash;
$hash{'sayhi'}=$subref;
&{$hash{'sayhi'}}('bob','sue'); #okay
#3:
my %hash2;
tie %hash2, 'Hash::MultiKey';
$hash2{$subref}=1;
foreach my $key (keys %hash2) {
print "Ref type is: ". ref($key)."\n";
&{$key}('bob','sue'); # Not okay
}
sub hello {
my $name=shift;
my $name2=shift;
print "hello $name and $name2\n";
}
This is what is returned:
hello bob and sue
hello bob and sue
Ref type is: ARRAY
Not a CODE reference at d:\temp\test.pl line 21.
That is correct, a normal hash key is only a string. Things that are not strings get coerced to their string representation.
my $coderef = sub { my ($name, $name2) = #_; say "hello $name and $name2"; };
my %hash2 = ( $coderef => 1, );
print keys %hash2; # 'CODE(0x8d2280)'
Tieing is the usual means to modify that behaviour, but Hash::MultiKey does not help you, it has a different purpose: as the name says, you may have multiple keys, but again only simple strings:
use Hash::MultiKey qw();
tie my %hash2, 'Hash::MultiKey';
$hash2{ [$coderef] } = 1;
foreach my $key (keys %hash2) {
say 'Ref of the key is: ' . ref($key);
say 'Ref of the list elements produced by array-dereferencing the key are:';
say ref($_) for #{ $key }; # no output, i.e. simple strings
say 'List elements produced by array-dereferencing the key are:';
say $_ for #{ $key }; # 'CODE(0x8d27f0)'
}
Instead, use Tie::RefHash. (Code critique: prefer this syntax with the -> arrow for dereferencing a coderef.)
use 5.010;
use strict;
use warnings FATAL => 'all';
use Tie::RefHash qw();
my $coderef = sub {
my ($name, $name2) = #_;
say "hello $name and $name2";
};
$coderef->(qw(bob sue));
my %hash = (sayhi => $coderef);
$hash{sayhi}->(qw(bob sue));
tie my %hash2, 'Tie::RefHash';
%hash2 = ($coderef => 1);
foreach my $key (keys %hash2) {
say 'Ref of the key is: ' . ref($key); # 'CODE'
$key->(qw(bob sue));
}
From perlfaq4:
How can I use a reference as a hash key?
(contributed by brian d foy and Ben Morrow)
Hash keys are strings, so you can't really use a reference as the key.
When you try to do that, perl turns the reference into its stringified
form (for instance, HASH(0xDEADBEEF) ). From there you can't get back
the reference from the stringified form, at least without doing some
extra work on your own.
Remember that the entry in the hash will still be there even if the
referenced variable goes out of scope, and that it is entirely
possible for Perl to subsequently allocate a different variable at the
same address. This will mean a new variable might accidentally be
associated with the value for an old.
If you have Perl 5.10 or later, and you just want to store a value
against the reference for lookup later, you can use the core
Hash::Util::Fieldhash module. This will also handle renaming the keys
if you use multiple threads (which causes all variables to be
reallocated at new addresses, changing their stringification), and
garbage-collecting the entries when the referenced variable goes out
of scope.
If you actually need to be able to get a real reference back from each
hash entry, you can use the Tie::RefHash module, which does the
required work for you.
So it looks like Tie::RefHash will do what you want. But to be honest, I don't think that what you want to do is a particularly good idea.
Why do you need it? If you e.g. need to store parameters to the functions in the hash, you can use HoH:
my %hash;
$hash{$subref} = { sub => $subref,
arg => [qw/bob sue/],
};
foreach my $key (keys %hash) {
print "$key: ref type is: " . ref($key) . "\n";
$hash{$key}{sub}->( #{ $hash{$key}{arg} } );
}
But then, you can probably choose a better key anyway.