Perl:如何计算 N 字窗口中出现 3 字短语(带间隙)的次数



我正在尝试计算文档中 12 个单词的窗口中出现 3 个单词短语的次数,但困难在于我正在搜索的关键字可以遍布整个窗口。

例如:

我想在一个 12 个单词的短语中找到短语"预计恶劣天气",只要包含 3 个单词的总短语不超过 12 个单词,就可以在 3 个所需单词之间插入其他单词。

有效的短语:

  • 我预计会有坏天气。
  • 他们预计天气恶劣多风。
  • 我预计,虽然没有人证实这一点,但恶劣的天气正在到来 方式。

我一直在努力弄清楚如何做到这一点。我知道如何计算 2 个单词短语的出现次数,其中可能存在差距。例如,如果我计算"预期"和"天气"在 12 个单词的短语中出现的频率,我可以做到:

$mycount =()= $text =~ /b(?:expectW+(?:w+W+){0,10}?weather)b/gi;

但是,当我想用 3 个单词来做这件事时,这并不那么简单,因为我最终会得到 2 个空格,这些空格必须加在一起,以便我的窗口不超过 12 个单词。理想情况下,我将能够执行以下操作:

$mycount =()= $text =~ /b(?:expectW+(?:w+W+){0,$Gap1}?badW+(?:w+W+){0,$Gap2}?weather)b/gi;

其中 $Gap 2 = 9 - $Gap 1,但我认为没有办法做到这一点。

我还想到创建一个循环,以便在循环的一次迭代中,$Gap 1=0 和 $Gap 2=9,在第二次迭代中$Gap 1=1 和 $Gap 2=8,依此类推,然后添加所有循环的计数。但是,这样做会重复计算该短语的某些实例。

我不知所措。有人有什么想法吗?我在任何地方都找不到任何相关的例子。

注意这篇文章解决了查找在窗口中散布的单词的问题。它没有考虑一般文本解析或语言分析等更复杂的问题。


下面的代码搜索第一个单词,然后继续为其他两个单词使用另一个正则表达式。在那里,它逐字扫描文本并保留一个计数器,以便它可以停在 12 个单词处。它使用pos来控制检查窗口后应继续的位置。

正如注释中所述,一旦找到单词expect,就会将 12 个长窗口从单词开始。搜索从完成的短语之后继续搜索下一个短语。

如果在接下来的 11 个单词中找不到该短语,则引擎将在expect后返回到该位置以继续搜索(因为选中的 11 个单词中可能有另一个expect)。

use warnings;
use strict;
use feature 'say';
my $s = q(I expect, although no one confirmed, that bad weather is on the way.)
. q(  Expect that we cannot expect to escape the bad, bad weather.);
my $word_range = 12;
my ($w1, $w2, $w3) = qw(expect bad weather);
FIRST_WORD: while ($s =~ /b($w1)b/gi) {
#say "SEARCH, at ", pos $s;
my ($one, $pos_one) = ($1, pos $s);
my ($two, $three, $cnt);
while ($s =~ /(w+)/g) {
my $word = $1; 
#say "t$word  ... (at ", pos $s, ")";
$two = $1  if $word =~ /b($w2)b/i; 

if ( $two and (($three) = $word =~ /b($w3)b/i) ) { 
say "$one + $two + $three  (pos ", pos $s, ')';
next FIRST_WORD;
}
last if ++$cnt == $word_range-1;  # failed (these 11 + 'expect') 
}
pos $s = $pos_one;         # return to position in string after 'expect'
}

请注意,不能在循环条件中分配匹配(对于$one),因为这会将匹配放在列表上下文中,从而干扰/g和pos所需的行为。

注释掉的打印可用于跟踪操作。 就目前而言,这打印

预期 + 恶劣 + 天气 (POS 53) 期望 + 恶劣 + 天气 (pos 128)

我扩展字符串以测试该短语的多次出现。匹配失败的操作可以通过削弱关键字和跟踪搜索中的位置来测试。

短语中可能的额外关键字(如第二句)将被忽略,如果存在,则接受该短语,因为这未指定但隐含在问题中。这很容易改变。

如果短语中有更多单词,它们都将在内部while循环中搜索,就像现在的最后两个一样,通过按顺序匹配它们(要求每个单词都找到所有前面的单词)。外部while循环仅在启动窗口时才需要。


窗口扫描失败后,外部while继续从窗口开始的位置搜索expect,从而再次扫描相同的 11 个单词。

通过在窗口扫描期间检查expect,也可以减少对文本的重复搜索。然后从该位置重新扫描,使用内while

# First sentence shortened and now does not contain the phrase
my $s = q(I expect, although no one confirmed, that bad expect.)
. q( Expect that we cannot expect to escape the bad, bad weather.);    
...
FIRST_WORD: while ($s =~ /b($w1)b/gi) {
my ($one, $pos_one) = ($1, pos $s);
my ($two, $three, $cnt, $pos_one_new);
while ($s =~ /(w+)/g) {
my $word = $1;
#say "t$word  ... (at ", pos $s, ")";
$pos_one_new = pos $s
if not $pos_one_new and $word =~ /b$w1b/i;
$two = $1  if $word =~ /b($w2)b/i;
if ( $two and (($three) = $word =~ /b($w3)b/i) ) {
say "$one + $two + $three  (pos ", pos $s, ')';
next FIRST_WORD;
} 
if (++$cnt == $word_range-1) {
last  if not $pos_one_new;

#say "Scan window anew from $pos_one_new";
pos $s   = $pos_one_new;
$pos_one = $pos_one_new;
$pos_one_new = 0;
$two = $three = '';
$cnt = 0;
}
}
pos $s = $pos_one;
}

