Is there a way to match all adjacent words in a sentence?

Viewed 209
my $line = "The quick brown fox jumps over the lazy dog.";

while ($line){
    $line =~ s/["",]//ig; #[] means to get rid of 
    #print $line
    $line = lc($line); #lc is lowercase
        while ($line=~m/\b(\w+\s\w+)\b/ig){ #[^ ] means any character except spaces and newline #($line=~m/\b(\s\w+\s\w+)\b/ig)
        my $word =$1;
        print "$word\n";
        $wordcount{$word} += 1;
         
    }
last;

}
close(INPUT);
close(OUTPUT);

Desired out put will be: the quick, quick brown, brown fox, fox jumps.... However, for the code above I am only getting the quick, brown fox, jumps over....

3 Answers

Can capture both but not consume the second using a lookahead, so that pairs overlap

use warnings;
use strict;
use feature 'say';

my $string = shift // 'The quick brown fox jumps over the lazy dog.';
 
while ( $string =~ /(\w+)\s+(?=(\w+))/g ) { 
   say "$1 $2";
}

Prints as desired. This allows any amount of whitespace between words.†


An explanation.

After a word is captured with (\w+), the lookahead (?=...) merely asserts ("looks ahead") that another word follows but doesn't "consume" it nor advances past it (while we capture it with (extra) parens, so we get two words in $1 and $2). We consumed just one word and the regex engine stays right after the space(s) following the first word.

So in the next iteration it can match the next word, the one last "seen" by lookahead. Then it again spots yet the next word by the lookahead, again capturing both. Etc. Thus the overlap.


† Drop that + and use only \s if you indeed want to allow only one whitespace. If you want a literal space only -- no tabs etc, see the link for what \s matches -- then instead of \s use (literal space, SPACE ) or [ ], literal space inside a "character class" (brackets), for clarity.

You can use

(\w+)\s(?=(\w+\b))

Regex Explanation

  • ( Capturing group
    • \w+ Match a word
  • ) Close group
  • \s Match a space
  • (?= Lookahead assertion - assert that the following regex matches
    • ( Capturing group
      • \w+\b Match a word
    • ) Close group
  • ) Close lookahead

See regex demo

Perl Example

my $line = "The quick brown fox jumps over the lazy dog.";

while ($line =~ /(\w+)\s(?=(\w+\b))/g) {
    print("$1 $2\n");
}

Output

The quick
quick brown
brown fox
fox jumps
jumps over
over the
the lazy
lazy dog

You don't need to do anything fancy with regular expressions at all if you split the string up into an array of words:

#!/usr/bin/env perl                                                                                                                                                                                                                              
use strict;
use warnings;
use feature qw/say/;

my $line = "The quick brown fox jumps over the lazy dog.";
$line =~ s/[^\w\s]//g; # Remove non-word, non-whitespace characters                                                                                                                                                                              
my @words = split ' ', $line;
for my $i (0 .. $#words - 1) {
    say "$words[$i] $words[$i + 1]";
}
Related