Opposite of ~~ in Perl - perl

Is there an opposite for the operator ~~ in Perl? I used it to match an element in an array like this:
my #arr = qw /hello ma duhs udsyyd hjgdsh/;
print "is in\n" if ('duhs' ~~ #arr);
This prints is in. It is pretty cool, because I don't have to iterate all the array and compare each record. My problem is that I want to do something if I don't have a match. I could go on the else side but I rather find a opposite for `~~'

You can also use List::Util (newer versions of this module only) or List::MoreUtils with any, none and friends.
use List::Util qw(any none);
my #arr = qw /hello ma duhs udsyyd hjgdsh/;
say "oh hi" if any { $_ eq 'hello' } #arr;
say "no goodbyes?" if none { $_ eq 'goodbye' } #arr;
While not perl-native, it doesn't need the experimental smartmatching.

unless == if not
print "is not in\n" unless ('duhs' ~~ #arr);
Note: Smart matching is experimental in perl 5.18+. See Smart matching is experimental/depreciated in 5.18 - recommendations? So use the following instead:
print "is not in\n" unless grep { $_ eq 'duhs' } #arr;

Related

Why does this code print only the first element?

Can anyone explain to me why only the first item of this array is being printed?
print "Hello World!\n";
#array1 = ("john","haider","elley","uul");
#array2 = qw/This is an array/;
$array_size=scalar #array1;
print "Size: ",$array_size,"\n";
$i;
for($i=0; $i<=$array_size; $i=$i+1)
{
print"Array elements are=",$array1[i],"\n";
}
Because you've typo'ed here: $array1[i].
That should be $i, as in $array1[$i] - and this is something that use strict; use warnings would have warned you about. Bareword "i" not allowed while "strict subs" in use
But as it stands - because you use i there, and it's not a variable - it's evaluated as zero, so you just print the first element of the array.
A better idiom would be:
foreach my $value ( #array1 ) {
print "Array elements are=", $value,"\n";
}
It is rarely necessary (or desirable) to manipulate arrays by index in perl.

Perl Checking if a scalar contains one of the elements in an array

I have an array
my #array = qw/FOO BAR BAZ/;
and a scalar read from a file containing data like
+++123++585+++FOO
or
+++589++458+++XYZ
I am looking for a nice way to check if an element of the array matches part of the input string.
I know I could just loop over the array and match that way but was wondering if there is a more perl like way.
You can create a regex that matches all of the #array:
my $regex = join '|', map quotemeta, #array;
$string =~ $regex;
Yes, there is far better way. You can construct regular expression. It will be alternatives of fixed strings which is fortunately translated into trie (Aho-Corasick) which leads into linear search time. It is the most efficient way at all.
my #array = qw/FOO BAR BAZ/;
my $re = join '|', map quotemeta, #array;
$re = qr/$re/;
for my $string (#strings) {
if ($string =~ $re) {
...
}
}
That is exactly what grep is for. Here's a small snippet:
use strict;
use warnings;
my $str = "+++123++585+++FOO";
my $blank = "+++123++585+++XYZ";
my #array = qw/FOO BAR BAZ/;
print grep {$str =~ $_} #array, "\n";
print grep {$blank =~ $_} #array, "\n";
This would just return:
FOO
grep, reduce and map are what we call higher order functions in FP world, though reduce might be called fold there. Have a look at MJD's Higher Order Perl for more of these.
grep
map
reduce
Higher Order Perl

Conditional statement- compare to values store in array

Need help figuring out working perl code to put in place of "any of the elements in #array"
%hash = (key1 => 'value1',key2 => 'value2',key3 => 'value3',);
#array= ('value3','value4','value6');
if ($hash{ 'key1' } ne <<any of the elements in #array>>) {print "YAY!";}
CPAN solution: use List::MoreUtils
use List::MoreUtils qw{any};
print "YAY!" if any { $hash{'key1'} eq $_ } #array;
Why use this solution over the alternatives?
Can't use smart match in Perl before 5.10
grep solution loops through the entire list even if the first element of 1,000,000 long list matches. any will short-circuit and quit the moment the first match is found, thus it is more efficient.
A 5.10+ solution: Use a smart-match!
say 'Modern Yay!' unless $hash{$key} ~~ #array;
You could use the grep function. Here's a basic example:
print "YAY!" if grep { $hash{'key1'} eq $_ } #array;
In a scalar context like this grep will give you the number of matching entries in #array. If that's non-zero, you have a match.
You could also use a hash:
#hash{"value3","value4","value6"}=undef;
print "YAY" if exists $hash{key1};

How do I search a Perl array for a matching string?

What is the smartest way of searching through an array of strings for a matching string in Perl?
One caveat, I would like the search to be case-insensitive
so "aAa" would be in ("aaa","bbb")
It depends on what you want the search to do:
if you want to find all matches, use the built-in grep:
my #matches = grep { /pattern/ } #list_of_strings;
if you want to find the first match, use first in List::Util:
use List::Util 'first';
my $match = first { /pattern/ } #list_of_strings;
if you want to find the count of all matches, use true in List::MoreUtils:
use List::MoreUtils 'true';
my $count = true { /pattern/ } #list_of_strings;
if you want to know the index of the first match, use first_index in List::MoreUtils:
use List::MoreUtils 'first_index';
my $index = first_index { /pattern/ } #list_of_strings;
if you want to simply know if there was a match, but you don't care which element it was or its value, use any in List::Util:
use List::Util 1.33 'any';
my $match_found = any { /pattern/ } #list_of_strings;
All these examples do similar things at their core, but their implementations have been heavily optimized to be fast, and will be faster than any pure-perl implementation that you might write yourself with grep, map or a for loop.
Note that the algorithm for doing the looping is a separate issue than performing the individual matches. To match a string case-insensitively, you can simply use the i flag in the pattern: /pattern/i. You should definitely read through perldoc perlre if you have not previously done so.
I guess
#foo = ("aAa", "bbb");
#bar = grep(/^aaa/i, #foo);
print join ",",#bar;
would do the trick.
Perl 5.10+ contains the 'smart-match' operator ~~, which returns true if a certain element is contained in an array or hash, and false if it doesn't (see perlfaq4):
The nice thing is that it also supports regexes, meaning that your case-insensitive requirement can easily be taken care of:
use strict;
use warnings;
use 5.010;
my #array = qw/aaa bbb/;
my $wanted = 'aAa';
say "'$wanted' matches!" if /$wanted/i ~~ #array; # Prints "'aAa' matches!"
If you will be doing many searches of the array, AND matching always is defined as string equivalence, then you can normalize your data and use a hash.
my #strings = qw( aAa Bbb cCC DDD eee );
my %string_lut;
# Init via slice:
#string_lut{ map uc, #strings } = ();
# or use a for loop:
# for my $string ( #strings ) {
# $string_lut{ uc($string) } = undef;
# }
#Look for a string:
my $search = 'AAa';
print "'$string' ",
( exists $string_lut{ uc $string ? "IS" : "is NOT" ),
" in the array\n";
Let me emphasize that doing a hash lookup is good if you are planning on doing many lookups on the array. Also, it will only work if matching means that $foo eq $bar, or other requirements that can be met through normalization (like case insensitivity).
#!/usr/bin/env perl
use strict;
use warnings;
use Data::Dumper;
my #bar = qw(aaa bbb);
my #foo = grep {/aAa/i} #bar;
print Dumper \#foo;
Perl string match can also be used for a simple yes/no.
my #foo=("hello", "world", "foo", "bar");
if ("#foo" =~ /\bhello\b/){
print "found";
}
else{
print "not found";
}
For just a boolean match result or for a count of occurrences, you could use:
use 5.014; use strict; use warnings;
my #foo=('hello', 'world', 'foo', 'bar', 'hello world', 'HeLlo');
my $patterns=join(',',#foo);
for my $str (qw(quux world hello hEllO)) {
my $count=map {m/^$str$/i} #foo;
if ($count) {
print "I found '$str' $count time(s) in '$patterns'\n";
} else {
print "I could not find '$str' in the pattern list\n"
};
}
Output:
I could not find 'quux' in the pattern list
I found 'world' 1 time(s) in 'hello,world,foo,bar,hello world,HeLlo'
I found 'hello' 2 time(s) in 'hello,world,foo,bar,hello world,HeLlo'
I found 'hEllO' 2 time(s) in 'hello,world,foo,bar,hello world,HeLlo'
Does not require to use a module.
Of course it's less "expandable" and versatile as some code above.
I use this for interactive user answers to match against a predefined set of case unsensitive answers.

How can I make Perl functions that use $_ by default?

I have an array and a simple function that trims white spaces:
my #ar=("bla ", "ha 1")
sub trim { my $a = shift; $a =~ s/\s+$//; $a}
Now, I want to apply this to an array with the map function. Why can't I do this by just giving the function name like one would do with built-in functions?
For example, you can do
print map(length, #ar)
But you can't do
print map(trim, #ar)
You have to do something like:
print map {trim($_)} #ar
print map(trim($_), #ar)
If you are using 5.10 or later, you can specify _ as the prototype for trim. If you are using earlier versions, use Axeman's answer:
As the last character of a prototype, or just before a semicolon, you can use _ in place of $ : if this argument is not provided, $_ will be used instead.
use strict; use warnings;
my #x = ("bla ", "ha 1");
sub trim(_) { my ($x) = #_; $x =~ s!\s+$!!; $x }
print map trim, #x;
Incidentally, don't use $a and $b outside of a sort comparator: They are immune from strict checking.
However, I prefer not to use prototypes for functions I write mainly because their use makes it harder to mentally parse the code. So, I would prefer using:
map trim($_), #x;
See also perldoc perlsub:
This is all very powerful, of course, and should be used only in moderation to make the world a better place.
The prototype that Sinan talks about is the best current way. But for earlier versions, there is still the old standby:
sub trim {
# v-- Here's the quick way to do it.
my $str = #_ ? $_[0] : $_;
# That was it.
$str =~ s/^\s+|\s+$//;
return $str;
}
Of course, I have a trim function with more features and handles more arguments and list context, but it doesn't demonstrate the concept as well. The ternary expression is a quick way to do what the '_' prototype character now does.
My favorite way to optionally use $_ without needing 5.10+ is as follows:
sub trim {
my ($s) = (#_, $_);
$s =~ s/\s+$//;
$s
}
This assigns the first element of #_ to $s if there is one. Otherwise it uses $_.
Many Perl built-in functions operate on $_ if given no arguments.
If your function did the same, it would work:
my #ar = ("bla ", "ha 1");
sub trim { my $s = #_ ? $_[0] : $_; $s =~ s/\s+$//; $s}
print map(trim, #ar), "\n";
And yes, Perl is kind of gross.