swapping values - regex

I was wondering if there is an easy/clean way of swapping values as follows, perhaps using a single regex/substitution?
If $a ends with "x", substitute it with "y". And similarly if $a ends with "y", swap it with "x":
$a = "test_x";
if ($a =~ /x$/) {
$a =~ s/x$/y/;
} else {
$a =~ s/y$/x/;
}
I can only think of something like this:
$a = $a =~ /x$/ ? s/x$/y/ : s/y$/x/;

This is simply:
$a =~ s/x$/y/ or $a =~ s/y$/x/;
It's almost always redundant to do a match to see if you should do a substitution.
Another way:
substr($a,-1) =~ y/xy/yx/;

You can squeeze it in a line like you show, perhaps a bit nicer with /r (with v5.14+).
Or you can prepare a hash. This also relieves the code from hard-coding particular characters.
my %swap = (x => 'y', y => 'x', a => 'b', b => 'a'); # expand as needed
my #test = map { 'test_' . $_ } qw(x y a b Z);
for my $string (#test)
{
$string =~ s| (.)$ | $swap{$1} // $1 |ex;
say $string;
}
The // (defined-or) is there to handle the case where the last character isn't in the hash, in which case $swap{$1} returns undef. Thanks to user52889 for the comment.

To swap individual characters, you can use tr///.
Not sure what your criteria for cleanliness or ease, but you could even do this inside the right hand side of the substitution:
$xy = "test_x" =~ s`([xy])$`$1=~tr/xy/yx/r`re; # $xy is "test_y"

Related

Unable to search one variable within another variable using Perl

Using perl, i'm unable to perform a simple search to see if one number is within another. The below example doesn't enter the if statement.
my $a = "12345";
my $b = "123456789";
if($a=~ m/$b/g) #doesn't work
{
print "success";
}
The below doesn't work either.
my $a = "12345";
my $b = "123456789";
if($a =~ /$b/) #doesn't work
{
print "success";
}
To check if $substr is in $string, you want:
if ($string =~ /\Q$substr\E/)
or
if (index($string, $substr) >= 0)
In your case, that means
if ($b =~ /\Q$a\E/)
or
if (index($b, $a) >= 0)
\Q..\E causes the contents of the variable in between to be matched literally rather than have it treated as a regex pattern.
if (//g) makes no sense, and using it can produce subtle and very odd problems.
I believe you might have mixed up your variables in the statement. The $a =~ m/$b/g indicates that $b can be found in $a (in order to be true), not the $a can be found in $b. Since $a is less in length than $b, the statements fail ( since 123456789 cannot be found in 12345).
I suspect what you want is:
if ( $b =~ m/$a/g ) # or ( $b =~ /$a/ )
{
print "success";
}
There are characters that may have different meanings in regular expressions. Instead, do this:
if (index($b, $a) != -1) #works

Perl Grepping from an Array

I need to grep a value from an array.
For example i have a values
#a=('branches/Soft/a.txt', 'branches/Soft/h.cpp', branches/Main/utils.pl');
#Array = ('branches/Soft/a.txt', 'branches/Soft/h.cpp', branches/Main/utils.pl','branches/Soft/B2/c.tct', 'branches/Docs/A1/b.txt');
Now, i need to loop #a and find each value matches to #Array. For Example
It works for me with grep. You'd do it the exact same way as in the More::ListUtils example below, except for having grep instead of any. You can also shorten it to
my $got_it = grep { /$str/ } #paths;
my #matches = grep { /$str/ } #paths;
This by default tests with /m against $_, each element of the list in turn. The $str and #paths are the same as below.
You can use the module More::ListUtils as well. Its function any returns true/false depending on whether the condition in the block is satisfied for any element in the list, ie. whether there was a match in this case.
use warnings;
use strict;
use Most::ListUtils;
my $str = 'branches/Soft/a.txt';
my #paths = ('branches/Soft/a.txt', 'branches/Soft/b.txt',
'branches/Docs/A1/b.txt', 'branches/Soft/B2/c.tct');
my $got_match = any { $_ =~ m/$str/ } #paths;
With the list above, containing the $str, the $got_match is 1.
Or you can roll it by hand and catch the match as well
foreach my $p (#paths) {
print "Found it: $1\n" if $p =~ m/($str)/;
}
This does print out the match.
Note that the strings you show in your example do not contain the one to match. I added it to my list for a test. Without it in the list no match is found in either of the examples.
To test for more than one string, with the added sample
my #strings = ('branches/Soft/a.txt', 'branches/Soft/h.cpp', 'branches/Main/utils.pl');
my #paths = ('branches/Soft/a.txt', 'branches/Soft/h.cpp', 'branches/Main/utils.pl',
'branches/Soft/B2/c.tct', 'branches/Docs/A1/b.txt');
foreach my $str (#strings) {
foreach my $p (#paths) {
print "Found it: $1\n" if $p =~ m/($str)/;
}
# Or, instead of the foreach loop above use
# my $match = grep { /$str/ } #paths;
# print "Matched for $str\n" if $match;
}
This prints
Found it: branches/Soft/a.txt
Found it: branches/Soft/h.cpp
Found it: branches/Main/utils.pl
When the lines with grep are uncommented and foreach ones commented out I get the corresponding prints for the same strings.
The slashes dot in $a will pose a problem so you either have to escape them it when doing regex match or use a simple eq to find the matches:
Regex match with $a escaped:
my #matches = grep { /\Q$a\E/ } #array;
Simple comparison with "equals":
my #matches = grep { $_ eq $a } #array;
With your sample data both will give an empty array #matches because there is no match.
This Solved My Question. Thanks to all especially #zdim for the valuable time and support
my #SVNFILES = ('branches/Soft/a.txt', 'branches/Soft/b.txt');
my #paths = ('branches/Soft/a.txt', 'branches/Soft/b.txt',
'branches/Docs/A1/b.txt', 'branches/Soft/B2/c.tct');
foreach my $svn (#SVNFILES)
{
chomp ($svn);
my $m = grep { /$svn/ } (#paths);
if ( $m eq '0' ) {
print "Files Mismatch\n";
exit 1;
}
}
You should escape characters like '/' and '.' in any regex when you need it as a character.
Likewise :
$a="branches\/Soft\/a\.txt"
Retry whatever you did with either grep or perl with that. If it still doesn't work, tell us precisely what you tried.

how to extract string with any operator between?

I have an array contain #arr = { "a=b", "a>b", "a<b", "a!=b", "a-b" }. What is the best way to get a and b with any operator between. I can extract by
for($i=0; $i<=$#arr; $i++){
$str = $arr[$i];
if($str =~ m/^(.*?)(\s*=\s*)(.*)(;)/g){
my $d = $1;
my $e = $3;
}
Follow by all if statement with the possible operator like "!=", "<" etc. But this will make my code look messy. Any better solution for this?
You could try something like this one liner
perl -e '#a = ("a=b","a>b","a<b","a!=b","a-b"); for $l (#a) { $l =~ s/(.).*(.)/$1/; print "$1$2\n"};'
The key thing is the greedy match ie "(.*)" between the two single character matches ie "(.)". To really make sure that you start at the start and end of the strings you could use this
perl -e '#a = ("a=b","a>b","a<b","a!=b","a-b"); for $l (#a) { $l =~ s/^(.).*(.)$/$1/; print "$1$2\n"};'
A complete working example that demonstrates the whole thing would be
#!/usr/bin/perl
use strict;
use warnings;
my #expressions = ("a=b","a>b","a<b","a!=b","a-b");
for my $exp (#expressions) {
$exp =~ s/^(.).*(.)$/$1$2/;
print "$1$2 is the same as $exp\n";
};
A very simple regex might be
/^(\w+)\s*(\W+)\s*(\w+)$/
Or you enumerate possible operators
/^(\w+)\s*(=|!=|<|>|<=|>=|\+|-|\*|\/|==)\s*(\w+)$/
It depends whether the input can be trusted or not. If not, you might have to be more meticulous w.r.t. the identifiers, too. Here's a simpler loop, and no need to use m//g(lobal). Not sure about the semicolon - omitted it.
my #arr = ( "a=b", "a>b", "a<b", "a!=b", "a-b" );
for my $str (#arr){
if($str =~ /^(\w+)\s*(=|!=|<|>|<=|>=|\+|-|\*|\/|==)\s*(\w+)$/ ){
my $d = $1;
my $e = $3;
print "d=$d e=$e\n";
}
}
Later If you enumerate the operators, you can also add word symbols:
if($str =~ /^(\w+)\s*(=|!=|<|>|<=|>=|\+|-|\*|\/|==|x?or|and)\s*(\w+)$/ ){
...
if there always 'a' and 'b' at the beginning and the end you could try:
my $str = 'a<b';
my( $op ) = $str =~ /^a(.*)b$/;
Not a well thought out answer. Will reconsider the problem.

Combination of while, array, and regex

The loop fails. What is wrong with the array?
I would like the regex to return B when it parses the first string, and M when it parses the second string.
How is such an regex constructed?
#!/usr/bin/perl
use warnings;
use strict;
my $a = "0.0 B/s";
my $b = "12.0 MiB/s";
while (defined (my $s = shift ("$a", "$b"))) {
my $unit = $1 if ($a =~ m/.*([KMGT])i?B\/s$/);
print "$unit\n";
}
shift is meant to be used with arrays, not lists. If you want to use a while loop, you need to pre-declare an array containing $a and $b (which, by the way, are a bad choice for variable names).
Having said that, a for loop construct is the more natural choice here:
for my $s ( $var1, $var2 ) { ... }
And given that you're trying to extract the measurement unit, why not do things a slightly different way:
say for map { my ( $s ) = /$regex/; $s } $var1, $var2;
You need another substitution:
for ($a, $b) {
if (m!((?:[KMGT]i)?B)/s$!) {
my $unit = $1;
$unit =~ s/(.).*/$1/;
print "$unit\n" if $unit;
}
}
Your while has issues.
You are using variable $a inside loop, when you want to use $s.
I'd use it this way:
#!/usr/bin/perl
use warnings;
use strict;
my $a = "0.0 B/s";
my $b = "12.0 MiB/s";
foreach my $s($a, $b) {
print $1 if ($s =~ m/.*([KMGT])i?B\/s$/);
}

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;