Perl Regular Expression default for non-matched - regex

Lets say I do this:
my ($a,$b,$let) = $version =~ m/^(\d+)\.(\d+)\.?([A-Za-z])?$/;
so this will match for instance: 1.3a, 1.3,...
I want to have a default value for $let if let is not available, lets say, default 0.
so for 1.3 I will get:
$a = 1
$b = 3
$let = 0
is it possible? (from the regex it self, without using additional statements)
Thanks,

This will work - updated to use bitwise or instead of ternary operator.
my ($a,$b,$let) = ($version =~ m/^(\d+)\.(\d+)\.?([A-Za-z])?$/)
&& ($1,$2,$3 || 0 );
Here is a test script
&t("1.3");
&t("1.3a");
&t("1.3.a");
sub t {
$version = shift;
my ($a,$b,$let) = ($version =~ m/^(\d+)\.(\d+)\.?([A-Za-z])?$/)
&& ($1,$2,$3 || 0 );
print "\n result $a.$b.$let";
}
Output is
result 1.3.0
result 1.3.a
result 1.3.a
original solution using ternary operator also works
my ($a,$b,$let) = ($version =~ m/^(\d+)\.(\d+)\.?([A-Za-z])?$/)
&& (defined $3 ? ($1,$2,$3) : ($1,$2,0));

$let should have a default value of undef. You can test on that if you need to.

Related

Perl conditional regex with inequalities

I have one query. I have to match 2 strings in one if condition:
$release = 5.x (Here x should be greater than or equal to 3)
$version = Rx (this x should be greater than or equal to 5 if $release is 5.3, otherwise anything is acceptable)
e.g. 5.1R11 is not acceptable, 5.3R4 is not, 5.3R5 is acceptable, and 5.4 R1 is acceptable.
I have written a code like this:
$release = "5.2";
$version = "R4";
if ( $release =~ /5.(?>=3)(\d)/ && $version =~ m/R(?>=5)(\d)/ )
{
print "OK";
}
How can I write this?
This is really a three-level version string, and I suggest that you use Perl's version facility
use strict;
use warnings 'all';
use feature 'say';
use version;
my $release = '5.2';
my $version = 'R4';
if ( $version =~ /R(\d+)/ && version->parse("$release.$1") ge v5.3.5 ) {
say 'OK';
}
In regex (?>) it means atomic grouping.
Group the element so it will stored into $1 then compare the $1 with number so it should be
if (( ($release =~ /5\.(\d)/) && ($1 > 3) ) && (($version =~ m/R(\d)/) && ($1 >= 3) ) )
{
print "OK\n";
}
I got the correct one after modifying mkhun's solution:
if ((($release =~ /5.3/)) && (($version =~ m/R(\d+)(.\d+)?/) && ($1 >= 5))
|| ((($release =~ /5.(\d)/) && ($1 > 3)) && ($version =~ m/R(\d+)(.\d+)?/)) )
{
print "OK\n";
}

Assign captured group to value, and if no match: assign string

In Perl regex, the documentation says
... in scalar context, $time =~ /(\d\d):(\d\d):(\d\d)/ returns a true
or false value. In list context, however, it returns the list of
matched values ($1,$2,$3)
But how is it that when you provide an alternative option - when no match is found - TRUE or FALSE will be assigned even when in list context?
As an example, I want to assign the matched group to a variable and if not found, use the string value ALL.
my ($var) = $string =~ /myregex/ || 'ALL';
Is this possible? And what about multiple captured groups? E.g.
my ($var1, $var2) = $string =~ /(d.t)[^d]+(d.t)/ || 'dit', 'dat';
Where if the first group isn't matched, 'dit' is used, and if no match for the second is found 'dat'.
For the first requirement, you can use the ternary operator:
my $string = 'abded';
for my $f ('a' .. 'f') {
my ($v1) = $string =~ /($f)/ ? ($1) : ('ALL') ;
say "$f : $v1";
}
Output:
a : a
b : b
c : ALL
d : d
e : e
f : ALL

Perl Ternary statement with regex as one of its results

