How can I create multidimensional arrays in Perl? - perl

I am a bit new to Perl, but here is what I want to do:
my #array2d;
while(<FILE>){
push(#array2d[$i], $_);
}
It doesn't compile since #array2d[$i] is not an array but a scalar value.
How should I declare #array2d as an array of array?
Of course, I have no idea of how many rows I have.

To make an array of arrays, or more accurately an array of arrayrefs, try something like this:
my #array = ();
foreach my $i ( 0 .. 10 ) {
foreach my $j ( 0 .. 10 ) {
push #{ $array[$i] }, $j;
}
}
It pushes the value onto a dereferenced arrayref for you. You should be able to access an entry like this:
print $array[3][2];

Change your "push" line to this:
push(#{$array2d[$i]}, $_);
You are basically making $array2d[$i] an array by surrounding it by the #{}... You are then able to push elements onto this array of array references.

Have a look at perlref and perldsc to see how to make nested data structures, like arrays of arrays and hashes of hashes. Very useful stuff when you're doing Perl.

There's really no difference between what you wrote and this:
#{$array2d[$i]} = <FILE>;
I can only assume you're iterating through files.
To avoid keeping track of a counter, you could do this:
...
push #array2d, [ <FILE> ];
...
That says 1) create a reference to an empty array, 2) storing all lines in FILE, 3) push it onto #array2d.

Another simple way is to use a hash table and use the two array indices to make a hash key:
$two_dimensional_array{"$i $j"} = $val;

If you're just trying to store a file in an array you can also do this:
fopen(FILE,"<somefile.txt");
#array = <FILE>;
close (FILE);

Related

Combine scalar array references in perl

