Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can Perl substitution operator match an element in an array?

Tags:

perl

I have an array like this

my @stopWords = ("and","this",....)

My text is in this variable

my $wholeText = "....and so this is...."

I want to match every occurrence of every element of my stopWords array in the scalar wholeText and replace it with spaces.

One way of doing this is as follows :

foreach my $stopW (@stopWords)
{
   $wholeText =~ s/$stopW/ /;
}

This works and replaces every occurrence of all the stop words. I was just wondering, if there is a shorter way of doing it.

Like this:

$wholeText =~ s/@stopWords/ /;

The above does not seem to work though.

like image 974
Radz Avatar asked Oct 27 '10 06:10

Radz


People also ask

How do I match an array pattern in Perl?

The Perl grep() function is a filter that runs a regular expression on each element of an array and returns only the elements that evaluate as true. Using regular expressions can be extremely powerful and complex. The grep() functions uses the syntax @List = grep(Expression, @array).

Which function is used to handle substitutions Perl?

Substitution Operator or 's' operator in Perl is used to substitute a text of the string with some pattern specified by the user.

How do you find the index of an array element in Perl?

The grep equivalent would be $idx = grep { $array[$_] eq 'whatever' and last } 0 ..

How do you find the length of an array in Perl?

Note: In Perl arrays, the size of an array is always equal to (maximum_index + 1) i.e. And you can find the maximum index of array by using $#array. So @array and scalar @array is always used to find the size of an array.


5 Answers

While the various map/for-based solutions will work, they'll also do regex processing of your string separately for each and every stopword. While this is no big deal in the example given, it can cause major performance issues as the target text and stopword list grow.

Jonathan Leffler and Robert P are on the right track with their suggestions of mashing all the stopwords together into a single regex, but a simple join of all the stopwords into a single alternation is a crude approach and, again, becomes inefficient if the stopword list is long.

Enter Regexp::Assemble, which will build you a much 'smarter' regex to handle all the matches at once - I've used it to good effect with lists of up to 1700 or so words to be checked against:

#!/usr/bin/env perl

use strict;
use warnings;
use 5.010;

use Regexp::Assemble;

my @stopwords = qw( and the this that a an in to );

my $whole_text = <<EOT;
Fourscore and seven years ago our fathers brought forth
on this continent a new nation, conceived in liberty, and
dedicated to the proposition that all men are created equal.
EOT

my $ra = Regexp::Assemble->new(anchor_word_begin => 1, anchor_word_end => 1);
$ra->add(@stopwords);
say $ra->as_string;

say '---';

my $re = $ra->re;
$whole_text =~ s/$re//g;
say $whole_text;

Which outputs:

\b(?:t(?:h(?:at|is|e)|o)|a(?:nd?)?|in)\b
---
Fourscore  seven years ago our fathers brought forth
on  continent  new nation, conceived  liberty, 
dedicated   proposition  all men are created equal.
like image 78
Dave Sherohman Avatar answered Oct 29 '22 00:10

Dave Sherohman


My best solution:

$wholeText =~ s/$_//g for @stopWords;

You might want to sharpen the regexp using some \b and whitespace.

like image 31
zoul Avatar answered Oct 29 '22 01:10

zoul


What about:

my $qrstring = '\b(' . (join '|', @stopWords) . ')\b';
my $qr = qr/$qrstring/;
$wholeText =~ s/$qr/ /g;

Concatenate all the words to form '\b(and|the|it|...)\b'; the parentheses around the join are necessary to give it a list context; without them, you end up with the count of the number of words). The '\b' metacharacters mark word boundaries, and therefore prevent you changing 'thousand' into 'thous'. Convert that into a quoted regular expression; apply it globally to your subject string (so that all occurrences of all stop words are removed in a single operation).

You can also do without the variable '$qr':

my $qrstring = '\b(' . (join '|', @stopWords) . ')\b';
$wholeText =~ s/$qrstring/ /g;

I don't think I'd care to maintain the code of anyone who managed to do without the variable '$qrstring'; it probably can be done, but I don't think it would be very readable.

like image 33
Jonathan Leffler Avatar answered Oct 28 '22 23:10

Jonathan Leffler


My paranoid version:

$wholeText =~ s/\b\Q$_\E\b/ /gi for @stopWords;

Use \b to match word boundaries, and \Q..\E just in case any of your stopwords contains characters which may be interpreted as "special" by the regex engine.

like image 26
mfontani Avatar answered Oct 29 '22 01:10

mfontani


You could consider using a regex join to create a single regex.

my $regex_str = join '|', map { quotemeta } @stopwords;
$string =~ /$regex_str/ /g;

Note that the quotemeta part just makes sure that any regex characters are properly escaped.

like image 3
Robert P Avatar answered Oct 28 '22 23:10

Robert P