Perl: correctly print array of arrays (dereference) - arrays

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.

Related

Adding values together from a hash

I'm trying to add keys together from a hash to get a total from the added values.
Here is what I have so far. Help is appreciated.
print "What is your name?\n";
$letters = <STDIN>;
%alphabet = {
a=>1, b=>2, c=>3, d=>4, e=>5, f=>6, g=>7, h=>8,
i=>9, j=>10, k=>11, l=>12, m=>13,n=>14, o=>15,
p=>16, q=>17, r=>18, s=>19, t=>20, u=>21, v=>22,
w=>23, x=>24, y=>25, z=>26
};
#characters = split('', $letters);
#$characters = keys (%alphabet);
foreach #$character {
$starting_total = 0;
$total = $starting_total + #$character - 10;
print "$total\n";
};
This program will do as you ask
Rather than using split, it applies a global regular expression that finds all of the alphabetic characters in the string. A call to lc makes each letter lower-case to match the hash keys
use strict;
use warnings 'all';
my %alphabet = (
a => 1, b => 2, c => 3, d => 4, e => 5, f => 6, g => 7, h => 8,
i => 9, j => 10, k => 11, l => 12, m => 13, n => 14, o => 15,
p => 16, q => 17, r => 18, s => 19, t => 20, u => 21, v => 22,
w => 23, x => 24, y => 25, z => 26
);
print 'What is your name? ';
my $name = <>;
my $total = 0;
while ( $name =~ /([a-z])/gi ) {
my $letter = $1;
my $n = $alphabet{lc $letter};
printf "%s %2d\n", $letter, $n;
$total += $n;
}
printf "Total %d\n", $total;
output
What is your name? Kokio
K 11
o 15
k 11
i 9
o 15
Total 61
Note that there is no need for a hash to calculate the index of a letter within the alphabet. You could do arithmetic on the code points of the letters, like this
my $n = 1 + ord(lc $letter) - (ord 'a');
or you could declare a constant string ALPHABET and then use index to find the position of each character within it
use constant ALPHABET => join "", 'a' .. 'z';
my $n = 1 + index ALPHABET, lc $letter;
These alternatives produce exactly the same result, as the solution above, and don't require the hash
I'm trying to add keys together from a hash to get a total from the added values.
I don't think you are. The keys in your hash are letters. You can't (sensibly) add letters together. I think you're trying to add together the values from a hash which match a list of keys.
Accuracy and precision are important traits in a programmer. If you can't describe your problem accurately and precisely, then you have little chance of solving it.
Your code doesn't even compile. Let's take a look at it.
# You should always start your Perl programs with "use strict"
# and "use warnings".
print "What is your name?\n";
# When you "use strict" you will need to declare all of your variables
# using "my". So "my $letters = <STDIN>"
$letters = <STDIN>;
# Similarly, "my %alphabet = ..."
# But there are far better ways to set up this hash, as we'll see
# later.
# Also (as Borodin points out in a comment) you have initialised this
# hash incorrectly. A hash should be initialised with a list:
# %alphabet = (a => 1, ...);
# Note the round parentheses indicating a list.
# You have initialised your hash with a single-element list containing
# a hash reference - braces { ... } are the anonymous hash constructor
# and they return a reference to the new hash.
# This is an error that would have been picked up by "use warnings".
%alphabet = {
a=>1, b=>2, c=>3, d=>4, e=>5, f=>6, g=>7, h=>8,
i=>9, j=>10, k=>11, l=>12, m=>13,n=>14, o=>15,
p=>16, q=>17, r=>18, s=>19, t=>20, u=>21, v=>22,
w=>23, x=>24, y=>25, z=>26
};
# "my #characters ..."
#characters = split('', $letters);
# But you're also using an array reference called $characters.
# That's bound to confuse you at some point in the future
#$characters = keys (%alphabet);
# This is the bit that doesn't compile. It should be
# "foreach (#character)". But that's also not right as it uses
# an array called #character, and you don't have an array called
# #character (you have an array called #characters). "use strict"
# will catch errors like this.
# Also, each time round this loop, one of the elements from #character
# will be put into $_. But you don't use $_ in your code at all.
foreach #$character {
# Do you really want to set this to 0 each time?
$starting_total = 0;
# #$character is the number of elements in the array referenced
# by $character. Which is zero as you don't have an array
# reference called $character. I assume you meant #$characters,
# but that is always going to be 26 - which doesn't seem useful.
# And why subtract 10?
$total = $starting_total + #$character - 10;
print "$total\n";
}
Your description of the problem is incredibly vague, but looking at your code (and guessing a lot) I think what you're trying to do is this:
Get a name for the user
Split the name into individual letters
Encode each letter into a number (a=1, b=2, ..., z=26)
Sum the letters in the name
Here's how I would do that.
#/usr/bin/perl
use strict;
use warnings;
# We use modern Perl, specifically say()
use 5.010;
print 'What is your name? ';
chomp(my $name = <STDIN>);
my %letters;
#letters{'a' .. 'z'} = (1 .. 26);
my $total;
foreach (split //, $name) {
$_ = lc $_; # force lower case
next unless exists $letters{$_}; # ignore non-letters
$total += $letters{$_};
}
say "$name is $total";
I don't know what you exactly want. I added the script which gives the addition of the character position
print "Enter your name: ";
chomp (my $name = <STDIN>);
my #arc = split('',$name);
my $total;
my $lc_offset = ord("a") - 1;
foreach (#arc)
{
$total+=(ord(lc($_))) - $lc_offset;
}
print $total;
No need to store the position of the alphabets in hashes. Becuase perl has inbuilt function ord. so the small letters are starts at 97.
It's quite unclear from your question, so I will guess you want to get the numeric sum of all letters in a word.
#!/usr/bin/perl
use strict;
use warnings;
use constant ORD_LC_OFFSET => ord('a') - 1;
print "What is your name?\n";
chomp (my $name = <STDIN>);
my $sum = 0;
$sum += ord( lc($_) ) - ORD_LC_OFFSET for grep { m/[a-zA-Z]/ } split '', $name;
print "$sum\n";
We split the name to the characters and grep only the letter characters. Then we convert each character to the index of the letter (ord does the magic and converts the letter to it's ASCII value). Now we add that to $sum.

Printing "Multi-Dimensional" Array in Perl

I am having a problem attempting to print an array that contains arrays. When printing the array #dev which contains the other arrays, I am only managing to print the first three as it is indicated by the #printing in-line comments. The commented line #print($dev[4][2]); works fine, as well as any of the other combination of numbers within the allowable range. For some reason the for loop does not work. Help!?
my #dev;
my #tf;
my #a;
my #t;
my #f;
my #ofv;
my #tfv;
#tf = ('123456787', '123456788', '123456789'); #printing
#a = (78, 65, 57); #printing
#t = (70, 55, 42); #printing
#f = (77, 64, 56);
#ofv = ('true', 'false', 'false');
#tfv = ('false', 'true', 'true');
#dev = (
[#tf],
[#a],
[#t],
[#f],
[#ofv],
[#tfv],
);
#print($dev[4][2]);
for (my $i = 0; $i <= (scalar(#tf) - 1); $i++) {
for (my $j = 0; $j <= (scalar(#dev) - 1); $j++) {
print($dev[$i][$j]);
print("\n");
}
}
Thank you.
If you just want to show the data of such complex data struct, the modules Data::Dumper or Smart::Comments may be good options.
use Data::Dumper;
print Dumper(\#dev);
or
use Smart::Comments;
### #dev
The output is much more perl-style and not that readable, but is quite convenient to show the struct of such complex data.
Perl can be quite compact.
This snippet of code do the same thing for my arrays #arr1, #arr2 and #arr3:
#arr1 = (1..10);
#arr2 = ('a'..'j');
#arr3 = ('.') x 10;
#multid = \(#arr1, #arr2, #arr3);
print "#$_\n" for (#multid);
OUTPUT:
1 2 3 4 5 6 7 8 9 10
a b c d e f g h i j
. . . . . . . . . .
Also the [] copies an array and gives a reference to it (It's an anonymous array in memory, regardless of the array, a copy of which he is). If there is no need to such duplicate, it is better to use the backslash \ which instead gives a reference to existing array without coping. (like & operator in C, as tell us perldoc)
Your outermost for loop is constrained by the length of t, which is 3. It will never print more than three arrays.
If I understand what you're trying to do, you need top swap #t and #dev. That will print all your values.
That won't, however, print any array that is longer than 3 (the length of dev).
For that, you need:
#dev = (
[#tf], # Probably meant tf
[#a],
[#t],
[#f],
[#ofv],
[#tfv],
);
#print($dev[4][2]);
for (my $i = 0; $i < #dev; $i++) {
for (my $j = 0; $j < #{ $dev[$i] }; $j++) {
print($dev[$i][$j]);
print("\n");
}
}

"Use of uninitialized value" when indexing an array

I get the following error from Perl when trying to run the code below
Use of uninitialized value within #words in concatenation (.) or string...
It references the line where I try to create an array made up of three-word sequences (the line that starts with $trigrams). Can anyone help me figure out the problem?
my %hash;
my #words;
my $word;
my #trigrams;
my $i = 0;
while (<>) {
#words = split;
foreach $word (#words) {
$hash{$word}++;
# Now trying to create the distinct six-grams in the 10-K.
$trigrams[$i] = join " ", $words[$i], $words[$i + 1], $words[$i + 2];
print "$words[$i]\n";
$i++;
}
}
All that is happening is that you are falling off the end of the array #words. You are executing the loop for each element of #words, so the value of $i goes from 0 to $#words, or the index of the final element of the array. So the line
join " ", $words[$i], $words[$i + 1], $words[$i + 2];
accesses the last element of the array $words[$i] and two elements beyond that which don't exist.
In this case, as with any loop which uses the current index of an array, it is easiest to iterate over the array indices instead of the contents. For the join to be valid you need to start at zero and stop at two elements before the end, so 0 .. $#words-2.
It is also neater to use an array slice to select the three elements for the trigram, and use the fact that interpolating an array into a string, as in "#array", will do the same as join ' ', #array. (More precisely, it does join $", #array, and $" is set to a single space by default.)
I suggest this fix. It is essential to use strict and use warnings at the start of every Perl program, and you should declare all your variables using my as late as possible.
use strict;
use warnings;
my %hash;
while (<>) {
my #words = split;
my #trigrams;
for my $i (0 .. $#words - 2) {
my $word = $words[$i];
++$hash{$word};
$trigrams[$i] = "#words[$i,$i+1,$i+2]";
print "$word\n";
}
}
Update
You may prefer this if it isn't too terse for you
use strict;
use warnings;
my %hash;
while (<>) {
my #words = split;
my #trigrams = map "#words[$_,$_+1,$_+2]", 0 .. $#words-2;
}

How can I create a variable array name in Perl?

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.

Storing data into array of hashes

I have a school program I just got and we are learning hashes and the teacher went over hashes of arrays but not really array of hashes and I feel like an AoH is going to work out better for me in the long run. Right now I get all my data into separate variables and I want store them into a AoH bc I have the same variables the entire time but the values change.
What the program is, is a log analyzer and parses through a gigantic log file and all the data is, is lines that look like this.
IPADDY x x [DATE:TIME -x] "METHOD URL HTTPVERS" STATUSCODE BYTES "REFERER" "USERAGENT"
example line being
27.112.105.20 - - [09/Oct/2011:07:22:51 -0500] "GET / HTTP/1.1" 200 4886 "-" "Python-urllib/2.4"
Now I get all the data fine I just dont really understand how to populate and Array of Hashes if anyone can help me out.
Here is an updated code that grabs the data and tries storing it into an AoH. The output in my file used to be perfect just like the print statments I now have commented out. This is all that comes in my output file now "ARRAY(0x2395df0): HASH(0x23d06e8)". Am I doing something wrong?
#!/usr/bin/perl
use strict;
use warnings;
my $j = 0;
my #arrofhash;
my $ipadd;
my $date;
my $time;
my $method;
my $url;
my $httpvers;
my $statuscode;
my $bytes;
my $referer;
my $useragent;
my $dateANDtime;
my ($dummy1, $dummy2, $dummy3);
open ( MYFILE, '>>dodoherty.report');
if ( #ARGV < 1)
{
printf "\n\tUsage: $0 file word(s)\n\n";
exit 0;
}
for (my $i = 0; $i < #ARGV; ++$i)
{
open( HANDLE, $ARGV[$i]);
while( my $line = <HANDLE> )
{
($ipadd, $dummy1, $dummy2, $dateANDtime, $dummy3, $method, $url, $httpvers, $statuscode, $bytes, $referer, $useragent) = split( /\s/, $line);
$method = substr ($method, 1, length($method));
$httpvers = substr ($httpvers, 0, length($httpvers)-1);
$referer = substr ($referer, 1, length($referer)-2);
$useragent = substr ($useragent, 1, length($useragent)-1);
if ( substr ($useragent, length($useragent)-1, length($useragent)) eq '"')
{
chop $useragent;
}
if ( $dateANDtime =~ /\[(\S*)\:(\d{2}\:\d{2}\:\d{2})/)
{
$date = $1;
$time = $2;
}
$arrofhash[$i] = {ipadd => $ipadd, date => $date, 'time' => $time, method => $method, url => $url, httpvers => $httpvers, statuscode => $statuscode, bytes => $bytes, referer => $referer, useragent => $useragent};
# print MYFILE "IPADDY :$ipadd\n";
# print MYFILE "METHOD :$method\n";
# print MYFILE "URL :$url\n";
# print MYFILE "HTTPOVERS : $httpvers\n";
# print MYFILE "STATUS CODE: $statuscode\n";
# print MYFILE "BYTES : $bytes\n";
# print MYFILE "REFERER : $referer\n";
# print MYFILE "USERAGENT : $useragent\n";
# print MYFILE "DATE : $date\n";
# print MYFILE "TIME : $time\n\n";
}
}
for ( my $j = 0; $j < #arrofhash; ++$j)
{
foreach my $hash (#hashkeys)
{
printf MYFILE "%s: %s\n",$hash, $arrofhash[$j];
}
print MYFILE "\n";
}
close (MYFILE);
A common beginner mistake is to not make use of the lexical scope of variables, and just declare all variables at the top, like you do. Declare them within the scope that you need them, no more, no less.
In your case, it would be beneficial to just store the data directly in a hash, then push that hash reference to an array. I would also advise against using split here, as it is working unreliably IMO, and you are splitting quoted strings, using dummy variables to get rid of unwanted data. Instead use a regex.
This regex won't handle escaped quotes inside quotes, but I get the feeling that you will not have to deal with that, since you were using split before to handle this.
You will need to add any further processing to the data, like extracting date and time, etc. If you want some added safety, you can add a warning if the regex seems to have failed, e.g. unless (%f) { warn "Warning: Regex did not match line: '$_'"; next; }
use strict;
use warnings;
use Data::Dumper;
my #all;
while (<DATA>) {
my %f; # make a new hash for each line
# assign the regex captures to a hash slice
#f{qw(ipadd dateANDtime method statuscode bytes referer useragent)} =
/^ # at beginning of line...
(\S+) [\s-]* # capture non-whitespace and ignore whitespace/dash
\[([^]]+)\]\s* # capture what's inside brackets
"([^"]+)"\s* # capture what's inside quotes
(\d+)\s* # capture digits
(\d+)\s*
"([^"]+)"\s*
"([^"]+)"\s*
$/x; # ..until end of line, /x for regex readability only
push #all, \%f; # store hash in array
}
#f{qw(date time)} = split /:/, $f{dateANDtime}, 2;
print Dumper \#all; # show the structure you've captured
__DATA__
27.112.105.20 - - [09/Oct/2011:07:22:51 -0500] "GET / HTTP/1.1" 200 4886 "-" "Python-urllib/2.4"
Basically you just declare the top level structure, and then use it:
my #AoH;
$AoH[0]{some_key} = 5;
$AoH[1]{some_other_key} = 10;
# ^ ^ second level is a hash
# | first level is an array
Which would create an array with two elements, each hashes, each with one key. This feature is called autovivification, and it causes container structures to spring into existence when they are used.
All of this is documented in the perldsc tutorial.
In your case, it would be something like:
$arrofhash[$i]{key_name} = value;
$arrofhash[$i]{another_key} = another_value;
...
or
$arrofhash[$i] = {key => value, key2 => value2, ...}
to set the whole hash at once.

Resources