I have a datastructure like:
$hashRef->{"key1"}->{"key2"} = ['1','2','3']
This $hashRef gets new values every iteration of the for loop. I am trying to append all of these to produce an output like so:
$hashRef->{"key1"}->{"key2"} = ['loop1.out1','loop1.out2','loop1.out3','loop2.out1','loop2.out2','loop2.out3',...]
loop1.ou1 is symbolic for the first output from loop 1 and not intended to be printed.
Is this possible?
Hope this is what you want:
map {
push(#{$hashRef->{"key1"}->{"key2"}}, "loop$count.out".$_);
} #{$arr2};
$count is the loop number.
#{$arr1} is the incoming array.
Thanks,
Anoop
You may use push to append new values to the array:
push( #{$hashRef->{"key1"}->{"key2"}}, 'loop1.out1','loop1.out2','loop1.out3');
$aData = [ 'loop2.out1','loop2.out2','loop2.out3' ];
push( #{$hashRef->{"key1"}->{"key2"}}, #$aData);
print join(",",#{$hashRef->{"key1"}->{"key2"}} ),"\n";

Multidimension array in perl

I am working on a short script in which two to three variables are linked with each other.
Example:
my #batch;
my #case;
my #type = {
back => "sticker",
front => "no sticker",
};
for (my $i=0; $i<$#batch; $i++{
for (my $j=0; $j<$#batch; $j++{
if ($batch[$i]=="health" && $case[$i]$j]=="pain"){
$type[$i][$j]->back = "checked";
}
}
}
In this short code I want to use #type as $type[$i][$j]->back & $type[$i][$j]->front, but I am getting error that array referenced not defined . Can anyone help me how to fix this ?
Perl two-dimensional arrays are just arrays of arrays: each element of the top level array contains a (reference to) another array. The best reference for this is perldoc perlreftut
From what I can understand, you want an array of arrays of hashes. $type[$i][$j]->back and $type[$i][$j]->front are method calls in Perl, and what you want is $type[$i][$j]{back} and $type[$i][$j]{front}.
use strict;
use warnings;
my #batch;
my #case;
# Populate #batch and #case
my #type;
for my $i (0 .. $#batch) {
for my $j (0 .. $#{ $batch[$i] } ) {
if ($batch[$i] eq 'health' and $case[$i][$j] eq 'pain') {
$type[$i][$j]{back} = 'checked';
}
}
}
But I am very worried about your design. #type will be full of undefined elements, with only occasional ones set to checked. A proper fix depends entirely on what you need to do with #type once you have built it.
I hope this helps
Perl doesn't have multiple dimension variables. To emulate multidimential arrays, you can use what are called references. A reference is a way of referring to a memory location of another Perl structure such as an array or hash.
References allows you to build up more complex structures. For example, you could have an array and instead of each element in the array having a distinct value, it could point to another array. Using this, I can treat my array of arrays as a two dimensional array. But it's not a two dimensional array.
In a two dimensional array, each column ($j) has the same length. That's guaranteed. In Perl, what you have is each row ($i), pointing to a different array of columns ($j), and each of those column arrays could have a different number of elements (or even none at all! That inner array $j may not even be defined!).
There for, I have to check each column and see exactly how many values it might have:
for my $i ( 0..$#array ) {
if ( ref $array[i] ne "ARRAY" ) {
die qq(There is no sub array! for \$array[$i]!\n);
}
my #temp_j_array = #{ $array[$i] } { # This is how you dereference a reference
for my $j ( 0..$#temp_j_array ) {
# Here be dragons...
}
}
Note that I have to see exactly how many columns are in my inner ($j) array before I can go through it.
By the way, notice how I use .. to index my arrays. It's a lot cleaner than using that three part for loop which is very error prone. For example, should you check $i < $#array or $i <= $#array`? See the difference?
Since you're already dealing with a very complex structure (an array of arrays), I'm going to make it even more complex: (An array of arrays of hashes). This added complexity allows me to get rid of three separate variables. Instead of trying to keep #batch #case and #type in sync with each other, I can make these keys to my inner most hash:
my #structure = ... # Some sort of structure...
for my $i ( 0..$#structure ) {
my #temp = #{ $structure[$i] }; # This is a reference to an array. Dereference it.
for my $j ( 0..$#temp ) {
if ( $structure[$i]->[$j]->{batch} eq "health"
and $structure[$i]->[$j]->{case} eq "pain" ) {
$structure[$i]->[$j]->{back} = "checked";
}
}
}
This is a very common way to use Perl references to build more complex data structures:
my %employees; # Keyed by employee number:
$employees{1001}->{NAME} = "Bob";
$employees{1001}->{JOB} = "Yes man";
$employees{1002}->{NAME} = "Susan";
$employees{1002}->{JOB} = "sycophant";
You had some syntax errors, and were using the wrong boolean operator (==) instead of (ne).

Iterating over data structure

I am trying to iterate over this data structure:
$deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}[0]->{code}
where fact[0] is increasing. It's several files I am processing so the number of {facts}[x] varies.
I thought this might work but it doesn't seem to be stepping up the $iter var:
foreach $iter(#{$deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}}){
print $deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}[$iter]->{code}."\n";
}
I'm totally digging data structures but this one is stumping me. Any advice what might be wrong here?
$iter is being set to the content of each item in the array not the index. e.g.
my $a = [ 'a', 'b', 'c' ];
for my $i (#$a) {
print "$i\n";
}
...prints:
a
b
c
Try:
foreach $iter (#{$deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}}){
print $iter->{code}."\n";
}
$iter is not going to be an index that you can subscript the array with, it is rather the current element of the array. So I guess you should be fine with:
$iter->{code}
Your $iter contains the data sctructure. What you basiclly want is:
foreach my $elem ( #{$deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}} ){
print $elem->{code};
}
or:
foreach my $iter ( 0 .. scalar #{$deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}} ){
print $deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}[$iter]->{code}."\n";
}
Since you are looping over the array, your misnamed $iter is the value you are looking for, not an index.
If you want to loop over the indexes instead, do:
foreach $iter ( 0 .. $#{$deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}} ) {
print "Index $iter: ",
$deconstructed->{data}->{workspaces}[0]->{workspace}->{facts}[$iter]->{code}."\n";
}
Also note that you can drop -> between two [] or {}:
$deconstructed->{data}{workspaces}[0]{workspace}{facts}[$iter]{code}
I recommend reading http://perlmonks.org/?node=References+quick+reference.
When you have ugly data structures like this, make an interface for it so your life is easier:
foreach my $fact ( $data_obj->facts ) { # make some lightweight class for this
....;
}
Even without that, consider using a reference to just the part of the data structure you need so you don't think about the rest:
my $facts = $deconstructed->{data}{workspaces}[0]{workspace}{facts};
foreach my $fact ( #$facts ) {
print "Thing is $fact->{code}\n";
}
It's just a reference, so you're not recreating anything. Since you only have to think about the parts beyond the facts key, the problem doesn't look as hard.

Perl: Beginner. Which data structure should I use?

