How can I create a variable array name in Perl? - arrays

Array #p is a multiline array, e.g. $p[1] is the second line.
This code will explain what I want:
$size=#p; # line number of array #p
for($i=0; $i<$size; $i++)
{
#p{$i}= split(/ +/,$p[$i]);
}
I want the result should be like this:
#p0 = $p[0] first line of array #p goes to array #p0;
#p1 = $p[1] second line of array #p goes to array #p1;
...
...
and so on.
But above code does not work, how can I do it?

It is a bad idea to dynamically generate variable names.
I suggest the best solution here is to convert each line in your #p array to an array of fields.
Lets suppose you have a better name for #p, say #lines. Then you can write
my #lines = map [ split ], <$fh>;
to read in all the lines from the file handle $fh and split them on whitespace. The first field of the first line is then $lines[0][0]. The third field of the first line is $lines[0][2] etc.

First, the syntax #p{$i} accesses the entry with the key $i in a hash %p, and returns it in list context. I don't think you meant that. use strict; use warnings; to get warned about undeclared variables.
You can declare variables with my, e.g. my #p; or my $size = #p;
Creating variable names on the fly is possible, but a bad practice. The good thing is that we don't need to: Perl has references. A reference to an array allows us to nest arrays, e.g.
my #AoA = (
[1, 2, 3],
["a", "b"],
);
say $AoA[0][1]; # 2
say $AoA[1][0]; # a
We can create an array reference by using brackets, e.g. [ #array ], or via the reference operator \:
my #inner_array = (1 .. 3);
my #other_inner = ("a", "b");
my #AoA = (\#inner_array, \#other_array);
But careful: the array references still point to the same array as the original names, thus
push #other_inner, "c";
also updates the entry in #AoA:
say $AoA[1][2]; # c
Translated to your problem this means that you want:
my #pn;
for (#p) {
push #pn, [ split /[ ]+/ ];
}
There are many other ways to express this, e.g.
my #pn = map [ split /[ ]+/ ], #p;
or
my #pn;
for my $i ( 0 .. $#p ) {
$pn[$i] = [ split /[ ]+/, $p[$i] ];
}
To learn more about references, read
perlreftut,
perldsc, and
perlref.

Related

difference between the following two codes of [ and ( in perl?

When I want to assign input file to array, I am getting this error.
while (<>) {
my #tmp = split;
push my #arr,[#tmp];
print "#arr\n";
}
output: ARRAY(0x7f0b00)
ARRAY(0x7fb2f0)
If I change [ to ( then I am getting the required output.
while (<>) {
my #tmp = split;
push my #arr,(#tmp);
print "#arr\n";
output: hello, testing the perl
check the arrays.
What is the deference between (#tmp) and [#tmp]?
Normal parentheses () have no special function besides changing precedence. They are commonly used to confine a list, e.g. my #arr = (1,2,3) Square brackets return an array reference. In your case, you would be constructing a two-dimensional array. (You would, if your code was not broken).
Your code should perhaps be written like this. Note that you need to declare the array outside the loop block, otherwise it will not keep values from previous iterations. Note also that you do not need to use a #tmp array, you can simply put the split inside the push.
my #arr; # declare #arr outside the loop block
while (<>) {
push #arr, [ split ]; # stores array reference in #arr
}
for my $aref (#arr) {
print "#$aref"; # print your values
}
This array would have the structure:
$arr[0] = [ "hello,", "testing", "the", "perl" ];
$arr[1] = [ "check", "the", "arrays." ];
This is a good idea if you for example want to keep lines of input from being mixed up. Otherwise all values end up in the same level of the array.

Generate array and subarray dynamically (perl)

I have a several files that have contain product install information.
I am able to grep for the elements that I want (for example, "version") from the files. And I end up with something like this:
instancefile1:ABC_version=1.2.3
instancefile1:XYZ_version=2.5
instancefile2:ABC_version=3.4.5
instancefile3:XYZ_version=1.1
Where the components are named ABC or XYZ.
What I'd like to do is take this grep output and parse it through perl to build arrays on the file.
The first array would be composed of the instance number (pulled from the filenames) - above, I'd have an array that would have 1,2,3 as elements.
And inside each of those arrays would be the components that that particular instance has.
Full expected arrays and components from above:
array[0] = instancefile1 # can keep this named like the filename,
or assign name. Does not matter
array[0][0] = ABC_version=1.2.3
array[0][1] = XYZ_version=2.5
array[1] = instancefile2
array[1][0] = ABC_version=3.4.5
array[2] = instancefile3
array[2][0] = XYZ_version=1.1
(I know my notation for referencing subarrays is not correct - I'm rusty on my perl.)
How might I do that?
(I've been doing it with just bash arrays and grep and then reiterating through the initial grep output with my first array and doing another grep to fill another array - but this seems like it is going through the data more than one time, instead of building it on the fly.)
The idea is for it to build each array as it sees it. It sees "fileinstance1", it stores the values to the right in that array, as it sees it. Then if it sees "fileinstance2", it creates that array and populates with those values, all in one pass. I believe perl is the best tool for this?
Unless you can guaranteed the records with the same key will be next to each other, it's easier to start with a HoA.
my %data;
while (<>) {
chomp;
my ($key, $val) = split /:/;
push #{ $data{$key} }, $val;
}
Then convert to AoA:
my #data = values(%data);
Order-preserving:
my %data;
my #data;
while (<>) {
chomp;
my ($key, $val) = split /:/;
push #data, $data{$key} = []
if !$data{$key};
push #{ $data{$key} }, $val;
}

Two-dimensional array access in Perl

I have an array populated with cities. I want to pass the array by reference to a sub routine and print each city to output. However, I have the following problems:
I can access each element before my while loop in the subroutine. But I cannot access the elements within my while loop. I get the error message:
...
Use of uninitialized value in print at line 44, line 997 (#1)
Use of uninitialized value in print at line 44, line 998 (#1)
...
The following is some code. I have commented what prints and what doesn't (I tried to cut out code that is not needed for my explanation...):
#cities;
# Assume cities is loaded successfully
&loadCities(getFileHandle('cities.txt'), $NUM_CITIES, \#cities);
&printElements(getFileHandle('names.txt'), \#cities);
sub printElements{
my $counter = 0;
my $arraySize = scalar $_[1];
# Prints fine!!!
print #{$_[1][($counter)%$arraySize];
while ((my $line = $_[0]->getline()) && $counter < 1000){
# Doesn't print. Generates the above error
print #{$_[1][($counter)%$arraySize];
$counter += 1;
}
}
The Perl syntax has me super confused. I do not understand what is going on with #{$_[1]}[0]. I am trying to work it out.
$_[1], treat the value at this location as scalar value (memory
address of the array)
#{...}, interpret what is stored at this
memory address as an array
#{...} [x], access the element at index x
Am I on the right track?
My first tip is that you should put use strict; and use warnings; at the top of your script. This generally reveals quite a few things.
This line: print #{$_[1][($counter)%$arraySize]; doesn't have a closing }. You also don't need the parenthesis around $counter.
Like you mentioned, the best/most clear way to get the length of an array is my $arraySize = scalar #{$_[1]};.
You can check out the documentation here for working with references. I'll give you a quick overview.
You can declare an array as normal
my #array = (1, 2, 3);
Then you can reference it using a backslash.
my $array_ref = \#array;
If you want to use the reference, use #{...}. This is just like using a regular array.
print #{$array_ref};
You could also declare it as a reference to begin with using square braces.
my $array_ref = [1, 2, 3];
print #{$array_ref}; # prints 123
In Perl, a 2-dimensional array is actually an array of array references. Here is an example:
my #array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
print #{$array[1]}; # prints def
Now let's try passing in an array reference to a subroutine.
my #array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
example(\#array); # pass in an array reference
sub example {
my #arr = #{$_[0]}; # use the array reference to assign a new array
print #{$arr[1]};
print #{$_[0][1]}; # using the array reference works too!
}
Now let's put it together and print the whole 2-d array.
my #array = ( ['a', 'b', 'c'], ['d', 'e', 'f'], ['g', 'h', 'i'] );
example(\#array);
sub example {
my #arr = #{$_[0]};
for my $ref (#arr) {
print #{$ref};
}
} # prints abcdefghi
You could adapt this example pretty easily if you wanted to use it for your printElements subroutine.
Another note about printing the elements in an array. Let's take this line from the last example:
print #{$ref};
Since we are calling it every time through the loop, we may want to print a new line at the end of it.
print #{$ref} . "\n";
What does this print? Try it! Does it work?
This is where the built-in subroutine join comes in handy.
print join(" ", #{$ref}) . "\n";
For loops are generally the best way to iterate through an array. My answer here talks a little about doing it with a while loop: https://stackoverflow.com/a/21950936/2534803
You can also check out this question: Best way to iterate through a Perl array
To make references a bit easier to understand, I prefer the -> syntax instead of the munge-it-all-together syntax.
Instead of:
#{$_[1]}[0].
Try
$_[1]->[0];
It means the same thing. It's easier, and it's cleaner to see. You can see that $_[1] is an array reference, and that you're referencing the first element in that array reference.
However, an even better way is to simply set variables for your various element in #_. You have to type a few more letters, but your code is much easier to understand, and is a lot easier to debug.
sub print_elements {
my $file_handle = shift; # This isn't a "reference", but an actual file handle
my $cities_array_ref = shift; # This is a reference to your array
my #cities = #{ $cities_array_ref }; # Dereferencing makes it easier to do your program
Now, your subroutine is dealing with variables which have names, and your array reference is an array which makes things cleaner. Also, you cannot accidentally affect the values in your main program. When you use #_, it's a direct link to the values you pass to it. Modifying #_ modifies the value in your main program which is probably not what you want to do.
So, going through your subroutine:
sub printElements {
my file_handle = shift;
my $cities_array_ref = shift;
my #cities = #{ $cities_array_ref };
my $counter;
my $array_size = #cities; # No need for scalar. This is automatic
while ( my $line = $file_handle->getline and $counter < 1000 ) {
chomp $line;
my $city_number = $counter % $array_size;
print $cities[$city_number]. "\n";
$counter += 1;
}
}
Note, how much easier it is to see what's going on by simply assigning a few variables instead of trying to cram everything together. I can easily see what your parameters to your subroutine are suppose to be. If you called the subroutine with the incorrect parameter order, you could easily spot it. Also notice I broke out $counter % $array_size and assigned that to a variable too. Suddenly, it's obvious what I'm trying to get out of it.
However, I can't see where you're using the $line you're getting with getline. Did I miss something?
By the way, I could have done this without referencing the array in the while loop too:
sub printElements {
my file_handle = shift;
my $cities = shift; # This is an array reference!
my $counter;
my $array_size = #{ $cities }; # I need to deref to get an array
while ( my $line = $file_handle->getline and $counter < 1000 ) {
chomp $line;
my $city_number = $counter % $array_size;
print $cities->[$city_number]. "\n"; # That's it!
$counter += 1;
}
}
See how that -> syntax makes it easy to see that $cities is a reference that points to an array? A lot cleaner and easier to understand than ${$cities}[$city_number].
This code wouldn't actually compile.
print #{$_[1][($counter)%$arraySize];
It probably wants to be
print $_[1]->[($counter)%$arraySize];
after you fix arraySize.
If the result is somehow a pointer to an array then
print "#{$_[1]->[($counter)%$arraySize]}";
I figured how to solve my #1 problem (still looking for help on my #2 if anyone can).
I changed
my $arraySize = scalar $_[1];
to
my $arraySize = #{$_[1]};
And my second print statement is printing the way I want.
It seems that scalar $_[1] was taking the memory address of the array and I was moding against this allowing my $counter to go way beyond the number of elements in the array.
References confuse me too! I always like to dereference them as soon as possible. This works for me:
sub printElements{
my $counter = 0;
my $fh = $_[0];
my #array = #{$_[1]};
my $arraySize = scalar #array;
# Prints fine!!!
print #array[($counter)%$arraySize];
while ((my $line = $fh->getline()) && $counter < 1000){
#Doesn't print. Generates the above error
print #array[($counter)%$arraySize];
$counter += 1;
}
}
I'm sure someone else could explain in the comments why they think working with the reference is a better way (please do), but under the mantra of "keep it simple", I don't like working with them. Probably because I was never a C programmer...

Perl: correctly print array of arrays (dereference)

Hey fellow perl monks,
I'm still wrapping my head around how to correctly dereference. (I read the similar posts prior to posting, but unfortunately am still a bit cloudy on the concept.)
I have the following array, which internally is composed of two arrays. (BTW, I am using strict and warning pragmas.)
use strict; use warnings;
my #a1; my #a2;
where:
#a1 = ( "1MB", "2MB", ... )
and..
#a2 = ( "/home", "/home/debug", ... )
Both #a1 & #a2 are arrays which contain 51 rows. So, I populate these into my 2nd array.
my #b;
push (#b, [ #a1, #a2 ]);
However, when I try to print the results of #b:
sub newl { print "\n"; print "\n"; }
my $an1; my #an1;
$an1 = $#a1;
#an1 = ( 0, 1..$an1 );
for my $i (#an1) { print #b[$i]; &newl; }
I see references to the arrays:
ARRAY(0x81c0a10)
.
ARRAY(0x81c0a50)
.
.
.
How do I properly print this array? I know I need to dereference the array, I'm not sure how to go about doing this. I tried populating my array as such:
push (#b, [ \#a1, \#a2 ]);
Which produces the same results. I also tried:
for my $i (#an1) { print #{$b[$i]}; &newl; }
Which unfortunately errors due to having 0 as an array reference?
Can't use string ("0") as an ARRAY ref while "strict refs" in use at p_disk_ex6.pl line 42.
Any suggestions are greatly appreciated!
A short example program, which might help you:
use strict;
use warnings;
my #a1 = qw(1MB 2MB 10MB 7MB);
my #a2 = qw(/foo /bar /flub /blub);
my #b = (\#a1, \#a2);
# equivalent long version:
# my #b = ();
# $b[0] = \#a1;
# $b[1] = \#a2;
for (my $i = 0; $i <= $#a2; $i++) {
print "a1[$i]: $b[0][$i]\n";
print "a2[$i]: $b[1][$i]\n";
print "\n";
}
In your example you were pushin an anoymous arrayref [] into #b. Therefore $b[0] contained the arrayref.
my #b;
push (#b, [ \#a1, \#a2 ]);
# this corresponds to:
# $b[0][0] = \#a1;
# $b[0][1] = \#a2;
In the example where you wrote [#a1, #a2] you were creating an array_ref which contained the joined arrays #a1 and #a2 (first all elements of #a1, and then all elements of #a2):
my #b;
push(#b , [#a1, #a2]);
# $b[0] = ['1MB' , '2MB', '10Mb', '7MB', '/foo', '/bar', '/flub', '/blub']
Even Simply this also works
use strict;
use warnings;
my #a1 = qw(1MB 2MB 10MB 7MB);
my #a2 = qw(/foo /bar /flub /blub);
my #b = (#a1, #a2);
print "#b";
If you want a general solution that doesn't assume how many elements there are in each of the sub-arrays, and which also allows arbitrary levels of nesting, you're better off using packages that someone else has already written for displaying recursive data structures. A particularly prevalent one is YAML, which you can install if you don't already have it by running cpan:
$ cpan
Terminal does not support AddHistory.
cpan shell -- CPAN exploration and modules installation (v1.9800)
Enter 'h' for help.
cpan[1]> install YAML
Then you can display arbitrary data structures easily. To demonstrate with a simple example:
use YAML;
my #a1 = qw(1MB 2MB 10MB 7MB);
my #a2 = qw(/foo /bar /flub /blub);
my #b = (\#a1, \#a2);
print Dump(\#b);
results in the output
---
-
- 1MB
- 2MB
- 10MB
- 7MB
-
- /foo
- /bar
- /flub
- /blub
For a slightly more complicated example
my #b = (\#a1, \#a2,
{ a => 0, b => 1 } );
gives
---
-
- 1MB
- 2MB
- 10MB
- 7MB
-
- /foo
- /bar
- /flub
- /blub
- a: 0
b: 1
To read this, the three "-" characters in column 1 indicate an array with three elements.
The first two elements have four sub elements each (the lines with "-" in column 3). The
third outer element is a hash reference, since it is made up of "key: value" pairs.
A nice feature about YAML is that you can use it to dump any recursive data structure into a file, except those with subroutine references, and then read it back later using Load.
If you really have to roll your own display routine, that is certainly possible, but you'll have a much easier time if you write it recursively. You can check whether your argument is an array reference or a hash reference (or a scalar reference) by using ref:
my #a1 = qw(1MB 2MB 10MB 7MB);
my #a2 = qw(/foo /bar /flub /blub);
my #b = (\#a1, \#a2,
{ a => 0, b => 1 } );
print_recursive(\#b);
print "\n";
sub print_recursive {
my ($obj) = #_;
if (ref($obj) eq 'ARRAY') {
print "[ ";
for (my $i=0; $i < #$obj; $i++) {
print_recursive($obj->[$i]);
print ", " if $i < $#$obj;
}
print " ]";
}
elsif (ref($obj) eq 'HASH') {
print "{ ";
my #keys = sort keys %$obj;
for (my $i=0; $i < #keys; $i++) {
print "$keys[$i] => ";
print_recursive($obj->{$keys[$i]});
print ", " if $i < $#keys;
}
print " }";
}
else {
print $obj;
}
}
which produces the output
[ [ 1MB, 2MB, 10MB, 7MB ], [ /foo, /bar, /flub, /blub ], { a => 0, b => 1 } ]
I have not written my example code to worry about pretty-printing, nor does it
handle scalar, subroutine, or blessed object references, but it should give you the idea of how you can write a fairly general recursive data structure dumper.

Ways to Flatten A Perl Array in Scalar Context

I recently started learning perl and have a question that I'm not finding a clear answer to on the Internet. say I have something like this,
#arr = (1, 2, 3);
$scal = "#arr"
# $scal is now 123.
Is the use of quotes the only way to flatten the array so that each element is stored in the scalar value? It seems improbable but I haven't found any other ways of doing this. Thanks in advance.
The join function is commonly used to "flatten" lists. Lets you specify what you want between each element in the resulting string.
$scal = join(",", #arr);
# $scal is no "1,2,3"
In your example, you're interpolating an array in a double-quoted string. What happens in those circumstances is is controlled by Perl's $" variable. From perldoc perlvar:
$LIST_SEPARATOR
$"
When an array or an array slice is interpolated into a double-quoted string or a similar context such as /.../ , its elements are separated by this value. Default is a space. For example, this:
print "The array is: #array\n";
is equivalent to this:
print "The array is: " . join($", #array) . "\n";
Mnemonic: works in double-quoted context.
The default value for $" is a space. You can obviously change the value of $".
{
local $" = ':',
my #arr = (1, 2, 3);
my $scalar = "#arr"; # $scalar contains '1:2:3'
}
As with any of Perl's special variables, it's always best to localise any changes within a code block.
You could also use join without any seperator
my $scalar = join( '' , #array ) ;
There is more than one way to do it.
in the spirit of TIMTOWTDI:
my $scal;
$scal .= $_ foreach #arr;
Read section Context in perldata. Perl has two major contexts: scalar and list.
For example:
#a = (1, 1, 1); # list context
print #a; # list context
$count = #a; # scalar context, returns the number of elements in #a
etc.

Resources