In Perl, how can I loop through variable names? [duplicate] - perl

This question already has answers here:
How can I use a variable as a variable name in Perl?
(3 answers)
Closed 8 years ago.
I have N array and i want to print 1st elements of them in single for loop
my code:
#arr1=qw(1..5);
#arr2=qw(6..10);
...
#arrN=qw(...);
for($i=1;$i<=$N;$i++)
{
print "$arr[$i][0]";
}

When you find that you need to know the names of 0 .. N different variables. Its time to consider that you might be doing it wrong.
Arrays = list of 0 .. N values, can be sequential
Hash = list of 0 .. N named values
For your arrays, unless you actually want to be converting to strings, don't use qw() just use the bare ()
See solution below, you need an array of arrays:
#!/usr/bin/perl
use strict;
use warnings;
my $n = 10;
my #nArrays;
#fills the nArrays list with array_refs
for my $num(0 .. $n){
push #nArrays, [($num .. $num+5)];
}
#Print out the arrays, one per row, long way
for my $array (#nArrays){
print $array->[0], "\n";
}

If, contrary to all normal recommendations, you leave use strict; out of your script, you could use:
$N = 3;
#arr1 = (1..5);
#arr2 = (6..10);
#arr3 = (7..12);
#arrs = ("arr1", "arr2", "arr3");
for ($i = 0; $i < $N; $i++)
{
print "$arrs[$i][0]\n";
}
Output:
1
6
7
This is absolutely not recommended, but it does work still (mainly for reasons of backwards compatibility).
With use strict; etc, you can use explicit references:
use strict;
use warnings;
my $N = 3;
my #arr1 = (1..5);
my #arr2 = (6..10);
my #arr3 = (7..12);
my #arrs = (\#arr1, \#arr2, \#arr3);
for (my $i = 0; $i < $N; $i++)
{
print "$arrs[$i][0]\n";
}
Output:
1
6
7
On the whole, though, you would do better with a single array of arrays, perhaps like this:
use strict;
use warnings;
my $N = 3;
my #arr = ( [1..5], [6..10], [7..12] );
for (my $i = 0; $i < $N; $i++)
{
print "$arr[$i][0]\n";
}
Output:
1
6
7

What you have in mind is called a symbolic reference, and generally not a good idea.
If the values of these variables belong together, there is already a data structure that is indexed by an integer: Use an array to hold them:
use strict;
use warnings;
my #arr = (
[ 1 .. 5 ],
[ 6 .. 10 ],
# ...
[ 1_000_000 .. 1_000_005 ],
);
for my $i (0 .. $#arr) {
print $arr[$i][0], "\n";
}

Related

How to create dynamic arrays in perl