Okay, not sure where to ask this, but I'm a beginner programmer, using Perl. I need to create an array of an array, but I'm not sure if it would be better use array/hash references, or array of hashes or hash of arrays etc.
I need an array of matches: #totalmatches
Each match contains 6 elements(strings):
#matches = ($chapternumber, $sentencenumber, $sentence, $grammar_relation, $argument1, $argument2)
I need to push each of these elements into the #matches array/hash/reference, and then push that array/hash/reference into the #totalmatches array.
The matches are found based on searching a file and selecting the strings based on meeting the criteria.
QUESTIONS
Which data structure would you use?
Can you push an array into another array, as you would push an element into an array? Is this an efficient method?
Can you push all 6 elements simultaneously, or have to do 6 separate pushes?
When working with 2-D, to loop through would you use:
foreach (#totalmatches) {
foreach (#matches) {
...
}
}
Thanks for any advice.
Which data structure would you use?
An array for a ordered set of things. A hash for a set of named things.
Can you push an array into another array, as you would push an element into an array? Is this an efficient method?
If you try to push an array (1) into an array (2), you'll end up pushing all the elements of 1 into 2. That is why you would push an array ref in instead.
Can you push all 6 elements simultaneously, or have to do 6 separate pushes?
Look at perldoc -f push
push ARRAY,LIST
You can push a list of things in.
When working with 2-D, to loop through would you use:
Nested foreach is fine, but that syntax wouldn't work. You have to access the values you are dealing with.
for my $arrayref (#outer) {
for my $item (#$arrayref) {
$item ...
}
}
Do not push one array into another array.
Lists just join with each other into a new list.
Use list of references.
#create an anonymous hash ref for each match
$one_match_ref = {
chapternumber => $chapternumber_value,
sentencenumber => $sentencenumber_value,
sentence => $sentence_value,
grammar_relation => $grammar_relation_value,
arg1 => $argument1,
arg2 => $argument2
};
# add the reference of match into array.
push #all_matches, $one_match_ref;
# list of keys of interest
#keys = qw(chapternumber sentencenumber sentence grammer_relation arg1 arg2);
# walk through all the matches.
foreach $ref (#all_matches) {
foreach $key (#keys) {
$val = $$ref{$key};
}
# or pick up some specific keys
my $arg1 = $$ref{arg1};
}
Which data structure would you use?
An array... I can't really justify that choice, but I can't imagine what you would use as keys if you used a hash.
Can you push an array into another array, as you would push an element into an array? Is this an efficient method?
Here's the thing; in Perl, arrays can only contain scalar variables - the ones which start with $. Something like...
#matrix = ();
#row = ();
$arr[0] = #row; # FAIL!
... wont't work. You will have to instead use a reference to the array:
#matrix = ();
#row = ();
$arr[0] = \#row;
Or equally:
push(#matrix, \#row);
Can you push all 6 elements simultaneously, or have to do 6 separate pushes?
If you use references, you need only push once... and since you don't want to concatenate arrays (you need an array of arrays) you're stuck with no alternatives ;)
When working with 2-D, to loop through would you use:
I'd use something like:
for($i=0; $i<#matrix; $i++) {
#row = #{$matrix[$i]}; # de-reference
for($j=0; $j<#row; $j++) {
print "| "$row[$j];
}
print "|\n";
}
Which data structure would you use?
Some fundamental container properties:
An array is a container for ordered scalars.
A hash is a container for scalars obtained by a unique key (there can be no duplicate keys in the hash). The order of values added later is not available anymore.
I would use the same structure like ZhangChn proposed.
Use a hash for each match.
The details of the match then can be accessed by descriptive names instead of plain numerical indices. i.e. $ref->{'chapternumber'} instead of $matches[0].
Take references of these anonymous hashes (which are scalars) and push them into an array in order to preserve the order of the matches.
To dereference items from the data structure
get an item from the array which is a hash reference
retrieve any matching detail you need from the hash reference

What does it mean when you try to print an array or hash using Perl and you get, Array(0xd3888)?

