perl: Use map and foreach at once? - perl

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;

Related

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!

Issues using List::MoreUtils::firstidx

I am trying to use List::MoreUtils methods. But, need some clarity on its usage it in some scenarios.
Please let me know, if it can be used with a map. For example:
#!/usr/bin/perl
use strict;
use warnings;
use List::Util;
use List::MoreUtils;
use Data::Dumper;
my #udData1 = qw(WILL SMITH TOMMY LEE JONES);
my #arr = qw(WILL TOMMY);
my %output = map{$_=>List::MoreUtils::firstidx{/$_/} #udData1} #arr;
print Dumper %output;
print List::MoreUtils::firstidx{/TOMMY/} #udData1;
print "\n";
Output:
$VAR1 = 'TOMMY';
$VAR2 = 0;
$VAR3 = 'WILL';
$VAR4 = 0;
2
As observed I am not getting the values correctly when using map, but getting it fine when used in the later command.
I intend to use $_ as an element of #arr. This may be incorrect. So, please suggest me an alternative. Shall i have to use foreach?
The problem is this bit right here:
List::MoreUtils::firstidx{/$_/} #udData1
In this bit of code, you're expecting $_ to be both the pattern taken from #arr and the string taken from #udData1 at the same time. (Remember that firstidx{/TOMMY/} means firstidx{$_ =~ /TOMMY/}, and likewise firstidx{/$_/} means firstidx{$_ =~ /$_/}.)
What actually happens is that $_ is the value from #udData1 (since that's the innermost loop) and you wind up matching that against itself. Because it's a simple alphabetic string, it always matches itself, and firstidx correctly returns 0.
Here's one solution using a temporary lexical variable:
my %output = map{ my $p = $_;
$p => List::MoreUtils::firstidx{/$p/} #udData1 } #arr;

Perl and Multimap

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;

Missing $ on loop variable

#!/usr/bin/perl
use strict;
use warnings;
my #array = qw[a b c];
foreach my($a,$b,$c) (#array) {
print "$a , $b , $c\n";
}
I receive following error:
Missing $ on loop variable
What is wrong?
I am using: perl v5.10.1 (*) built for x86_64-linux-thread-multi
To grab multiple list items per iteration, use something like List::MoreUtils::natatime
or use splice:
my #tmparray = #array; # don't trash original array
while ( my ($a,$b,$c) = splice(#tmparray,0,3) ) {
print "$a , $b , $c\n";
}
Or reorganize your data into multiple arrays and use one of the Algorithm::Loops::MapCar* functions to loop over multiple arrays at once.
I'm not aware that foreach can eat up more than one parameter at a time in Perl. I might be reading the documentation wrong.
As mentioned in the other answers, Perl does not directly support iterating over multiple values in a for loop. This is one of the issues I addressed in my module List::Gen:
use List::Gen;
my #array = qw/a b c d e f/;
for (every 3 => #array) {
print "#$_\n";
}
outputs:
a b c
d e f
Edit: The list slices that List::Gen produces are aliased to the original list, so that means you can change values in the slice to change the original list. That functionality does not seem possible with several of the other solutions posted for this question.
Block::NamedVar provides nfor that DWIM. This is more convenient than the alternative ways to iterate.
use Block::NamedVar;
my #array = qw[a b c];
nfor my($a,$b,$c) (#array) {
print "$a , $b , $c\n";
}
I think you are missing the point of foreach loop. It goes through every value in an array. It handles one value at a time. Here's the correct code:
#!/usr/bin/perl
use strict;
use warnings;
my #array = qw[a b c];
foreach my $z (#array) {
print "$z\n";
}