I have a situation where input is in form of
$n followed by n lines having elements of n different arrays.
like
2
1 2 3
1 6
means i have 2 arrays with elements as 1,2,3 and 1,6.
Now i really dont know how big N can be. How to create dynamic arrays and store value to them. Arrays may be named array1,array2 or any other method to differentiate different arrays.
$n = <STDIN>;
for ($i = 0; $i < $n; $i++) {
$l = <STDIN>;
#arr = split(" ", $l);
}
Please improve this code.
You can use an array of arrays:
use strict;
my #array;
while(<STDIN>) {
my #line = split(" ", $_);
push #array, \#line;
}
# Just to display what's inside your array:
use Data::Dumper;
print Dumper(\#array);
Or even shorter:
use strict;
my #array;
push #array, [split ' ', $_] while(<STDIN>);

Selecting elements from an array and putting it in another array in Perl

I have an array containing 10 numbers. I want to pick numbers in array index 0,2,4,6,8 and put them in a new array. Likewise with index 1,3,5,7,9. I am new to Perl (started a few days ago).
My program:
my #b;
#a = (1,2,3,4,5,6,7,8,9,10);
for($i=0;$i<=$#a;$i++)
{
push(#b,$a[$i+1]);
}
print "#b";
What am I doing wrong?
I suggest avoiding for loop as it's easier to make mistake somewhere in its usage, and use foreach
my #a = (1,2,3,4,5,6,7,8,9,10);
my (#even, #odd);
foreach my $i (0 .. $#a) {
if ($i % 2) { push #odd, $a[$i] } else { push #even, $a[$i] }
}
You can also use map to test array index modulo % 2, and then for #even decide to filter it by () or take value for it using $a[$_]
my #even = map { $_%2 ? () : $a[$_] } 0 .. $#a;
my #odd = map { $_%2 ? $a[$_] : () } 0 .. $#a;
Simple.
my #a = (1,2,3,4,5,6,7,8,9,10);
my (#even, #odd);
for ( #a ) {
$_ % 2 ? push #odd, $_ : push #even, $_;
}
A few things:
Use the pragmas use strict; and use warnings;. These will catch a lot of errors. If you use use strict;, you'll have to declare your variables with my (sometimes you'll use our, but 99% of the time, you'll use my)
In your for loop, you're using the default variable $_. This variable is evil for a variety of reasons. (One, it's global in scope, so something else could change this variable on your and you wouldn't know.). Declare your variables except in situations where you must use $_.
Standard is to put the { on the line with the for and while. Another is to avoid the C style for loop (and to avoid foreach which is just an alias to for)
Use spaces. It's much easier to read $i <= $#a than $i<=$a.
Here's my interpretation of your program:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say); #A nicer 'print'
my #a = qw(12 13 14 15 16 17 18 19 20);
my #even;
my #odd;
for my $element (0..$#a) {
if ( $element % 2 ) {
push #odd, $a[$element];
}
else {
push #even, $a[$element];
}
}
say '#even = ' . join ': ', #even;
say '#odd = ' . join ': ', #odd;
The output:
#even = 12: 14: 16: 18: 20
#odd = 13: 15: 17: 19
Note my for loop. I use the 0..$#a to go through each element of the array. The $# is returns the last index of the array. Note that this is easier to understand than the for($i=0;$i<=$#a;$i++) that you used. It's one of the reasons why C style for loops are discouraged.
I use the modulo operator % to parse my even/odd. Modulo is like remainder division. If the number is odd, the modulo % 2 will be a 1. Otherwise, it's zero. Modulo operations are great for anything that works on a cycle.
But let's get back to your program. Here's your original code with a few minor tweaks.
I added the use strict; and use warnings;. These catch about 99% of your programming errors.
I use use feature qw(say); because say is nicer when it comes to debugging. I can take a statement, copy it, and then throw say qq(...); around it and see what it's doing.
I added a bunch of say statements to reveal the logic of your code.
Let's watch what happens. Here's your program slightly modified:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
my #b;
my #a = (1,2,3,4,5,6,7,8,9,10);
my $i;
for($i=0; $i<=$#a; $i++) {
say "Index = $i Element = $a[$i + 1]";
say qq(push(\#b, $a[$i+1]););
push(#b,$a[$i+1]);
}
print "#b";
And here's the output:
Index = 0 Element = 2
push(#b, 2);
Index = 1 Element = 3
push(#b, 3);
Index = 2 Element = 4
push(#b, 4);
Index = 3 Element = 5
push(#b, 5);
Index = 4 Element = 6
push(#b, 6);
Index = 5 Element = 7
push(#b, 7);
Index = 6 Element = 8
push(#b, 8);
Index = 7 Element = 9
push(#b, 9);
Index = 8 Element = 10
push(#b, 10);
Use of uninitialized value in concatenation (.) or string at ./test.pl line 11.
Index = 9 Element =
Use of uninitialized value within #a in concatenation (.) or string at ./test.pl line 12.
push(#b, );
Use of uninitialized value $b[9] in join or string at ./test.pl line 15.
I can see the how each push statement is being executed, and look at that, you're pushing in each and every element. Actually, you're not because you used $a[$i+1] as what you're pushing.
Using use warnings and I can see that I am trying to push the non-existant $a[10] into your #b array.
Let's change your for loop to go to every other element
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
my #b;
my #a = qw(1 2 3 4 5 6 7 8 9 10);
my $i;
for ($i=0; $i <= $#a; $i += 2) {
push #b, $a[$i];
}
The first element is $a[0]. The next element in the loop is $a[2] because I added 2 to the index instead of just incrementing it by 1. Now, I'll go through all the even elements and skip all of the odd elements.
And the output:
1 3 5 7 9
(Note that $a[0] = 1. That's why they're all odd numbers. It's why I started at 12 in my program, so $a[0] = 12 which is the even number).
My preference would be to use the while and avoid the for(...; ...; ...) construct:
#! /usr/bin/env perl
use strict;
use warnings;
use feature qw(say);
my #b;
my #a = qw(1 2 3 4 5 6 7 8 9 10);
my $i = 0;
while ( $i < $#a ) {
push #b, $a[$i];
$i += 2;
}
Even:
for($i=0;$i<=$#a;$i+=2)
{
push(#b,$a[$i]);
}
Odd:
for($i=1;$i<=$#a;$i+=2)
{
push(#b,$a[$i]);
}
List::MoreUtils has an indexes function:
use List::MoreUtils qw{indexes} ;
use 5.10.0 ;
my #a = (1,2,3,4,5,6,7,8,9,10) ;
# index of array where even
say foreach indexes { $_ % 2 == 0 } #a ;
# index of array where odd
say foreach indexes { $_ % 2 != 0 } #a ;
I admit this may be sort of inelegant and it's possibly cheating here to use a module - especially one that is not in CORE. It would be convenient if List::MoreUtils and List::Utils were just one CORE module, but still not as elegant as some the other answers here.

$array can't print anything

This is my program , I want to let user type a matrix line by line and print the while matrix , but I can't see the matrix
The user will type
1 2 3
4 5 6
7 8 9
like this
and I want to let it show
1 2 3
4 5 6
7 8 9
Perl program
$Num = 3;
while($Num > 0 )
{
$Row = <STDIN>;
$Row = chomp($Row);
#Row_array = split(" ",$Row);
push #P_matrix , #Row_array;
#Row_array = ();
$Num = $Num - 1;
}
for($i=0;$i<scalar(#P_matrix);$i++)
{
for($j=0;$j<scalar(#P_matrix[$i]);$j++)
{
printf "$d ",$P_matrix[$i][$j];
}
print "\n";
}
I change the expression => printf "$d ",$P_matrix[$i][$j]; to print $P_matrix[$i][$j]
but still don't work.
To create a multi-dimensional array, you have to use references. Use
push #P_matrix, [ #Row_array ];
to create the desired structure.
Also, chomp does not return the modified string. Simply use
chomp $Row;
to remove a newline from $Row. Moreover, chomp is not needed at all if you split on ' '.
printf uses % as the formatting character, not $.
You can use Data::Dumper to inspect complex data structures. Use strict and warnings to help you avoid common problems. Here is how I would write your program:
#!/usr/bin/perl
use warnings;
use strict;
use Data::Dumper;
my #p_matrix;
push #p_matrix , [ split ' ' ] while <>;
warn Dumper \#p_matrix;
for my $i (0 .. $#p_matrix)
{
for my $j (0 .. $#{ $p_matrix[$i] })
{
printf '%d ', $p_matrix[$i][$j];
}
print "\n";
}
First and foremost please use use strict; use warnings;
Issues in your code:
You have a single dimensional array, but your are trying to access
it like two dimensional array. In order to make 2 dimensional array push the array reference of Row_array in #P_matrix as [#Row_array].
Where is $d defined? declare $d as my $d or our $d if you mean $d as scalar variable.
OR
For using %d, use need sprintf. Please read this.

Building and printing a multidimensional list in Perl without looping

The top answer in this post: How can I create a multidimensional array in Perl? suggests building a multi-dimensional array as follows:
my #array = ();
foreach my $i ( 0 .. 10 ) {
foreach my $j ( 0 .. 10 ) {
push #{ $array[$i] }, $j;
}
}
I am wondering if there is a way of building the array more compactly and avoiding the nested loop, e.g. using something like:
my #array = ();
my #other_array = (0 ... 10);
foreach my $i ( 0 .. 10 ) {
$array[$i] = #other_array; # This does not work in Perl
}
}
Does Perl support any syntax like that for building multi-dimensional arrays without nested looping?
Similarly, is there a way to print the multidimensional array without (nested) looping?
There is more than one way to do it:
Generating
push accepts LISTs
my #array;
push #{$array[$_]}, 0 .. 10 for 0 .. 10;
Alternative syntax:
my #array;
push #array, [ 0 .. 10 ] for 0 .. 10;
map eye-candy
my #array = map { [ 0 .. 10 ] } 0 .. 10;
Alternative syntax:
my #array = map [ 0 .. 10 ], 0 .. 10;
Printing
With minimal looping
print "#$_\n" for #array;
On Perl 5.10+
use feature 'say';
say "#$_" for #array;
With more formatting control
print join( ', ', #$_ ), "\n" for #array; # "0, 1, 2, ... 9, 10"
"No loops" (The loop is hidden from you)
use Data::Dump 'dd';
dd #array;
Data::Dumper
use Data::Dumper;
print Dumper \#array;
Have a look at perldoc perllol for more details
You are close, you need a reference to the other array
my #array; # don't need the empty list
my #other_array = (0 ... 10);
foreach my $i ( 0 .. 10 ) {
$array[$i] = \#other_array;
# or without a connection to the original
$array[$i] = [ #other_array ];
# or for a slice
$array[$i] = [ #other_array[1..$#other_array] ];
}
}
You can also make anonymous (unnamed) array reference directly using square braces [] around a list.
my #array;
foreach my $i ( 0 .. 10 ) {
$array[$i] = [0..10];
}
}
Edit: printing is probably easiest using the postfix for
print "#$_\n" for #array;
for numerical multidimensional arrays, you can use PDL. It has several constructors for different use cases. The one analogous to the above would be xvals. Note that PDL objects overload printing, so you can just print them out.
use PDL;
my $pdl = xvals(11, 11);
print $pdl;

How to produce range with step in Perl

In Bash, seq 5 5 20 produces 5 10 15 20.
In Perl, 1..5 produces 1 2 3 4 5; does it support step?
How do I produce a range with step in Perl?
perldoc -f map is one way:
use warnings;
use strict;
use Data::Dumper;
my #ns = map { 5 * $_ } 1 .. 4;
print Dumper(\#ns);
__END__
$VAR1 = [
5,
10,
15,
20
];
See also: perldoc perlop
The range operator in Perl doesn't support steps. You could use a for loop instead:
for (my $i = 5; $i <= 20; $i += 5) {
print "$i\n";
}
The List::Gen range function does this:
use strict;
use warnings;
use feature 'say';
use List::Gen;
my $range = range 5, 20, 5;
say for #$range; # 5
# 10
# 15
# 20
say while <$range>; # TIMT1WTDI
$range->say; # TAMT2WTDI, v.0.974
say $range->str; # TAMT3WTDI, v.0.974
my $by_fives = <5 .. 20 by 5>;
say while <$by_fives>; #TAMT4WTDI
<5 .. * by 5>->say( 4 ); #TAMT5WTDI
Not as good as toolic's answer:
use warnings;
use strict;
my #ns;
for my $n (1..4) {
push(#ns, $n*5);
}
I wrote Acme::Range::Module a while back as a gag module - hence the Acme:: namespace - but it does do what you want and has tests and is supported. Here's the example code:
use Acme::Globule qw( Range );
foreach (<10..1>) {
print "$_... ";
}
print "Lift-off!\n";
# put down that crack pipe...
sub my_keys(\%) {
my #hash = %{ $_[0] };
return #hash[ glob("0,2..$#hash") ];
}
sub my_values(\%) {
my #hash = %{ $_[0] };
return #hash[ glob("1,3..$#hash") ];
}
Here is an easy solution that utilizes map and the built-in range operator:
sub range {
my ($start, $end, $step) = #_;
$step ||= 1;
return map { $_ * $step } ($start / $step .. $end / $step);
}
Notice the key point here is the map {} block. We simply divide the end
by the given step (works for negative and positive) then map each value
to the multiple of the given step.
Like the map solution :)
Here it is used to look for files starting with even numbers in a range 116 to 648:
perl -e 'foreach (map { 2 * $_ } (116/2) .. (648/2)) { system("ls -l $_*"); } '
Perl is just wonderful for some jobs and making funny one-liners :)