Why does `=~` behave differently than `eq` and `==` in list context? - regex

Consider
my #array = ( 'x' =~ /y/, 'x' eq 'y', 1 == 2 );
and
my %hash = ( 'a', 'x' =~ /y/, 'b', 'x' eq 'y', 'c', 1 == 2 );
Using Data::Dumper, we see that =~ behaves differently than eq and ==
\#array = [
'',
''
];
\%hash = {
'a' => 'b',
'' => undef
};
Specifically it appears that the snippets of code above are being interpreted as
my #array = ( 'x' eq 'y', 1 == 2 );
and
my %hash = ( 'a', 'b', 'x' eq 'y', 'c', 1 == 2 );
Can someone provide an explanation for this arguably unexpected behavior?

The match operation you use is not only about finding out if something has matched (scalar context) but also to give the match results (list context). To cite from the documentation:
Matching in list context
m// in list context returns a list consisting of the subexpressions matched by the parentheses in the pattern, that is, ($1, $2, $3...) (Note that here $1 etc. are also set). When there are no parentheses in the pattern, the return value is the list (1) for success. With or without parentheses, an empty list is returned upon failure

Related

In Perl why does adding "|| die" onto a statement cause it to evaluate in scalar context?

I finally found out that my code was getting evaluated in scalar context instead of list context, even though I had () around the assignment.
1st question is why does adding "|| die ..." onto an expression/assignment cause it to evaluate in scalar context?
2nd, is there an "|| die .... " idiom/equivalent that can be used when doing a list assignment?
Here is my sample code that demonstrates the issue.
#!/usr/bin/perl
use strict;
use warnings;
use Data::Dumper qw(Dumper);
my $h1 = {
var => "1",
bar => "1",
baz => "1",
};
my $h2 = {
var => "2",
bar => "2",
baz => "2",
};
my $ds;
$ds->{rules} = [$h1,$h2];
print "TEST1\n";
print Dumper($ds);
print "TEST2\n";
my (#processes) = #{$ds->{rules}};
print Dumper(\#processes);
print "#processes\n";
print "TEST3\n";
(#processes) = #{$ds->{rules}} || die "unable to get rules form config.. \n";
print Dumper(\#processes);
print "#processes\n";
Output:
TEST2
$VAR1 = [
{
'bar' => '1',
'baz' => '1',
'var' => '1'
},
{
'bar' => '2',
'baz' => '2',
'var' => '2'
}
];
HASH(0x25eea68) HASH(0x260b240)
TEST3
$VAR1 = [
2
];
2
Compare TEST2 and TEST3. As best I can tell, simply adding the || die '.... ' bit onto the end changes the way the statement gets evaluated into scalar context, even though I have parenthesis around the left-hand side. Q1: Maybe i'm just dense, but why does that happen?
Q2: is there an "|| die .... " idiom/equivalent that can still be used when doing a list assignment?
The reason that this forces a scalar context is because || binds stronger than =, so
(#processes) = #{$ds->{rules}} || die "unable to get rules form config.. \n";
Is parsed as
(#processes) = (#{$ds->{rules}} || die "unable to get rules form config.. \n");
And the || creates a boolean context. However perl has a weakly binding version of ||:
(#processes) = #{$ds->{rules}} or die "unable to get rules form config.. \n";
Which will get parsed the way you desire.

Checking the user input in perl for characters and length

My question is basically as the title says. How can I check, in perl, whether a user inputted string
is only certain characters long (for example, 3 characters long)
contains only characters (for example, it should only contain the characters 'u', 'a', 'g', and 'c')
my $input = <>;
if ($input =~ /^[uagc]{3}$/g){
# your codes
} else {
#exit or die;
}
$s =~ /^[uagc]{3}\z/
or die("usage\n");

swapping values

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"

Perl - Using regex to match input in hash key or value

First, this is a homework assignment. I am having a tough time with regex, and I'm stuck.
This is the code I have so far, where I have the user designate a filename, and if it exists, populates a hash of the names as keys, and the phone numbers as the values.
#!/usr/bin/perl
use strict;
print "\nEnter Filename: ";
my $file = <STDIN>;
chomp $file;
if(!open(my $fileName, "<", "$file"))
{
print "Sorry, that file doesn't exist!", "\n";
}
else
{
my %phoneNums;
while (my $line=<$fileName>)
{
chomp($line);
(my $name,my $number) = split /:/, $line;
$phoneNums{$name} = $number;
}
print "Read in the file!", "\n\n";
print "Enter search: ";
my $input = <STDIN>;
chomp $input;
#HERE IS WHERE I'M LOST
}
print "\n";
This is the part I am stuck on:
Allow the user to enter a search string.
Look for matches using the same style as the phone. Any individual
character in the search string can match any other character from the
key, meaning a ‘2’ in the search string can match a ‘2’, ‘A’, ‘B’, or ‘C’ in the contact list. Matches can occur in the contact name or the phone number. For a match to occur, each character in the search string must appear, in order, in the contact info, but not necessarily next to each
other. For example, a search string of “86” (essentially the same as a search string of “TM” or “NU”) would match “TOM” but not “MOTHER”.
Characters on each phone keys:
0,
1,
2ABC,
3DEF,
4GHI,
5JKL,
6MNO,
7PQRS,
8TUV,
9WXYZ
I just am stuck on how exactly to make all those character classes, and any help at all is much appreciated.
The way to tackle this is by writing a function that reduces your 'things' to their common components. The best way to do this IMO is use a hash:
my %num_to_letter = (
0 => [],
1 => [],
2 => [ "A", "B", "C" ],
3 => [ "D", "E", "F" ],
4 => [ "G", "H", "I" ],
5 => [ "J", "K", "L" ],
## etc.
);
my %letter_to_num;
foreach my $key ( keys %num_to_letter ) {
foreach my $element ( #{$num_to_letter{$key}} ) {
$letter_to_num{lc($element)} = lc($key);
}
}
print Dumper \%letter_to_num;
This creates a map of which letters or numbers map to their original - a bit like this:
$VAR1 = {
'b' => '2',
'g' => '4',
'e' => '3',
'i' => '4',
'a' => '2',
'j' => '5',
...
Note - you can do this by hand, but I prefer to generate from the top map, because I think it looks neater. Note - we use lc to lower case everything, so this becomes case insensitive. It's probably worth looking at fc - which is a similar tool but handles international characters. (Not relevant in this example though)
You then 'reduce' both search and 'target' to their common values:
sub normalise {
my ( $input ) = #_;
#join with no delimiter.
return join ( '',
#look up $_ (each letter) in $letter_to_num
#if not present, use // operator to return original value.
#this means we get to turn numbers into letters,
#but leave things that are already numbers untouched.
map { $letter_to_num{lc($_)} // $_ }
#split the input line into characters.
split ( //, $input )
);
}
print normalise ("DAD"),"\n"; ## 323
And then compare one against the other:
my $search = "DAD";
my $normalised_search = normalise($search);
print "Searching for: \"$normalised_search\"\n";
my $number_to_match = '00533932388';
my $string_to_match = "daddyo";
print "Matches number\n"
if normalise($number_to_match) =~ m/$normalised_search/;
print "Matches string\n"
if normalise($string_to_match) =~ m/$normalised_search/;
Here's an almost procedural approach that cheats a bit by using Hash::MultiValue:
use Hash::MultiValue; # makes reversing and flattening easier
# build a hash from the phone_keypad array or do it manually!
my #phone_keypad = qw(0 1 2ABC 3DEF 4GHI 5JKL 6MNO 7PQRS 8TUV 9WXYZ);
my %num2let = map { /(\d{1})(\w{3,4})/;
if ($2) { $1 => [ split('',$2) ] } else { 0 => [] , 1 => [] }
} #phone_keypad ;
# Invert the hash using Hash::MultiValue
my $num2let_mv = Hash::MultiValue->from_mixed(\%num2let);
my %let2num = reverse $num2let_mv->flatten ;
# TOM in numbers - 866 in letters
my $letters = "TOM" ;
print join '', $let2num{$_} // $_ for (split('', $letters)), "\n";
my $phone_input = "866" ;
print join '', #{$num2let{$_}}," " for (split('', $phone_input)) , "\n";
Output:
866
TUV MNO MNO
So here "TOM" would overlap with "UNO" ... I like #Sobrique's answer :-)
To search an array/list of contact names using the phone keypad input we can create a hash containing keys and values of the names and their number equivalents and then match the "converted" name value against the input:
use Hash::MultiValue; # makes reversing and flattening easier
my #contacts = <DATA> ;
chomp #contacts;
# build a hash from the phone_keypad array or do it manually!
my #phone_keypad = qw(0 1 2ABC 3DEF 4GHI 5JKL 6MNO 7PQRS 8TUV 9WXYZ);
my %num2let = map { /(\d{1})(\w{3,4})/;
if ($2) { $1 => [ split('',$2) ] } else { 0 => [] , 1 => [] }
} #phone_keypad ;
# Invert the hash using Hasj::MultiValue
my $num2let_mv = Hash::MultiValue->from_mixed(\%num2let);
my %let2num = reverse $num2let_mv->flatten ;
# create key/value pairs for contact database
my %contacts2nums ;
for $contact (#contacts) {
$contacts2nums{$contact} = join "",
map { $let2num{$_} } split('', uc $contact);
}
my $phone_input = "866";
for my $contact (keys %contacts2nums) {
print "The text: \"$contact\" matches the input: \"$phone_input\" \n"
if $phone_input eq $contacts2nums{$contact};
}
__DATA__
Tom
Mother
TIMTOWDI
DAD
Gordon
Output:
The text: "Tom" matches the input: "866"
A more organized approach would wrap the conversion operation in a function.
Addendum:
With a real keypad you could probably come up with a simple algorithm that could be more deterministic regarding the letter you want to associate with the number on the keypad. You could iterate through the array based on number of presses of the key: e.g. two presses on "2" would be equal to "B", etc. You'd just have to figure out how/when to move to the next character with some kind of timeout/wait value. This way you would have a more exact string on which to base your search.

Perl: how to find a match in one string and make a substitution in another?

Lets say I have a file learning.txt with the following info :
A *
B &
C (
D )
How can I take a user-input string abc and return *&(
There is this very efficient (O(N+M)) solution in Perl
my %replace = ( A => '*', B => '&', C => '(', D => ')' );
my $re = join '|', map quotemeta, keys %replace;
$re = qr/($re)/;
# and somewhere else in the scope with $re and %replace
s/$re/$replace{$1}/g;
And but for the case insensitive it's a little bit more complicated
use feature qw(fc); # since v5.16 use lc otherwise
my %replace = ( A => '*', B => '&', C => '(', D => ')' );
my $re = join '|', map quotemeta, keys %replace;
$re = qr/($re)/i;
my %replace_fc;
#replace_fc{ map fc, keys %replace } = values %replace;
# and somewhere else in the scope with $re and %replace_fc
s/$re/$replace_fc{fc $1}/g;
Just feed %replace from your file like this
while (<>) {
my ($key, $val) = split;
$replace{$key} = $val;
}
Use /^(A|B|C)\\b\\s*(.+)/m regex (that has a multiline option) and then concatenate second groups.
See example of this regex output here.