Why does concatenating arrays in Perl produce numbers? - arrays

I just tried to concatenate arrays in Perl with the + operator and got strange results:
perl -wE 'say([1, 2, 3] + [4, 5, 6])'
73464360
Doing the same with hashes seems to be a syntax error:
perl -wE 'say({} + {})'
syntax error at -e line 1, near "} +"
Execution of -e aborted due to compilation errors.
What is the result of the 1st expression? Is it documented anywhere?

It is from the numification of references, which produces the memory address of the reference.
perl -E 'say \#a; say 0+\#a; printf "%x\n",0+\#a'
Typical output (though it may change every time you run the program)
ARRAY(0x1470518)
21431576
1470518 <--- same number as in first line
Your hash reference example almost works, but it seems that Perl is parsing the first set of {} blocks as a code block rather than as a hash reference. If you use a unary + and force Perl to treat it as a hash reference, it will work. I mean "work".
perl -E 'say(+{} + {})'
40007168

Because + in Perl is an arithmetic operator only. It forces its arguments to be interpreted as numbers, no matter what. That's why Perl has a separate operator for string concatenation (.).
What you are doing, effectively, is adding the addresses where the arrays are stored.
Array concatenation is accomplished by simply listing the arrays, one after the other. However, if you are using references to arrays ([...]), then you have to dereference them first with #{...}:
perl -wE 'say( #{[1,2,3]}, #{[4,5,6]} )'
But normally you would use array variables and not need the extra syntax.
perl -wE 'my #a = (1,2,3); my #b = (4,5,6); say join("-",#a,#b)'
#=> 1-2-3-4-5-6
The same thing goes for hashes; my %c = (%a,%b); will combine the contents of %a and %b (in that order, so that %b's value for any common keys will overwrite %a's) into the new hash %c. You could use my $c = { %$a, %$b }; to do the same thing with references. One gotcha, which you are running into in your + attempt, is that {} may be interpreted as an empty block of code instead of an empty hash.

Related

Why is Perl giving "Can't modify string in scalar output" error?

