Perl - use 0 if string is empty - perl

When using arithmetic expressions in combination with an undefined or empty string, perl throws an error. Is there a clean way to use zero in case a string is empty?
This does not work:
$x = 1 + $emptyVar
In bash you could use something like this for instance to use zero if the variable $emptyVar is empty:
x=1+${emptyVar:-0}
I am looking for something similar as this

There are many ways to do this. e.g.:
$x = $emptyVar;
$x += 1;
or maybe:
$x = 1 + ($emptyVar // 0); # defined-or operator
or maybe:
{
no warnings 'uninitialized';
$x = 1 + $emptyVar;
}
Of these, I usually use the second one. If the level of perl I'm using doesn't have defined-or, then a simple or (||) works fine, too.

Related

Perl “do { … } if …” as expression

my $x = do { 3; } if 1; say $x # works
my $x = (do { 3; } if 1); say $x # syntax error
How come? If the do block is an expression, why can't it be parenthesised? If it's not, how does the first one parse?
A compound statement used for flow control (if BLOCK), as well as one with the statement modifier (used here, the postfix if), cannot appear inside parenthesis.
This restriction makes sense since such a statement may or may not return a value
if executes the statement once if and only if the condition is true.
(original emphasis)
A side note. The first example runs without warnings but it has undefined behavior, what must be avoided. From the end of the section Statement Modifiers in perlsyn
NOTE: The behaviour of a my, state, or our modified with a statement modifier conditional or loop construct (for example, my $x if ...) is undefined. The value of the my variable may be undef, any previously assigned value, or possibly anything else. Don't rely on it. Future versions of perl might do something different from the version of perl you try it out on. Here be dragons.
(original emphasis)
Any instances of this should be rewritten, and Perl::Critic has a policy for it, making it easier to find them
It's not that the do that's the problem, it's the postfix if. That postfix can't appear inside the parens:
$ perl -E 'my $x = ( 1 if 1); say $x'
syntax error at -e line 1, near "1 if"
Execution of -e aborted due to compilation errors.
Instead, you can use the conditional operator ?: with a do in one of the branches:
$ perl -E 'my $x = ( time % 2 ? do { 1 } : () ); say $x'
my $x = do { 3; } if 1;
is actually equivalent to
( my $x = do { 3; } ) if 1;
Note that you shouldn't execute my conditionally. (More precisely, you shouldn't use a my variable that hasn't been executed. Your code is technically ok since the my is always executed before $x is used.)
An expression (my $x = do { ... }) modified by a statement modifier (if 1) is a statement.
The inside of parens must be an expression, not a statement.
You can't do
( $x = 3; )
( sub f { } )
( if (f()) { g() } )
( g() if f(); )
( g() if f() )
You get the idea.
if 1 is a statement modifier. my $x = do { 3; } is a statement; do { 3; } is an expression.

Using the $# operator

I've just taken over maintenance of a piece of Perl system. The machine it used to run on is dead so I do not know which version of Perl it was using, but it was working. It included the following line to count the lines in a page of ASCII text
my $lcnt = $#{#{$page{'lines'}}};
In Perl 5.10.1 ( we are now running this on CentOS 6.3 ) the above code no longer works. I instead use the following, which works fine.
my #arr = #{$page{'lines'}};
my $lcnt = $#arr;
I'll admit my Perl isn't great but from what I can see the first version should never have worked as it is trying to deference an array rather than an array ref
First question - is my guess at why this first line of code doesn't now work correct, and secondly did it work earlier due to a now fixed bug in a prior Perl version?
Thanks!
The first version never worked. Assuming $page{'lines'} is an arrayref, this is what you want:
my $lcnt = $#{$page{'lines'}};
Note that this is going to give you one less than the number of items in your arraref. The $# operator is the INDEX of the last item, not the number of items. If you want the number of items in $page{'lines'}, you probably want this:
my $lcnt = scalar(#{$page{'lines'}});
Some things about your code. This:
my $lcnt = $#{#{$page{'lines'}}};
Was never correct. Take a look at the three things going on here
$page{'lines'} # presumably an array ref
#{ ... } # dereference into an array
$#{ ... } # get last index of an array ref
This is equivalent to (continuing on your own code):
my #arr = #{$page{'lines'}};
my $foo = #arr; # foo is now the size of the array, e.g. 3
my $lcnt = $#$foo;
If you use
use strict;
use warnings;
Which you should always do, without question (!), you will get the informative fatal error message:
Can't use string ("3") as an ARRAY ref while "strict refs" in use
(Where 3 will be the size of your array)
The correct way to get the size (number of elements) of an array is to put the array in scalar context:
my $size = #{ $page{'lines'} };
The way to get the index of the last element is using the $# sigil:
my $last_index = $#{ $page{'lines'} };
As you'll note, the syntax is the same, it is just a matter of using # or $# to get what you want, just the same as when using a regular array
my $size = #array;
my $last = $#array;
So, to refer back to the beginning: Using both # and $# is not and was never correct.

Meaning of the '+=' operator

Could someone help me to understand what the '+=' operator means in a particular situation. The script says:
$receipts{$weather} += $receipt;
$days{$weather}++;
Assuming $foo += $bar, the += operator does the following:
$foo = $foo + $bar;
That is, increments $foo by $bar. Assuming $foo++, the ++ operator does the following:
$foo = $foo + 1;
That is, increments the variable by one.
With all this said, these operators also have some hidden perl magic. For example, the += and ++ operator does not give an uninitialized warning where the corresponding statement would:
# $foo is undefined
$foo += 10; # no warning
$foo++; # no warning
$foo = $foo + 10 # Use of uninitialized value $foo in addition
The ++ operator also works on strings
my $foo = 'a';
$foo++;
print $foo; # prints 'b'
The ++ operator comes in two flavours, post increment and pre increment. The return value of the expression is either calculated before or after the incrementation:
$foo = 1;
print ++$foo; # prints 2
print $foo++; # prints 2, but $foo is now 3
It is adding the value of $receipt to the value of $receipts{$weather} and storing the result back into $receipts{$weather}. It is the equivalent of:
$receipts{$weather} = $receipts{$weather} + $receipt
However, it may be implemented more efficiently in some cases.
See perldoc perlop:
"=" is the ordinary assignment operator.
Assignment operators work as in C. That is,
$a += 2;
is equivalent to
$a = $a + 2;
Example:
this example : int i = 2; i=i+4; and this example int i = 2; i+=4 are the same;

Perl floating point number usage confusion

While using scalar values in perl, I am not able to accomplish the desired results. Need your help in figuring where I am going wrong..
Say I want to loop 9 times and print 0.1 to 0.9
I declared variable $i and using it in for loop as well as inside the loop.
for($i = 1; $i < 10; $i++)
{
$b = $ie-01; # (This where I go wrong, I am not sure If I am following correct
# syntax here, Because I see -1 getting printed instead of $i value
# which is incremented on each loop)
print "The value now is: $b\n";
}
I do know of different ways to get the desired result but I wanna know how to use exponent to get the desired output. . . . .
Why $i is treated as 0 when used in conjunction with e?
I think you only forgot to include the multiplication operator *:
$i * 1e-01
The string $ie-01 will be interpreted as $ie - 01 which is an unititialized variable (i.e. zero) minus one which will give you -1. (You can use the e-notation only with constant numbers but not with variables.)
Your first mistake was not including:
use strict;
use warnings;
This would have told you about the variable $ie not being declared.
There is no reasonable way to make ${i}e-01 work; you would have to eval it, which is not reasonable. The standard way to write it would be:
$b = $i * 0.1;

How do I tell if a variable has a numeric value in Perl?

Is there a simple way in Perl that will allow me to determine if a given variable is numeric? Something along the lines of:
if (is_number($x))
{ ... }
would be ideal. A technique that won't throw warnings when the -w switch is being used is certainly preferred.
Use Scalar::Util::looks_like_number() which uses the internal Perl C API's looks_like_number() function, which is probably the most efficient way to do this.
Note that the strings "inf" and "infinity" are treated as numbers.
Example:
#!/usr/bin/perl
use warnings;
use strict;
use Scalar::Util qw(looks_like_number);
my #exprs = qw(1 5.25 0.001 1.3e8 foo bar 1dd inf infinity);
foreach my $expr (#exprs) {
print "$expr is", looks_like_number($expr) ? '' : ' not', " a number\n";
}
Gives this output:
1 is a number
5.25 is a number
0.001 is a number
1.3e8 is a number
foo is not a number
bar is not a number
1dd is not a number
inf is a number
infinity is a number
See also:
perldoc Scalar::Util
perldoc perlapi for looks_like_number
The original question was how to tell if a variable was numeric, not if it "has a numeric value".
There are a few operators that have separate modes of operation for numeric and string operands, where "numeric" means anything that was originally a number or was ever used in a numeric context (e.g. in $x = "123"; 0+$x, before the addition, $x is a string, afterwards it is considered numeric).
One way to tell is this:
if ( length( do { no warnings "numeric"; $x & "" } ) ) {
print "$x is numeric\n";
}
If the bitwise feature is enabled, that makes & only a numeric operator and adds a separate string &. operator, you must disable it:
if ( length( do { no if $] >= 5.022, "feature", "bitwise"; no warnings "numeric"; $x & "" } ) ) {
print "$x is numeric\n";
}
(bitwise is available in perl 5.022 and above, and enabled by default if you use 5.028; or above.)
Check out the CPAN module Regexp::Common. I think it does exactly what you need and handles all the edge cases (e.g. real numbers, scientific notation, etc). e.g.
use Regexp::Common;
if ($var =~ /$RE{num}{real}/) { print q{a number}; }
Usually number validation is done with regular expressions. This code will determine if something is numeric as well as check for undefined variables as to not throw warnings:
sub is_integer {
defined $_[0] && $_[0] =~ /^[+-]?\d+$/;
}
sub is_float {
defined $_[0] && $_[0] =~ /^[+-]?\d+(\.\d+)?$/;
}
Here's some reading material you should look at.
A simple (and maybe simplistic) answer to the question is the content of $x numeric is the following:
if ($x eq $x+0) { .... }
It does a textual comparison of the original $x with the $x converted to a numeric value.
Not perfect, but you can use a regex:
sub isnumber
{
shift =~ /^-?\d+\.?\d*$/;
}
A slightly more robust regex can be found in Regexp::Common.
It sounds like you want to know if Perl thinks a variable is numeric. Here's a function that traps that warning:
sub is_number{
my $n = shift;
my $ret = 1;
$SIG{"__WARN__"} = sub {$ret = 0};
eval { my $x = $n + 1 };
return $ret
}
Another option is to turn off the warning locally:
{
no warnings "numeric"; # Ignore "isn't numeric" warning
... # Use a variable that might not be numeric
}
Note that non-numeric variables will be silently converted to 0, which is probably what you wanted anyway.
rexep not perfect... this is:
use Try::Tiny;
sub is_numeric {
my ($x) = #_;
my $numeric = 1;
try {
use warnings FATAL => qw/numeric/;
0 + $x;
}
catch {
$numeric = 0;
};
return $numeric;
}
Try this:
If (($x !~ /\D/) && ($x ne "")) { ... }
I found this interesting though
if ( $value + 0 eq $value) {
# A number
push #args, $value;
} else {
# A string
push #args, "'$value'";
}
Personally I think that the way to go is to rely on Perl's internal context to make the solution bullet-proof. A good regexp could match all the valid numeric values and none of the non-numeric ones (or vice versa), but as there is a way of employing the same logic the interpreter is using it should be safer to rely on that directly.
As I tend to run my scripts with -w, I had to combine the idea of comparing the result of "value plus zero" to the original value with the no warnings based approach of #ysth:
do {
no warnings "numeric";
if ($x + 0 ne $x) { return "not numeric"; } else { return "numeric"; }
}
You can use Regular Expressions to determine if $foo is a number (or not).
Take a look here:
How do I determine whether a scalar is a number
There is a highly upvoted accepted answer around using a library function, but it includes the caveat that "inf" and "infinity" are accepted as numbers. I see some regex stuff for answers too, but they seem to have issues. I tried my hand at writing some regex that would work better (I'm sorry it's long)...
/^0$|^[+-]?[1-9][0-9]*$|^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$|^[+-]?[0-9]?\.[0-9]+$|^[+-]?[1-9][0-9]*\.[0-9]+$/
That's really 5 patterns separated by "or"...
Zero: ^0$
It's a kind of special case. It's the only integer that can start with 0.
Integers: ^[+-]?[1-9][0-9]*$
That makes sure the first digit is 1 to 9 and allows 0 to 9 for any of the following digits.
Scientific Numbers: ^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$
Uses the same idea that the base number can't start with zero since in proper scientific notation you start with the highest significant bit (meaning the first number won't be zero). However, my pattern allows for multiple digits left of the decimal point. That's incorrect, but I've already spent too much time on this... you could replace the [1-9][0-9]* with just [0-9] to force a single digit before the decimal point and allow for zeroes.
Short Float Numbers: ^[+-]?[0-9]?\.[0-9]+$
This is like a zero integer. It's special in that it can start with 0 if there is only one digit left of the decimal point. It does overlap the next pattern though...
Long Float Numbers: ^[+-]?[1-9][0-9]*\.[0-9]+$
This handles most float numbers and allows more than one digit left of the decimal point while still enforcing that the higher number of digits can't start with 0.
The simple function...
sub is_number {
my $testVal = shift;
return $testVal =~ /^0$|^[+-]?[1-9][0-9]*$|^[+-]?[1-9][0-9]*(\.[0-9]+)?([eE]-?[1-9][0-9]*)?$|^[+-]?[0-9]?\.[0-9]+$|^[+-]?[1-9][0-9]*\.[0-9]+$/;
}
if ( defined $x && $x !~ m/\D/ ) {}
or
$x = 0 if ! $x;
if ( $x !~ m/\D/) {}
This is a slight variation on Veekay's answer but let me explain my reasoning for the change.
Performing a regex on an undefined value will cause error spew and will cause the code to exit in many if not most environments. Testing if the value is defined or setting a default case like i did in the alternative example before running the expression will, at a minimum, save your error log.