'ne' is not working in do-while loop, whereas '!=' works - perl

I'm not sure what mistake I'm making, but I just changed ne to != and it worked.
This is a simple program to let the user guess a number until they hit a target number.
#!/usr/bin/perl
my $guess = 1;
do {
$guess = <STDIN>;
} while ( $guess != 12 ) ; # it doesn't work if i replace != with ne
say "you guessed ", $guess;

Perl's ne is the string not-equal operator, so $guess and 12 are treated as strings.
A string obtained via <> contains a newline character at the end, so it is not equal to the string '12'.
!= is the numeric not-equal operator, so both operands are treated as numbers. In this case Perl will ignore any trailing non-numeric characters when making the conversion, so the newline is ignored and the string 12<newline> is treated as numeric 12.
Were you to chomp the obtained value before comparison, the ne operator would also work.

Related

Substitution on string in perl changes string to an integer value

I am trying to do delete some characters matching a regex in perl and when I do that it returns an integer value.
I have tried substituting multiple spaces in a string with empty string or basically deleting the space.
#! /usr/intel/bin/perl
my $line = "foo/\\bar car";
print "$line\n";
#$line = ~s/(\\|(\s)+)+//; <--Ultimately need this, where backslash and space needs to be deleted. Tried this, returns integer value
$line = ~s/\s+//; <-- tried this, returns integer value
print "$line\n";
Expected results:
First print: foo/\bar car
Second print: foo/barcar
Actual result:
First print: foo/\\bar car
Second print: 18913234908
The proper solution is
$line =~ s/[\s\\]+//g;
Note:
g flag to substitute all occurrences
no space between = and ~
=~ is a single operator, binding the substitution operator s to the target variable $line.
Inserting a space (as in your code) means s binds to the default target, $_, because there is no explicit target, and then the return value (which is the number of substitutions made) has all its bits inverted (unary ~ is bitwise complement) and is assigned to $line.
In other words,
$line = ~ s/...//
parses as
$line = ~(s/...//)
which is equivalent to
$line = ~($_ =~ s/...//)
If you had enabled use warnings, you would've gotten the following message:
Use of uninitialized value $_ in substitution (s///) at prog.pl line 6.
You've already accepted an answer, but I thought it would be useful to give you a few more details.
As you now know,
$line = ~s/\s+//;
is completely different to:
$line =~ s/\s+//;
You wanted the second, but you typed the first. So what did you end up with?
~ is "bitwise negation operator". That is, it converts its argument to a binary number and then bit-flips that number - all the zeroes become ones and all the ones become zeros.
So you're asking for the bitwise negation of s/\s+//. Which means the bitwise negation works on the value returned by s/\s+//. And the value returned by a substitution is the number of substitutions made.
We can now work out all of the details.
s/\s+// carries out your substitution and returns the number of substitutions made (an integer).
~s/\s+// returns the bitwise negation of the integer returned by the substitution (which is also an integer).
$line = ~s/\s+// takes that second integer and assigns it to the variable $line.
Probably, the first step returns 1 (you don't use /g on your s/.../.../, so only one substitution will be made). It's easy enough to get the bitwise negation of 1.
$ perl -E'say ~1'
18446744073709551614
So that might well be the integer that you're seeing (although it might be different on a 32-bit system).

Validating phone number using perl

I want to validate phone number with following conditions:
length should be 10 digits
should start with 7 or 8 or 9
If it does not meet these requirements, we should fail the number.
I have tried the following:
print "Enter phone number: \n";
$name=<>;
chomp $name;
if (length($name)==10 && $name==~ m{/[^7-9]/}){
print "$name is valid \n";
}
else {
print "$name is not valid \n";
}
It might be worth explaining what was wrong with your original version. You have two checks. The first one (length($name)==10) is fine. The problems are with the second one.
$name==~ m{/[^7-9]/}
There are three problems here. Firstly, you're using the wrong operator. You know you need to bind your variable ($name) to your match operator, but the binding operator is =~ not ==~. But, unfortunately, your operator isn't wrong enough to throw a syntax error. Perl will interpret it as a numeric comparison (==) followed by a bitwise negation (~). That's certainly not what you want!
Your second problem is with the match operator. It looks like you know that the match operator is m/.../ and that you also know you can choose an alternative delimiter for the match operator - you've picked m{...}. But you shouldn't nest those delimiters. When you use m{/.../} you're are looking for two literal characters / in your string.
Finally, there's a problem with your actual regex. You want the string to start with 7, 8 or 9. Putting those digits in a character class ([7-9]) is a good idea. But you shouldn't also place the start of string anchor (^) inside the character class. At the start of a character class, ^ has a different meaning - it means "not one of the characters listed here". So your character class ends up meaning the exact opposite of what you wanted it to mean.
Your match expression should have looked like this:
$name =~ m{^[7-9]}
Making your complete code:
print "Enter phone number: \n";
$name = <>;
chomp $name;
if (length($name) == 10 and $name =~ m{^[7-9]}) {
print "$name is valid \n";
}
else {
print "$name is not valid \n";
}
(I did a little tidying - adding some whitespace around operators and switching && for and as is has lower precedence. You might also consider revising your variable name. $name is not a great name for a variable that contains a phone number!)
I would just use a single regex here:
^[789][0-9]{9}$
This avoids having to spread your validation logic across a few places.
if ($name =~ m{/^[789][0-9]{9}$/}){
print "$name is valid \n";
}
else {
print "$name is not valid \n";
}

Perl simple comparison == vs eq

On the accepted answer for
String compare in Perl with "eq" vs "=="
it says that First, eq is for comparing strings; == is for comparing numbers.
"== does a numeric comparison: it converts both arguments to a number and then compares them."
"eq does a string comparison: the two arguments must match lexically (case-sensitive)"
You can ONLY use eq for comparing strings but
both eq AND == works for comparing numbers
numbers are subset of strings so i just dont understand why you would ever use ==
Is there a reason why you would want to use == for comparing numeric values over just using eq for all?
Here is an example of why you might want ==:
$a = "3.0";
print "eq" if $a eq "3"; # this will not print
print "==" if $a == 3; # this will print
3.0 is numerically equal to 3, so if you want them to be equal, use ==. If you want to do string comparisons, then "3.0" is not equal to "3", so in this case you would use eq. Finally, == is a cheaper operation than eq.
String comparisons are just plain different, especially with numbers.
#s_num=sort {$a <=> $b} (20,100,3); # uses explicit numeric comparison
print "#s_num\n"; # prints 3 20 100, like we expect
#s_char=sort (20,100,3); # uses implicit string comparison
print "#s_char\n"; # prints 100 20 3, not so good.
-Tom Williams

Warnings on equality operators

Has something changed in Perl or has it always been this way, that examples like the second ($number eq 'a') don't throw a warning?
#!/usr/bin/env perl
use warnings;
use 5.12.0;
my $string = 'l';
if ($string == 0) {};
my $number = 1;
if ($number eq 'a') {};
# Argument "l" isn't numeric in numeric eq (==) at ./perl.pl line 6.
Perl will be try to convert a scalar to the type required by the context where it is used.
There is a valid conversion from any scalar type to a string, so this is always done silently.
Conversion to a number is also done silently if the string passes a looks_like_number test (accessible through Scalar::Util). Otherwise a warning is raised and a 'best guess' approximation is done anyway.
my $string = '9';
if ( $string == 9 ) { print "YES" };
Converts the string silently to integer 9, the test succeeds and YES is printed.
my $string = '9,8';
if ( $string == 9 ) { print "YES" };
Raises the warning Argument "9,8" isn't numeric in numeric eq (==), converts the string to integer 9, the test succeeds and YES is printed.
To my knowledge it has always been this way, at least since v5.0.
It has been that way.
In the first if, l is considered to be in numeric context. However, l cannot be converted to a number. Therefore, a warning is emitted.
In the second if, the number 1 is considered to be in string context. Therefore the number 1 is converted to the string '1' before comparison and hence no warnings are emitted.
Did you use a lowercase "L" on purpose? It's often hard to tell the difference between a lowercase "L" and one. You would have answered your own question if you had used a one instead.
>perl -wE"say '1' == 0;"
>perl -wE"say 1 eq 'a';"
>
As you can see,
If one needs a number, Perl will convert a string to a number without warning.
If one needs a string, Perl will convert a number to a string without warning.
Very consistent.
You get a warning when you try to convert a lowercase L to a number, but how is that surprising?

Unexpected result for IF statement: "string" <= 72 is true?

In my code below, when I enter in some non-numeric letters at the input (ie. $temp), it responds with "Too cold!" instead of "invalid". What am I missing?
#!/usr/bin/perl
print "What is the temperature outside? ";
$temp=<>;
if ($temp > 72) {
print "Too hot!\n"; }
elsif ($temp <= 72) {
print "Too cold!\n"; }
else {
print "Temperature $temp is invalid.\n"; }
This is because it will be treated as 0 if it cannot be converted into a number. You should check before if the response has only numbers, or restrict the input in any other way so that only a valid number can be entered. Something along the lines:
print "invalid" if ($temp =~ /\D/);
(prints invalid if $temp contains any non-digit character. Note that this may invalidate "+" and "-", but you get the idea).
The numerical comparison operators expect their arguments to be numbers. If you try to compare a string like 'foo' using a numerical comparison, it will be converted silently to the number 0, which is less than 72.
If you had warnings turned on, you would have been told what was going on.
friedo$ perl -Mwarnings -E 'say "foo" < 72'
Argument "foo" isn't numeric in numeric lt (<) at -e line 1.
1
This is why you should always begin your programs with
use strict;
use warnings;
Casting an invalid numerical string to a number results in 0, therefor you could use something as the below to see if the input was indeed valid or not.
print "What is the temperature outside? ";
$temp=<>;
if ($temp == 0 && $temp ne '0') {
print "Temperature $temp is invalid.\n"; }
elsif ($temp > 72) {
print "Too hot!\n"; }
elsif ($temp <= 72) {
print "Too cold!\n"; }
Explanation: If the input string was casted into 0 (zero) though the string itself isn't equal to '0' (zero) the input is not numeric, hence; invalid.
You could also check to see if the input only consists of [0-9.] by using a regular expression, that would ensure that it's a valid number (also remember that numbers do not start with 0 (zero) and then have digits that follow, unless you are writing in octal.
Note: Remember to trim the input string from white spaces before the above check.
For precisely this reason (and many others), you're MUCH better off if you enable "use warnings":
#!/usr/bin/perl
use strict;
use warnings;
...
Try it after removing the trailing newline, which is probably what's causing Perl to treat it as a string rather than a number:
chomp( my $test = <> );