What does it mean when you try to print an array or hash and you see the following; Array(0xd3888) or HASH(0xd3978)?
EXAMPLE
CODE
my #data = (
['1_TEST','1_T','1_TESTER'],
['2_TEST','2_T','2_TESTER'],
['3_TEST','3_T','3_TESTER'],
['4_TEST','4_T','4_TESTER'],
['5_TEST','5_T','5_TESTER'],
['6_TEST','6_T','^_TESTER']
);
foreach my $line (#data) {
chomp($line);
#random = split(/\|/,$line);
print "".$random[0]."".$random[1]."".$random[2]."","\n";
}
RESULT
ARRAY(0xc1864)
ARRAY(0xd384c)
ARRAY(0xd3894)
ARRAY(0xd38d0)
ARRAY(0xd390c)
ARRAY(0xd3948)
It's hard to tell whether you meant it or not, but the reason why you're getting array references is because you're not printing what you think you are.
You started out right when iterating over the 'rows' of #data with:
foreach my $line (#data) { ... }
However, the next line is a no-go. It seems that you're confusing text strings with an array structure. Yes, each row contains strings, but Perl treats #data as an array, not a string.
split is used to convert strings to arrays. It doesn't operate on arrays! Same goes for chomp (with an irrelevant exception).
What you'll want to do is replace the contents of the foreach loop with the following:
foreach my $line (#data) {
print $line->[0].", ".$line->[1].", ".$line->[2]."\n";
}
You'll notice the -> notation, which is there for a reason. $line refers to an array. It is not an array itself. The -> arrows deference the array, allowing you access to individual elements of the array referenced by $line.
If you're not comfortable with the idea of deferencing with arrows (and most beginners usually aren't), you can create a temporary array as shown below and use that instead.
foreach my $line (#data) {
my #random = #{ $line };
print $random[0].", ".$random[1].", ".$random[2]."\n";
}
OUTPUT
1_TEST, 1_T, 1_TESTER
2_TEST, 2_T, 2_TESTER
3_TEST, 3_T, 3_TESTER
4_TEST, 4_T, 4_TESTER
5_TEST, 5_T, 5_TESTER
6_TEST, 6_T, ^_TESTER
A one-liner might go something like print "#$_\n" for #data; (which is a bit OTT), but if you want to just print the array to see what it looks like (say, for debugging purposes), I'd recommend using the Data::Dump module, which pretty-prints arrays and hashes for you without you having to worry about it too much.
Just put use Data::Dump 'dump'; at beginning of your script, and then dump #data;. As simple as that!
It means you do not have an array; you have a reference to an array.
Note that an array is specified with round brackets - as a list; when you use the square bracket notation, you are creating a reference to an array.
foreach my $line (#data)
{
my #array = #$line;
print "$array[0] - $array[1] - $array[2]\n";
}
Illustrating the difference:
my #data = (
['1_TEST','1_T','1_TESTER'],
['2_TEST','2_T','2_TESTER'],
['3_TEST','3_T','3_TESTER'],
['4_TEST','4_T','4_TESTER'],
['5_TEST','5_T','5_TESTER'],
['6_TEST','6_T','^_TESTER']
);
# Original print loop
foreach my $line (#data)
{
chomp($line);
#random = split(/\|/,$line);
print "".$random[0]."".$random[1]."".$random[2]."","\n";
}
# Revised print loop
foreach my $line (#data)
{
my #array = #$line;
print "$array[0] - $array[1] - $array[2]\n";
}
Output
ARRAY(0x62c0f8)
ARRAY(0x649db8)
ARRAY(0x649980)
ARRAY(0x649e48)
ARRAY(0x649ec0)
ARRAY(0x649f38)
1_TEST - 1_T - 1_TESTER
2_TEST - 2_T - 2_TESTER
3_TEST - 3_T - 3_TESTER
4_TEST - 4_T - 4_TESTER
5_TEST - 5_T - 5_TESTER
6_TEST - 6_T - ^_TESTER
You're printing a reference to the hash or array, rather than the contents OF that.
In the particular code you're describing I seem to recall that Perl automagically makes the foreach looping index variable (my $line in your code) into an "alias" (a sort of reference I guess) of the value at each stage through the loop.
So $line is a reference to #data[x] ... which is, at each iteration, some array. To get at one of the element of #data[0] you'd need the $ sigil (because the elements of the array at #data[0] are scalars). However $line[0] is a reference to some package/global variable that doesn't exist (use warnings; use strict; will tell you that, BTW).
[Edited after Ether pointed out my ignorance]
#data is a list of anonymous array references; each of which contains a list of scalars. Thus you have to use the sort of explicit de-referencing I describe below:
What you need is something more like:
print ${$line}[0], ${$line}[1], ${$line}[2], "\n";
... notice that the ${xxx}[0] is ensuring that the xxx is derefenced, then indexing is performed on the result of the dereference, which is then extracted as a scalar.
I testing this as well:
print $$line[0], $$line[1], $$line[2], "\n";
... and it seems to work. (However, I think that the first form is more clear, even if it's more verbose).
Personally I chalk this up to yet another gotchya in Perl.
[Further editorializing] I still count this as a "gotchya." Stuff like this, and the fact that most of the responses to this question have been technically correct while utterly failing to show any effort to actually help the original poster, has once again reminded me why I shifted to Python so many years ago. The code I posted works, of course, and probably accomplishes what the OP was attempting. My explanation was wholly wrong. I saw the word "alias" in the `perlsyn` man page and remembered that there were some funky semantics somewhere there; so I totally missed the part that [...] is creating an anonymous reference. Unless you drink from the Perl Kool-Aid in deep drafts then even the simplest code cannot be explained.