I currently have the following code:
my $class = $rs->{'CLS_ID'};
$class =~ s/^C\S{1,3}\s+// if ($transform);
This works fine, but I was wondering if those 2 statements could be combined into a single ternary expression?
The ternary conditional operator lets you specify a test and two values.
E.g.
my $value = somecondition ? value-if-true : value-if-false;
Now, you aren't doing that with your example - you're setting a value, then running a subroutine (regex) if your condition is true.
So I'd suggest whilst you possibly could, you're subverting the notion of what a ternary operator is for. And you'd still have your assignment on both 'sides' of the expression.
E.g:
my $class = ($transform) ? $rs->{'CLS_ID'} : $rs->{'CLS_ID'} =~ s/^C\S{1,3}\s+//r;
The 'r' flag tells your regex to 'return' the modified value, without modifying the original. But I wouldn't do this, because it makes what you're doing less clear.
Note that the r modifier to regex applies to perl 5.14 onwards.
Not with a conditional operator, but I think you're looking for this:
(my $class = 'initial-val') =~ s/something// if ($transform);
NOTE: This is officially undefined behaviour, and has very weird side effects. I'll leave it here, though, as a example of what not to do.
Another way it could be accomplished (assuming $transform is either 0 or 1):
use strict;
my $rs = {'CLS_ID' => 'Cabc and this should be left'};
# True value
my $transform = 1;
my $class = (($transform.$rs->{'CLS_ID'}) =~ s/^(1C\S{1,3}\s+)|0//r);
print $class . "\n";
# False value
my $transform = 0;
my $class = (($transform.$rs->{'CLS_ID'}) =~ s/^(1C\S{1,3}\s+)|0//r);
print $class . "\n";
This prints:
and this should be left
Cabc and this should be left
But... please don't do this ;)
Conditional operator, and no variable redundancy,
my ($class) = map { $transform ? s/^C\S{1,3}\s+//r : $_ } $rs->{'CLS_ID'};
for older perl with no /r switch for s///
$transform and s/^C\S{1,3}\s+// for my $class = $rs->{'CLS_ID'};

How can I count characters in Perl?

I have the following Perl script counting the number of Fs and Ts in a string:
my $str = "GGGFFEEIIEETTGGG";
my $ft_count = 0;
$ft_count++ while($str =~ m/[FT]/g);
print "$ft_count\n";
Is there a more concise way to get the count (in other words, to combine line 2 and 3)?
my $ft_count = $str =~ tr/FT//;
See perlop.
If the REPLACEMENTLIST is empty, the
SEARCHLIST is replicated. This latter is useful for counting
characters in a class …
$cnt = $sky =~ tr/*/*/; # count the stars in $sky
$cnt = tr/0-9//; # count the digits in $_
Here's a benchmark:
use strict; use warnings;
use Benchmark qw( cmpthese );
my ($x, $y) = ("GGGFFEEIIEETTGGG" x 1000) x 2;
cmpthese -5, {
'tr' => sub {
my $cnt = $x =~ tr/FT//;
},
'm' => sub {
my $cnt = ()= $y =~ m/[FT]/g;
},
};
Rate tr m
Rate m tr
m 108/s -- -99%
tr 8118/s 7440% --
With ActiveState Perl 5.10.1.1006 on 32 Windows XP.
The difference seems to be starker with
C:\Temp> c:\opt\strawberry-5.12.1\perl\bin\perl.exe t.pl
Rate m tr
m 88.8/s -- -100%
tr 25507/s 28631% --
When the "m" operator has the /g flag AND is executed in list context, it returns a list of matching substrings. So another way to do this would be:
my #ft_matches = $str =~ m/[FT]/g;
my $ft_count = #ft_matches; # count elements of array
But that's still two lines. Another weirder trick that can make it shorter:
my $ft_count = () = $str =~ m/[FT]/g;
The "() =" forces the "m" to be in list context. Assigning a list with N elements to a list of zero variables doesn't actually do anything. But then when this assignment expression is used in a scalar context ($ft_count = ...), the right "=" operator returns the number of elements from its right-hand side - exactly what you want.
This is incredibly weird when first encountered, but the "=()=" idiom is a useful Perl trick to know, for "evaluate in list context, then get size of list".
Note: I have no data on which of these are more efficient when dealing with large strings. In fact, I suspect your original code might be best in that case.
Yes, you can use the CountOf secret operator:
my $ft_count = ()= $str =~ m/[FT]/g;
You can combine line 2, 3 and 4 into one like so:
my $str = "GGGFFEEIIEETTGGG";
print $str =~ s/[FT]//g; #Output 4;

Why this code does not do what I mean?

$w = 'self-powering';
%h = (self => 'self',
power => 'pauә',
);
if ($w =~ /(\w+)-(\w+)ing$/ && $1~~%h && $2~~%h && $h{$2}=~/ә$/) {
$p = $h{$1}.$h{$2}.'riŋ';
print "$w:"," [","$p","] ";
}
I expect the output to be
self-powering: selfpauәriŋ
But what I get is:
self-powering: [riŋ]
My guess is something's wrong with the code
$h{$2}=~/ә$/
It seems that when I use
$h{$2}!~/ә$/
Perl will do what I mean but why I can't get "self-powering: selfpauәriŋ"?
What am I doing wrong? Any ideas?
Thanks as always for any comments/suggestions/pointers :)
When you run
$h{$2}!~/ә$/
In your if statement the contents of $1 and $2 are changed to be empty, because no groupings were matched (there were none). If you do it like this:
if ($w =~ /(\w+)-(\w+)ing$/){
my $m1 = $1;
my $m2 = $2;
if($m2~~%h && $m2~~%h && $h{$m2}=~/ә$/) {
$p = $h{$m1}.$h{$m2}.'riŋ';
print "$w:"," [","$p","] ";
}
}
I expect you will get what you want.
Are you running with use warnings enabled? That would tell you that $1 and $2 are not what you expect. Your second regex, not the first, determines the values of those variables once you enter the if block. To illustrate with a simpler example:
print $1, "\n"
if 'foo' =~ /(\w+)/
and 'bar' =~ /(\w+)/;