Indexing perl array - arrays

I have below code
#ar1 = ('kaje','beje','ceje','danjo');
$m = 'kajes';
my($next) = grep $_ eq 'kaje',#ar1;
print("Position is $next\n");
print("Next is $ar1[$next+1]\n");
print("Array of c is $ar1[$m+3]\n");
print("Array at m is $ar1[$m]\n");
Output seen:
Position is kaje
Next is beje
Array of c is danjo
Array at m is kaje
I want to understand how this works. Here $next is the matched string and i am able to index on that string for given array. Also $m is not found in the array, yet we get output as first element of array.

ALWAYS use use strict; use warnings;. It answers your question.
In one place, you treat the string kaje as a number.
In two places, you treat the string kajes as a number.
Since these two strings aren't numbers, Perl will use zero and issue a warning.
In other words, Next is beje only "works" because kaje happens to be at index 0. I have no idea how why you believe the last two lines work seeing as kajes is nowhere in the array.
You want
# Find the index of the element with value `kaje`.
my ($i) = grep $ar1[$_] eq 'kaje', 0..$#ar1;

Related

How do I pass in the name of an array index in perl?

Background
I have these four lines of code that I am trying to extract into one function:
my #firstList = split /\s+/, $doubleArray{A_I}{B_I};
#firstList = shuffle(#firstList);
my #secondList = split /\s+/, $doubleArray{A_I}{C_I};
#secondList = shuffle(#secondList);
Since the only functional difference is the second index of the two dimensional array , I wanted to make it so that the second index ("B_I" and "C_I") are passed into a function.
Proposed solution
In other words I want to make a function like the one below:
my funkyFunc($){
my $index = shift;
my #list = split /\s+/, $doubleArray{A_I}{$index};
#list = shuffle(#list);
return #list;
}
I would intend to use it as such:
my #firstList = funkyFunc("B_I");
my #secondList = funkyFunc("C_I");
However, I'm unsure as to how perl would interpret "B_I" or "C_I", or likewise B_I and C_I. Because of this complexity, I wanted to know...
Question
Is there a way in perl to pass in the name of an array index?
Those aren't arrays, those are hashes (dictionaries, associative arrays in other languages). With that, the B_I and C_I are then treated as bareword strings. If these were actual arrays, then these would be treated as bareword function calls, and you'd have to have those available to be called in the caller, and no need for quoting there.
Since you're using hashes, the keys are strings, and you passing in 'B_I' will result in $index being a string of B_I, and since your {$index} has something that isn't allowed in a bareword string (the $), perl will interpret it as a variable instead of a literal, and everything will work exactly the way you want.

Not able to divide each row of a csv file in the form of array using perl

