Perl, concatenation of 0s - perl

I have the following code:
foreach $string (#strings) {
if ($hash{$string}) {
$letter = $hash{$string};
$text .= $letter;
}
}
where $letter is one character or one number (for example: a, e, o, 2, 5...). It creates a fine text, but I have problems with appending the number 0.
If $letter = 0 it is not concatenated, giving for example 28 instead of 2008. However, if $letter at that moment is 1 or aaaa, it gives 2118 or 2aaaaaaaa8.
I was thinking that it might be something related to 0 and FALSE in Perl... might be?
Many thanks.

You test to see if the entry exists using if ($hash{$string}) but that also checks that the value is true. Use if (defined $hash{$string}) instead.

I was thinking that it might be something related to 0 and FALSE in Perl... might be?
Yes, that is the issue.
The correct way to check whether a hash key is defined as follows
foreach $string (#strings)
{
if (defined $hash{$string}) # Check if key is defined
{
$letter = $hash{$string};
$text .= $letter;
}
}

A value of 0 is defined but false. Check definedness rather than truth:
if (defined $hash{$string}) {
or using the defined-or operator:
$text .= $hash{$string} // '';

Related

I need to search for a value in a perl array and if I find a match execute some code

This is sort of what I am wanting to do. At present mm returns nothing, while searchname returns the expected value.
This is a perl script embedded in a web page.
I have tried numerous approaches to this code but nothing seems to provide the results I desire. I think it is just a case of syntax.
# search for an item
if ($modtype eq "search") {
$searchname=$modname;
print "Value of searchname $searchname\n";
my #mm = grep{$searchname} #names;
print "Value of mm #mm\n";
if ($mm eq $searchname) {
print "$searchname found!\n";
}
else {
print "$searchname not Found\n";
}
}
my #mm = grep { $_ eq $searchname } #names;
if (#mm) {
print "found\n";
}
grep takes a boolean expression, not just a variable. In that expression, $_ refers to the current list element. By using an equality comparison we get (in #mm) all elements of #names that are equal to $searchname, if any.
To check whether an array is empty, you can simply use it in boolean context, as in if (#mm).
If you don't care about the found elements themselves, just whether there are any, you can use grep in scalar context:
my $count = grep { $_ eq $searchname } #names;
if ($count > 0) {
print "found $count results\n";
}
This will give you the number of matching elements.
If you don't need to know that number, just whether there was any result at all, you can use any from List::Util:
use List::Util qw(any);
if (any { $_ eq $searchname } #names) {
...
}
If #names is big, this is potentially more efficient because it can stop after the first match is found.
I'm not sure what $mm refers to in your code. Did you start your code with use strict; use warnings;? If not, you should.
Looks like you misunderstand a couple of things.
my #mm = grep{$searchname} #names;
The grep() function takes two arguments. A block of code ({ $searchname }) and a list of values (#names). For each value in the list, it puts the value into $_ and executes the code block. If the code block returns a true value then the contents of $_ is added to the output list.
Your block of code ignores $_ and just checks for the value of $searchname. That is very likely to always be true, so all of the values from #names get copied into #mm.
I think it's more likely that you want:
my #mm = grep{ $_ eq $searchname } #names;
Secondly, you suddenly start using a new variable called $mm. I suspect you're getting confused between #mm and $mm which are completely different variables with no connection with each other.
I think what you're actually trying to do is to look at the first element of #mm so you want:
if ($mm[0] eq $searchname)
But, given that values only end up in #mm if they are equal to $searchname (because that's what your grep() does), I think you really just want to check whether or not anything ended up in #mm. So you should use:
if (#mm)
Which is, in my opinion, easier to understand.

Nested Loops in Perl - Failing logic

I'm trying to work out a bigger problem, but have simplified the issue for readability, ultimately the logic below is the reason the extended program is failing.
I am using Perl to search for a short sequence of letters within a larger sequence (protein sequences), and if it is not found, then I'd like to do some calculations. I don't know whether I'm going crazy, but I can't work out why this logic is failing.
sub calculateEpitopeMutations {
my #mutationArray;
my #epitopeArray;
my $count;
my $localEpitope;
open( EPITOPESIN2, $ARGV[5] ) or die "Unable to open file $ARGV[5]\n";
while ( my $line = <EPITOPESIN2> ) {
chomp $line;
push #epitopeArray, $line;
}
while ( my ( $key, $value ) = each our %sequencesForCalculation ) {
foreach ( #epitopeArray ) {
$localEpitope = $_;
if ( $value =~ /($localEpitope)/g ) {
print "$key\n$localEpitope\nexactly the same\n\n";
next;
}
else {
#This is where I'd like to do the further calculations
print "$key\n$localEpitope\nthere is a difference\n\n";
next;
}
}
}
}
$ARGV[5] is the name of a text file containing a list of 9-character sequences, exactly like the following
RVSENIQRF
SFQVDCFLW
The idea is to put these into array #epitopeArray and iterate through these, and compare them with all (currently just one) $value sequences in the hash %sequencesForCalculation.
%sequencesForCalculation is a hash, where $value is a long sequence of characters, like this
MDSNTMSSFQVDCFLWHIRKRFADNGLGDAPFLDRLRRDQKSLKGRGNTLGLDIETATLVGKQIVEWILKEESSETLRMTIASVPTSRYLSDMTLEEMSRDWFMLMPRQKKIGPLCVRLDQAVMEKNIVLKANFSVIFNRLETLILLRAFTEEEAIVGEISPLPSLPGHTYEDVKNAVGVLIGGLEWNGNTVRVSENIQRFAWRNCDENGRPSLPPEQK
Currently, the small 9-character long sequence $localEpitope is contained in the longer sequence $value so when I iterate through the program, I should get this printed every time.
($key contains a header of information about the protein sequences, but is irrelevant so I have shortened it to just the variable name.)
$key
RVSENIQRF
Exactly the same
$key
SFQVDCFLW
Exactly the same
$key
But instead I'm getting this
$key
RVSENIQRF
exactly the same
$key
SFQVDCFLW
there is a difference
$key
Any ideas? Please let me know if anything further is required.
Update
TL;DR: You should change $value =~ /($localEpitope)/g to $value =~ /$localEpitope/
Okay now that we know the real circumstances, the problem (as melpomene points out in his comment) is that you have the /g modifier on your pattern match. There's no reason for that; you don't want check how many times the substring appears, you just want to know whether it's there at all
The problem is that variables subjected to a /g pattern search keep a state that says where the last search ended. So you're searching for $epitopeArray[0] in the longer string and finding it, and then searching for $epitopeArray[1] from where the previous search terminated. The first substring appears after the second one, so only the first is found
For more information on this behaviour, take a look at the pos function which returns the current value of this state. For instance pos($value) will return the character offset where the next m//g will start its search
This short program demonstrates the problem. With the /g modifier only BBB is found. Remove it and both are found
use strict;
use warnings;
use 5.010;
my $long_s = 'xxxAAAxxxBBBxxx';
for my $substr ( qw/ BBB AAA / ) {
if ( $long_s =~ /$substr/g ) {
say "$substr okay";
}
else {
say "$substr nope";
}
}
output
BBB okay
AAA nope
Original
You say
Currently, the small 9-character long sequence ($localEpitope) IS contained in the longer sequence ($value), and so when I iterate through the program, I should get the following printed everytime
So $localEpitope is a substring of $value and you're saying that
$value =~ /($localEpitope)/g
evaluates to true
That is correct behaviour. $value =~ /$localEpitope/ will check whether $localEpitope can be found anywhere in $value
Unfortunately it's not clear enough from what you've written to suggest a solution

If-statement condtion reduction

Suppose it is:
...
use Config::Properties::Simple;
...
my $p = Config::Properties::Simple->new( file => $propfile );
...
$str = $p->getProperty('prop');
...
.
Does
...
if ( defined $str and $str ne "" ) { #1
...
equal to
...
if ($str) { #2
...
?
If not, is there a way to simplify the #1 marked statement?
No, they're not the same if $str is "0".
You can simplify the statement by just checking the length:
if (length $str) { ...
In recent versions of Perl, length(undef) is undef without any warning generated. And using undef as a boolean doesn't generate a warning either.
(By "recent" I mean 5.12 and up. Previously, length(undef) would produce "Use of uninitialized value in length" if you have warnings turned on, which you should.)
No. It's different for $str=0; and $str="0"; for starters.
Maybe. Depends on what values $str can have, what you are checking for and what version of Perl you want to support. Possibilities:
if ($str)
if (length($str))
if (defined($str))

Is there a way to do a "reverse" .=?

I want to add 0 to the front of $num:
$num = "0".$num;
This works fine, but is there a way to do it with $num.= plus some sort of operator or something to tell it to concatenate in the opposite way? Or any other way to do it, in general?
No. Assignment operators don't work that way.
$a .= "foo";
...always means something similar to
$a = $a . "foo";
There is no operator for "concatenate these strings and reverse the order", and so there is no corresponding assignment operator.
Insert 0 in front of $num,
$num =~ s|^|0|;
There is no prepend operator, but you can do this.
Assume $num is not defined yet.
my $num = '0';
$num .= expr-that-generates-the-number;
So you're effectively prepending 0 to the variable without having to type the variable twice.
If your concern is to do this frequently, you could dump the string elements into an array and then reverse it? But you won't necessarily be reducing the amount of code, but may be easier to keep track of everything.
$array = array();
array_push ( $array , "test" );
array_push ( $array , 0 );
$array = array_reverse( $array );
echo implode( "" , $array );

Perl need the right grep operator to match value of variable

I want to see if I have repeated items in my array, there are over 16.000 so will automate it
There may be other ways but I started with this and, well, would like to finish it unless there is a straightforward command. What I am doing is shifting and pushing from one array into another and this way, check the destination array to see if it is "in array" (like there is such a command in PHP).
So, I got this sub routine and it works with literals, but it doesn't with variables. It is because of the 'eq' or whatever I should need. The 'sourcefile' will contain one or more of the words of the destination array.
// Here I just fetch my file
$listamails = <STDIN>;
# Remove the newlines filename
chomp $listamails;
# open the file, or exit
unless ( open(MAILS, $listamails) ) {
print "Cannot open file \"$listamails\"\n\n";
exit;
}
# Read the list of mails from the file, and store it
# into the array variable #sourcefile
#sourcefile = <MAILS>;
# Close the handle - we've read all the data into #sourcefile now.
close MAILS;
my #destination = ('hi', 'bye');
sub in_array
{
my ($destination,$search_for) = #_;
return grep {$search_for eq $_} #$destination;
}
for($i = 0; $i <=100; $i ++)
{
$elemento = shift #sourcefile;
if(in_array(\#destination, $elemento))
{
print "it is";
}
else
{
print "it aint there";
}
}
Well, if instead of including the $elemento in there I put a 'hi' it does work and also I have printed the value of $elemento which is also 'hi', but when I put the variable, it does not work, and that is because of the 'eq', but I don't know what else to put. If I put == it complains that 'hi' is not a numeric value.
When you want distinct values think hash.
my %seen;
#seen{ #array } = ();
if (keys %seen == #array) {
print "\#array has no duplicate values\n";
}
It's not clear what you want. If your first sentence is the only one that matters ("I want to see if I have repeated items in my array"), then you could use:
my %seen;
if (grep ++$seen{$_} >= 2, #array) {
say "Has duplicates";
}
You said you have a large array, so it might be faster to stop as soon as you find a duplicate.
my %seen;
for (#array) {
if (++$seen{$_} == 2) {
say "Has duplicates";
last;
}
}
By the way, when looking for duplicates in a large number of items, it's much faster to use a strategy based on sorting. After sorting the items, all duplicates will be right next to each other, so to tell if something is a duplicate, all you have to do is compare it with the previous one:
#sorted = sort #sourcefile;
for (my $i = 1; $i < #sorted; ++$i) { # Start at 1 because we'll check the previous one
print "$sorted[$i] is a duplicate!\n" if $sorted[$i] eq $sorted[$i - 1];
}
This will print multiple dupe messages if there are multiple dupes, but you can clean it up.
As eugene y said, hashes are definitely the way to go here. Here's a direct translation of the code you posted to a hash-based method (with a little more Perlishness added along the way):
my #destination = ('hi', 'bye');
my %in_array = map { $_ => 1 } #destination;
for my $i (0 .. 100) {
$elemento = shift #sourcefile;
if(exists $in_array{$elemento})
{
print "it is";
}
else
{
print "it aint there";
}
}
Also, if you mean to check all elements of #sourcefile (as opposed to testing the first 101 elements) against #destination, you should replace the for line with
while (#sourcefile) {
Also also, don't forget to chomp any values read from a file! Lines read from a file have a linebreak at the end of them (the \r\n or \n mentioned in comments on the initial question), which will cause both eq and hash lookups to report that otherwise-matching values are different. This is, most likely, the reason why your code is failing to work correctly in the first place and changing to use sort or hashes won't fix that. First chomp your input to make it work, then use sort or hashes to make it efficient.