I'm pretty new to Perl and this is my most complex project yet. Apologies if any parts of my explanation don't make sense or I miss something out - I'll be happy to provide further clarification. It's only one line of code that's causing me an issue.
The Aim:
I have a text file that contains a single column of data. It reads like this:
0
a,a,b,a
b,b,b,a
1
a,b,b,a
b,b,b,a
It continues like this with a number in ascending order up to 15, and the following two lines after each number are a combination of four a's or b's separated by commas. I have tied this file to an array #diplo so I can specify specific lines of it.
I also have got a file that contains two columns of data with headers that I have converted into a hash of arrays (with each of the two columns being an array). The name of the hash is $lookup and the array names are the names of the headings. The actual arrays only start from the first value in each column that isn't a heading. This file looks like this:
haplo frequency
"|5,a,b,a,a|" 0.202493719
"|2,b,b,b,a|" 0.161139191
"|3,b,b,b,a|" 0.132602458
This file contains all of the possible combinations of a or b at the four positions combined with all numbers 0-14 and their associated frequencies. In other words, it includes all possible combinations from "|0,a,a,a,a|" followed be "|1,a,a,a,a|" through to "|13,b,b,b,b|" and "|14,b,b,b,b|".
I want my Perl code to go through each of the combinations of letters in #diplo starting with a,a,b,a and record the frequency associated with the row of the haplo array containing each number from 0-14, e.g. first recording the frequency associated with "|0,a,a,b,a|" then "|1,a,a,b,a|" etc.
The output would hopefully look like this:
0 #this is the number in the #diplo file and they increase in order from 0 up to 15
0.011 0.0023 0.003 0.0532 0.163 0.3421 0.128 0.0972 0.0869 0.05514 0.0219 0.0172 0.00824 0.00886 0.00196 #these are the frequencies associated with x,a,a,b,a where x is any number from 0 to 14.
My code:
And here is the Perl code I created to hopefully sort this out (there is more to create the arrays and such which I can post if required, but I didn't want to post a load of code if it isn't necessary):
my $irow = 1; #this is the row/element number in #diplo
my $lrow = 0; #this is the row/element in $lookup{'haplo'}
my $copynumber = 0;
#print "$copynumber, $diplo[2]";
while ($irow < $diplolines - 1) {
while ($copynumber < 15) {
while ($lrow < $uplines - 1) {
if ("|$copynumber,$diplo[$irow]|" = $lookup{'haplo'}[$lrow]) { ##this is the only line that causes errors
if ($copynumber == 0) {
print "$diplo[$irow-1]\n";
#print "$lookup{'frequency'}[$lrow]\t";
}
print "$lookup{'frequency'}[$lrow]\t";
}
$lrow = $lrow + 1;
}
$lrow = 0;
$copynumber = $copynumber + 1;
}
$lrow = 0;
$copynumber = 0;
$irow = $irow + 1;
}
However, the line if ("|$copynumber,$diplo[$irow]|" = $lookup{'haplo'}[$lrow]) is causing an error Can't modify string in scalar assignment near "]) ".
I have tried adding in speech marks, rounded brackets and apostrophes around various elements in this line but I still get some sort of variant on this error. I'm not sure how to get around this error.
Apologies for the long question, any help would be appreciated.
EDIT: Thanks for the suggestions regarding eq, it gets rid of the error and I now know a bit more about Perl than I did. However, even though I don't get an error now, if I put anything inside the if loop for this line, e.g. printing a number, it doesn't get executed. If I put the same command within the while loop but outside of the if, it does get executed. I have strict and warnings on. Any ideas?
= is assignment, == is numerical comparison, eq is string comparison.
You can't modify a string:
$ perl -e 'use strict; use warnings; my $foo="def";
if ("abc$foo" = "abcdef") { print "match\n"; } '
Found = in conditional, should be == at -e line 1.
Can't modify string in scalar assignment at -e line 1, near ""abcdef") "
Execution of -e aborted due to compilation errors.
Nonnumerical strings act like zeroes in a numerical comparison:
$ perl -e 'use strict; use warnings; my $foo="def";
if ("abc$foo" == 0) { print "match\n"; } '
Argument "abcdef" isn't numeric in numeric eq (==) at -e line 1.
match
A string comparison is probably what you want:
$ perl -e 'use strict; use warnings; my $foo="def";
if ("abc$foo" eq "abcdef") { print "match\n"; } '
match
This is the problematic expression:
"|$copynumber,$diplo[$irow]|" = $lookup{'haplo'}[$lrow]
The equals sign (=) is an assignment operator. It assigns the value on its right-hand side to the variable on its left-hand side. Therefore, the left-hand operand needs to be a variable, not a string as you have here.
I don't think you want to do an assignment here at all. I think you're trying to check for equality. So don't use an assignment operator, use a comparison operator.
Perl has two equality comparison operators. == does a numeric comparison to see if its operands are equal and eq does a string comparison. Why does Perl need two operators? Well Perl converts automatically between strings and numbers so it can't possibly know what kind of comparison you want to do. So you need to tell it.
What's the difference between the two types of comparison? Well, consider this code.
$x = '0';
$y = '0.0';
Are $x and $y equal? Well it depends on the kind of comparison you do. If you compare them as numbers then, yes, they are the same value (zero is the same thing whether it's an integer or a real number). But if you compare them as strings, they are different (they're not the same length for a start).
So we now know the following
$x == $y # this is true as it's a numeric comparison
$x eq $y # this is false as it's a string comparison
So let's go back to your code:
"|$copynumber,$diplo[$irow]|" = $lookup{'haplo'}[$lrow]
I guess you started with == here.
"|$copynumber,$diplo[$irow]|" == $lookup{'haplo'}[$lrow]
But that's not right as |$copynumber,$diplo[$irow]| is clearly as string, not a number. And Perl will give you a warning if you try to do a numeric comparison using a value that doesn't look like a number.
So you changed it to = and that doesn't work either as you've now changed it to an assignment.
What you really need is a string comparison:
"|$copynumber,$diplo[$irow]|" eq $lookup{'haplo'}[$lrow]

Splitting a string by colon

I am trying to split the string by ':' and store it in an array, so something that looks like a:b:c:d:x:y:z will be stored in an array which holds, a, b, c, d, x, y, z as elements.
What I have written is
IFS = ':' read - r -a ARR <<< "$INFO"
where INFO is a string which is being read in from a file containing multiple strings in the aforementioned format.
I get an error saying "IFS: command not found".
I am reading them in this way:
while read INFO
Lastly when I try to assign the first element in the array to a variable, I am getting an error:
export NAME = $INFO[0]
the two errors I get here are export: '=' not a valid identifier and export: '[0]: not a valid identifier
I am relatively a newcomer to bash.
The basic problem here is that your code contains spaces in places where they aren't allowed. For instance, the following is perfectly fine syntax (though it fails to comply with POSIX conventions on variable naming, which advises lowercase characters be used for application-defined names):
info_string='a:b:c:d:x:y:z'
IFS=: read -r -a info_array <<< "$info_string"
Similarly, on a dereference, you need curly braces, and (again) can't put spaces around the =:
name=${info_array[0]}
This works:
s=a:b:c:d #sample string
IFS=:
a=( $s ) #array
printf "'%s' " "${a[#]}" #prints 'a' 'b' 'c' 'd'
The syntax to get the n-th item in an array is
${array_name[$index]}
(The curlies are required), so you need export NAME="${INFO[0]}" (assignments normally don't need to be quoted, however with export, declare, local, and similar, it's better to quote).
https://www.lukeshu.com/blog/bash-arrays.html is a good tutorial on how bash arrays work.