I am stucked in a problem wherein I am parsing a csv file. The CSV file looks like-
CPU Name,DISABLE,Memory,Encoding,Extra Encoding
,b,d,,
String1,YES,1TB,Enabled,Enabled
String2,NO,1TB,Enabled,Enabled
String3,YES,1TB,Enabled,Enabled
I want to capture the first two rows in two different arrays. The code that I am using to do it is-
my $row_no =0;
while(my $row=<$fi>){
chomp($row);
$row=~ s/\A\s+//g;
$row=~s/\R//g;
#say $row;
if($row_no==0)
{
#say $row;
my #name_initial = split(',',$row);
say length(#name_initial);
say #name_initial;
}
elsif($row_no==1)
{
#say $row;
#data_type_initial =split(',',$row);
say length(#data_type_initial);
say #data_type_initial;
}
$row_no++;
}
Now I formed two arrays from topmost two lines in file (#name_initial and #data_type_initial respectively).When I am printing these array I can see all the 5 values but when I am printing the length of array it is showing length of each array as 1. When I am printing the element using index of arrays I find each element in place then why it is showing length as 1. Also second array which is formed from second line of csv file is printed as "bd". All the null values are gone and although it is containing two values 'b' and 'd'. Its length is printed as 1.
I want to convert the row of csv file in array with all the null and non_NULL values so that I can iterate on the array elements and can give conditions based on null and non null values.How can I do that???
Have a look at perldoc length. It says this:
length EXPR
length
Returns the length in characters of the value of
EXPR. If EXPR is omitted, returns the length of $_. If EXPR is
undefined, returns undef.
This function cannot be used on an entire array or hash to find out
how many elements these have. For that, use scalar #array and scalar
keys %hash, respectively.
Like all Perl character operations, length normally deals in logical
characters, not physical bytes. For how many bytes a string encoded as
UTF-8 would take up, use length(Encode::encode('UTF-8', EXPR)) (you'll
have to use Encode first). See Encode and perlunicode.
In particular, the bit that says "This function cannot be used on an entire array or hash to find out how many elements these have. For that, use scalar #array and scalar keys %hash, respectively".
So you're using the wrong approach here. Instead of say length(#array), you need say scalar(#array).
To explain the results you're getting. length() expects to be given a scalar value (a string) to measure. So it treats your array as a scalar (effectively adding an invisible call to scalar()) and gets back the number of elements in the array (which is "5") and length() then tells you the number of elements in that string - which is 1.
It's also worth pointing out that you don't need to keep track of your own $row_no variable. Perl has a built-in variable called $. which contains the current record number.
Using that knowledge (and adding little whitespace) gives us something like this:
while (my $row = <$fi>) {
chomp($row);
$row =~ s/\A\s+//g;
$row =~s/\R//g;
#say $row;
if ($. == 0) {
#say $row;
my #name_initial = split(/,/, $row);
say scalar(#name_initial);
say #name_initial;
} elsif ($. == 1) {
#say $row;
#data_type_initial = split(/,/, $row);
say scalar(#data_type_initial);
say #data_type_initial;
}
}
Update: You sneaked a couple of extra questions in at the end of this one. I'd suggest that you raise those separately.

Fun with array of arrays

I'm a total Perl newb, but still cannot believe I cannot figure this out with all the info I've read through online, but, I've burned too much time and am suffering from block at this point. Hoping to learn something based on my real life example...
Ok, I think I have an array of arrays, created like this:
my #array1 = ();
my #array2 = ();
my $ctr1 = 0;
my $col;
[sql query]
while(($col)=$sth->fetchrow_array() ) {
$array1[$ctr1]=$col;
$ctr1++;
}
print STDERR "#array1";
##results in 10 rows, a mac address in each
##00:00:00:00:00:00 00:11:11:11:11:11 22:22:22:22:22:22 33:33:33:33:33:33 ...
Now I do another query. While looping through results, I am looking for those 10 mac addresses. When I find one, I add a row to array2 with the mac and the sequential number accumulated to the point, like this:
[sql query]
while(($col)=$sth->fetchrow_array() ) {
$ctr2++;
if( my ($matched) = grep $_ eq $col, #array1 ) {
push( #array2, ($col,$ctr2) );
}
}
print STDERR "#array2";
##results in 10 rows, a mac address and an integer in each
##00:00:00:00:00:00 2 00:11:11:11:11:11 24 22:22:22:22:22:22 69 33:33:33:33:33:33 82 ...
Now the easy part. I want to loop through array2, grabbing the mac address to use as part of a sql query. Therein lies the problem. I am so ignorant as to exactly what I am doing that even though I had it almost working, I can't get back to that point. Ignorance is definitely not bliss.
When I loop through array2, I am getting a host of errors, based on the different forms of the statement. The one I think is right is listed below along with the error message...
my $ctr3 = 0;
foreach $ctr3 (#array2) {
my $chkmac = $array2[$ctr3][0]; <--- gacks here with the error below - line 607
[SQL query]
[Thu May 30 14:05:09 2013] [error] Can't use string ("00:66:55:77:99:88") as an ARRAY ref while "strict refs" in use at /path/to/test.cgi line 607.\n
I believe the issue is that my array of arrays is not an array of arrays. If it were, it would work as coded, or so I think from the reading... That said, I cannot fathom what I am dealing with otherwise. This will be a head slapper I'm all but sure, but I am stumped.... Little help, please?
TIA
O
For an array of arrays you want to use an array reference, e.g.
push #array2, [$col, $ctr2];
When accessing an element within an array refernce, you'll want to use the -> operator. Also, when looping through an array, it's not necessary to index back into that same array. So the last part would look more like:
foreach $ctr3 (#array2) {
my $chkmac = $ctr3->[0];
....
When you do the foreach there, $ctrl3 won't have the index in it, it'll have the value. So you should just need to do $ctrl3->[0]. Note the -> which dereferences the array reference (#array2 is actually an array of array references).
EDIT: As AKHolland pointed out, #array2 actually isn't an array of array references, although that's what it should be. You also need to change:
push( #array2, ($col, $ctr2) );
To
push( #array2, [$col, $ctr2] );
This makes an array reference, rather than a list. A list in this context just collapses down into regular arguments to push, meaning you're pushing two separate strings into #array2.
You are correct that your array of arrays is not an array of arrays, since in Perl there is no such thing. So what do you have instead? There's two ways to see.
First, when you print #array2, you come up with a string composed of alternating MACs and counts, separated by spaces. Since the spaces sort-of-signify the division between array elements, we might surmise that what we've got is a single array of heterogeneous elements, such that element 0 is a MAC, element 1 is a count, element 2 is another MAC, and so on.
The other perspective is to look at how #array2 is constructed:
push( #array2, ($col,$ctr2) );
From the documentation for push, we find that push ARRAY LIST works by appending the elements of LIST to the end of ARRAY. This has the effect of flattening the list into the array such that its original identity as a list is lost. You can add all the parentheses you want, when Perl expects a list it flattens all of them away.
So how do you achieve the effect you want? The List-of-Lists documentation has a detailed treatment, but the short answer is that you make a list of array references. Array references are scalars and are therefore legal elements in an array. But they retain their identify as array references!
The anonymous array reference constructor is the square bracket []. In order to push an array reference containing the elements $col and $ctr2 onto the end of #array2, you simply do this:
push( #array2, [$col, $ctr2] );
The code you wrote for accessing a particular element of the array-reference-in-an-array now works. But since I've already written a bunch of paragraphs on the subject, let me finish by explaining what was wrong originally and how changing the push statements suddenly makes it work.
The expression $array2[$ctr3][0] is sometimes written as $array2[$ctr3]->[0] to clarify what it's actually doing. What it does is to take the value of $array2[$ctr3] and treat it as an array reference, taking its 0 element. If we take $ctr3 to be 0 (as it would be at the top of the loop) the value of $array2[$ctr3] is the first element, 00:00:00:00:00:00. When you then subsequently ask Perl to treat 00:00:00:00:00:00 as an array reference, Perl dies because it doesn't know how to treat a string as an array reference.
When instead the value of $array2[$ctr3] is an array reference because that is what you pushed onto #array2 when constructing it, Perl is able to do as you ask, dereferencing the array reference and looking at element 0 of the resulting array, whose value happens to be 00:00:00:00:00:00.

How to get a single column of emails from a html textarea into array

I was thinking I could do this on my own but I need some help.
I need to paste a list of email addresses from a local bands mail list into a textarea and process them my Perl script.
The emails are all in a single column; delimited by newlines:
email1#email.com
email2#email.com
email3#email.com
email4#email.com
email5#email.com
I would like to obviously get rid of any whitespace:
$emailgoodintheory =~ s/\s//ig;
and I am running them through basic validation:
if (Email::Valid->address($emailgoodintheory)) { #yada
I have tried all kinds of ways to get the list into an array.
my $toarray = CGI::param('toarray');
my #toarraya = split /\r?\n/, $toarray;
foreach my $address(#toarraya) {
print qq~ $address[$arrcnt]<br /> ~:
$arrcnt++;
}
Above is just to test to see if I was successful. I have no need to print them.
It just loops through, grabs the schedules .txt file and sends each member the band schedule. All that other stuff works but I cannot get the textarea into an array!
So, as you can see, I am pretty lost.
Thank you sir(s), may I have another quick lesson?
You seem a bit new to Perl, so I will give you a thorough explanation why your code is bad and how you can improve it:
1 Naming conventions:
I see that this seems to be symbolic code, but $emailgoodintheory is far less readable than $emailGoodInTheory or $email_good_in_theory. Pick any scheme and stick to it, just don't write all lowercase.
I suppose that $emailgoodintheory holds a single email address. Then applying the regex s/\s//g or the transliteration tr/\s// will be enough; space characters are not case sensitive.
Using a module to validate adresses is a very good idea. :-)
2 Perl Data Types
Perl has three man types of variables:
Scalars can hold strings, numbers or references. They are denoted by the $ sigil.
Arrays can hold an ordered sequence of Scalars. They are denoted by the # sigil.
Hashes can hold an unordered set of Scalars. Some people tend to know them as dicitonaries. All keys and all values must be Scalars. Hashes are denoted by the % sigil.
A word on context: When getting a value/element from a hash/array, you have to change the sigil to the data type you want. Usually, we only recover one value (which always is a scalar), so you write $array[$i] or $hash{$key}. This does not follow any references so
my $arrayref = [1, 2, 3];
my #array = ($arrayref);
print #array[0];
will not print 123, but ARRAY(0xABCDEF) and give you a warning.
3 Loops in Perl:
Your loop syntax is very weird! You can use C-style loops:
for (my $i = 0; $i < #array; $i++)
where #array gives the length of the array, because we have a scalar context. You could also give $i the range of all possible indices in your array:
for my $i (0 .. $#array)
where .. is the range operator (in list context) and $#array gives the highest available index of our array. We can also use a foreach-loop:
foreach my $element (#array)
Note that in Perl, the keywords for and foreach are interchangeable.
4 What your loop does:
foreach my $address(#toarraya) {
print qq~ $address[$arrcnt]<br /> ~:
$arrcnt++;
}
Here you put each element of #toarraya into the scalar $address. Then you try to use it as an array (wrong!) and get the index $arrcnt out of it. This does not work; I hope your program died.
You can use every loop type given above (you don't need to count manually), but the standard foreach loop will suit you best:
foreach my $address (#toarraya){
print "$address<br/>\n";
}
A note on quoting syntax: while qq~ quoted ~ is absolutely legal, this is the most obfuscated code I have seen today. The standard quote " would suffice, and when using qq, try to use some sort of parenthesis (({[<|) as delimiter.
5 complete code:
I assume you wanted to write this:
my #addressList = split /\r?\n/, CGI::param('toarray');
foreach my $address (#addressList) {
# eliminate white spaces
$address =~ s/\s//g;
# Test for validity
unless (Email::Valid->address($address)) {
# complain, die, you decide
# I recommend:
print "<strong>Invalid address »$address«</strong><br/>";
next;
}
print "$address<br/>\n";
# send that email
}
And never forget to use strict; use warnings; and possibly use utf8.

Why is $#a one less than the actual number of elements in an array?

Does anyone knows why $#a one less than the actually number of elements in an array?
$ perl -we '#a=(1,2);print $#a'
1
That's the index of the last item and since arrays start at zero (unless you're messing around with things that are better left alone), $#a is one less than the length of the array.
I would imagine that is because it is the index of the last element in the array. Since the array indexing begins at 0, you need to add one to get the total number of elements in an array.
NB: You can also do this to find the count of an array:
#arr = ("one", "two");
$count = #arr;
print $count;
Array #a = ("a","b","c");
Value of $#a = index of last element(=2).
$a[0] = "a";
$a[1] = "b";
$a[2] = "c";
if you want to get number of elements in the array, you can assign the array to a scalar
like
$arrayLength = #a; #(=3)
Hope this helps you
$#array is used to find the index of the last element in the array.
In the example above it is position 1 - as indexes in arrays start at 0 in perl.
Everyone is telling you what it returns (which you already know), but you asked why it returns what it does.
Unfortunately, it's not really possible to answer why it returns what it does. Maybe because it makes the following easier?
for (0..$#a) {
...
}

Resources