这打印

预期 + 恶劣 + 天气 (POS 113)

请注意,使用窗口中第一次出现的expect

既然你提到处理一个文档,我假设你正在处理一长串句子。 因此,您可以:

我不知道为什么我总是期望别人不好。天气不是 指示器。

我认为这不希望标记为目标短语"预期恶劣天气"的出现。

你已经得到了一个纯粹面向正则表达式的好答案。您可以通过拆分句子来轻松修复它具有的跨句子短语检测错误,就像我在这里所做的那样。尽管如此,我想我会展示另一种思考问题的方式。

关键概念是标记化和规范化

首先,我们将语料库转换为句子列表。 这是标记化级别一。

秒,我们将每个句子变成一串小写单词,并删除标点符号(撇号除外)。 标记化级别二和规范化。

现在您所要做的就是筛选所有成堆的代币,看看是否有任何代币包含目标代币。

我以一种非常懒惰的方式处理回溯,在语料库文本上循环寻找与目标的第一个单词匹配的地方。 发生这种情况时,我从语料库中获取最大单词数,并检查目标列表是否包含在该列表中。 这提供了一个很好的回溯行为,而无需所有的簿记。

use strict;
use warnings;
use feature 'say';
use Lingua::Sentence;
my $doc = "I am unsure why I always expect bad from people. Weather isn't an indicator. My mood is fine whether it is sunny or I expect to see some bad weather.";
my @targets = (
[qw/ expect bad weather /],
[qw/ my mood is bad /],
);
my $max_phrase_length = 12;
my $splitter = Lingua::Sentence->new('en');
my @sentences = $splitter->split_array( $doc );
my %counter;
for my $sentence ( @sentences ) {
say "Checking sentence:  $sentence";
my @words = map lc,                  # Normalize to lowercase
map /(['w]*)/,          # get rid of punctuation
split /s+/, $sentence;  # Break sentence into words
for my $target ( @targets ) {
say "    Checking target:  @$target";
for my $i (0..$#words ) { 
my $word = @words[$i];
say "        Checking $word";
next if $word ne $target->[0];
my $first_word = $i; 
my $last_word = $i + $max_phrase_length -1; 
$last_word = $#words if $last_word > $#words;
if ( has_phrase( $target, [ @words[$first_word..$last_word] ] ) ) { 
say "BINGO!  @$target";
$counter{ "@$target" }++;
}
}
}
}
use Data::Dumper;
print Dumper %counter;

sub has_phrase {
my ( $target, $text ) = @_;
return if @$target > $text;
my $match_idx = 0;
for my $idx ( 0..$#$text ) {
if ($target->[$match_idx] eq $text->[$idx]) {
$match_idx++;
return 1 if $match_idx eq @$target;
}
}
return;
}

你的要求对我来说有点模糊。就像我不知道你是否想接受任何单词序列,然后计算"预期.*糟糕的.*天气",或者你是否只想拿12个单词而忽略其余的,或者如果你想一次滑动一个单词,一次不要看超过12个单词。

我想我会简化它: 我接受整行输入;我扔掉任何意想不到的词,坏的,或天气;然后我计算此后"预计恶劣天气"的发生次数。如果有人说"期待恶劣的坏天气",那不是匹配的。我相信你可以用你更确切的要求来修改它,因为你比我更了解它们。

while(<>){
$_=lc;
@w=split(/W+/);
@w=map {
if    (/expect/)      {1}
elsif (/bad/)         {2}
elsif (/weather/)     {3}
else                  {0}
} @w;
$_ = join("", @w);
print;
@w=grep {+$_>0} @w;
$_ = join("", @w);
print "=>$_";
@r=/123/g;
print "=".scalar(@r)."n";
}

例子:

hi! Expect really bad weather man.
010230=>123=1
hi! Expect really bad weather man.hi! Expect really bad weather man.hi! Expect really bad weather man.
010230010230010230=>123123123=3
Expect expect bad weather, expect bad bad bad weather, expect bad expect weather.
1123122231213=>1123122231213=1

你也可以单行这个,但我认为scalar(/123/g)的意思与@r=/123/g;scalar @r;不同,所以我把scalar(@_=/123/g).

$ perl -nE '$_=lc;$_=join("",grep{+$_>0}map{if(/expect/){1}elsif(/bad/){2}elsif(/weather/){3}else{0}}split(/W+/));say scalar(@_=/123/g)."n";'
hi! Expect really bad weather man.
1
hi! Expect really bad weather man. hi! Expect really bad weather man.
2
Expect Sad and Bad Weather today. Maybe Expect bad weather tomorrow too, because scalar is not helping.
2

最新更新