BASH Changing Array element into Variable

I'm trying to create variables from array elements and check value of my created variable in sourcefile.
MySourceFile:
value1abc="10"
value2efg="30"
value3ade="50"
value4tew="something else"
onemorevalue="192.168.1.0"
Script Code :
declare -a CfgData
CfgData=("value1abc" "value2efg" "value3ade" "value4tew" "othervalue" "onemorevalue")
COUNTER="0"
source MySourceFile
until [ $COUNTER == ${#CfgData[#]} ]; do
value=$[${CfgData[$COUNTER]}]
echo -ne "\\n${CfgData[$COUNTER]}=\"$value\""\\n\\n
let COUNTER+=1
done
Script works fine until it comes to values which contain data other than pure numbers (letters, spaces, dots, generally all characters) in which case :
value="randomcharacters" # Gives me "0"
value="something else" & value="192.168.1.0" # line XX: 192.168.1.0: syntax error: invalid arithmetic operator (error token is ".168.1.0")
I'm pretty sure I'm missing something elementary but I cannot figure it out now :P.
First, the $[...] bash syntax is obsolete and should not be used.
Now, it evaluates its content as an arithmetic expression. When you put in a string, is is interpreted as a variable, and its value again evaluated as an arithmetic expression. When a variable is unset, it evaluates to zero.
If you put a string with a single word, these rules make it expand to zero. If you put in a string with multiple words, the expansion fails, because the words are interpreted as variables, with no intermediate arithmetic operator, which is an error. This is what you see.
What you probably want, is replace the line value=$[${CfgData[$COUNTER]}] with value=${!CfgData[$COUNTER]}.
Let me add that your script would probably be better written without the indices, like
for varname in "${CfgData[#]}"; do
value=${!varname}
echo -ne "$varname=$value"
done

How to copy data to an array in perl?

I am trying access the data from the database and copy the data to an array. This is my code,
$sth = $dbh->prepare("SELECT * FROM abcd WHERE id=100 ");
$sth->execute;
$N=$sth->rows;
print "$N\n";
while (my #row_val = $sth->fetchrow_array()){
my ($uniqid, $time, $current, $id ) = #row_val;
$y[k]=$current;
$k++;
}
for ($k=0;$k<$N;$k++) {
print "$y[k]\t";
}
But it displays the same value for all $y[k]. How to copy the data from database to an array in perl?
You are using a bareword here:
$y[k]=$current;
# ^--- here, the "k" is a bareword
If you use warnings this will give a warning
Unquoted string "k" may clash with future reserved word at foo.pl line 10.
Argument "k" isn't numeric in array element at foo.pl line 10.
And the "k" will be interpreted as a string, will be converted to a number, which will be zero 0, so all your data is stored in $y[0].
This is why it is a very bad idea to not turn warnings on.
What you probably want instead is to push the new values onto the array:
push #y, $current;
This is, IMO, preferable to using an index, since it does all that work for you. Usually, you only want to specifically get involved with array indexes if the indexes themselves are of value for you, such as when comparing array elements.
This also means that your subsequent for loop
for ($k=0;$k<$N;$k++) {
print "$y[k]\t";
}
Is better written
for (#y) {
print "$_\t";
}
Although this is better written with join:
print join "\t", #y;
As a final note, you should always use
use strict;
use warnings;
It takes a small amount of learning to overcome the additional noise when using these pragmas, but it is well worth it in terms of learning and reducing your time spent debugging. I usually say that not using these pragmas is like covering up the low oil warning lamp in your car: Not knowing about the errors does not solve them.
This behaviour is because you are putting everything to index "k" - not any number just "k",
it is only a coincidence that its working at all :) - the "same value" is the last value - isnt it ? :)
SOLUTION:
1) variables are written with $ - keep that in mind when accessing $yourArray[$variableWithIndex]
2) $y[k]=$current; # wrong! you are trying to access "k" index
correct: $y[$k]=$current;
Didnt tested it - but this should work:
$sth = $dbh->prepare("SELECT * FROM abcd WHERE id=100 ");
$sth->execute;
$N=$sth->rows;
print "$N\n";
$k=0; # init first!
while (my #row_val = $sth->fetchrow_array()){
my ($uniqid, $time, $current, $id ) = #row_val;
$y[$k]=$current; # dont forget the $
$k++;
}
for ($k=0;$k<$N;$k++) {
print "$y[$k]\t"; # dont forget the $
}

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.

Resources