Perl 正则表达式 |如何从文件中排除单词

Posted

技术标签:

【中文标题】Perl 正则表达式 |如何从文件中排除单词【英文标题】:Perl Regular expression | how to exclude words from a file 【发布时间】:2019-09-07 18:25:17 【问题描述】:

我正在寻找一些关于我在项目中的一些要求的 Perl 正则表达式语法。 首先,我想从 txt 文件(字典)中排除字符串。

例如,如果我的文件有这个字符串:

path.../Document.txt |
  tree
  car
  ship

我使用正则表达式

a1testtre——匹配 orangesh1 -- 匹配 apleship3 -- 不匹配 [包含文件中的单词]

我还有一个我无法解决的要求。我必须创建一个不允许字符串重复超过 3 次字符(两个字符)的正则表达式。

例如:

adminnisstrator21 -- 匹配(有 2 次重复字符) kkeeykloakk -- 不匹配有超过 3 次重复 stack22ooverflow -- 匹配(有 2 次重复字符)

为此我已经尝试过

\b(?:([a-z])(?!\1))+\b

但它仅适用于第一个字符重复 知道如何解决这两个问题吗?

【问题讨论】:

查看What should I do when someone answers my question?(您可以通过单击答案旁边的箭头对答案进行投票,并通过单击复选标记接受答案) 【参考方案1】:

从给定列表中排除包含单词的字符串的一种方法是形成具有交替单词的模式并在正则表达式中使用该模式,并排除匹配的字符串。

use warnings;
use strict;
use feature qw(say);

use Path::Tiny;

my $file = shift // die "Usage: $0 file\n";  #/

my @words = split ' ', path($file)->slurp;

my $exclude = join '|', map  quotemeta  @words;

foreach my $string (qw(a1testtre orangesh1 apleship3)) 
 
    if ($string !~ /$exclude/)  
        say "OK: $string"; 
    

我使用Path::Tiny 将文件读入一个字符串(“slurp”),然后将split 通过空格转换为用于排除的单词。 quotemeta 转义非“单词”字符,如果您的单词中出现任何字符,然后由 | 连接以形成具有正则表达式模式的字符串。 (复杂模式使用qr。)

这可能会根据您的用例进行调整和改进,其中之一是关于具有交替的公共部分的模式的顺序。

检查连续重复字符不超过3次

foreach my $string (qw(adminnisstrator21 kkeeykloakk stack22ooverflow))

    my @chars_that_repeat = $string =~ /(.)\1+/g;

    if (@chars_that_repeat < 3)  
        say "OK: $string";
    

由于正则表达式中的 + 量词,一长串重复字符 (aaaa) 计为一个实例;如果您想计算所有对,请删除+,四个as 将计为两对。每次在字符串的不同位置重复的相同字符都会计数,因此aaXaa 算作两对。

这个sn-p可以直接加到上面的程序中,调用时用文件名加上用于排除的词。他们都打印了所提供样本的预期内容。


  考虑一个带有排除词的示例:sosolesolely。如果您只需要检查其中任何一个是否匹配,那么您首先需要较短的替代

my $exclude = join '|', map  quotemeta  sort  length $a <=> length $b  @words;
#==>  so|sole|solely

为了更快的匹配(so 匹配所有三个)。无论如何,这里似乎就是这种情况。

但是,如果你想正确识别哪个单词匹配,那么你必须首先有更长的单词,

solely|sole|so

以便字符串solely 与其单词正确匹配,然后它才能被so“窃取”。那么在这种情况下,你会想要反过来, sort length $b &lt;=&gt; length $a

【讨论】:

添加到 zdim 对您问题第一部分的回答中,在组装 @words 的正则表达式时要小心。用“|”连接的单词的顺序会影响匹配。 用'|'连接的单词的顺序会影响匹配。例如,您希望 TRUSTEES 在 TRUSTEE 之前,在 TRUST 之前,因此连接结果将包含如下单词:'TRUSTEES|TRUSTEE|TRUST'。我有一个小程序来排序模式/单词列表,我将把它作为答案发布。 @BruceVanAllen 这就是答案中的“这可能会根据您的用例进行调整和改进。”声明。事实上,考虑到他们的确切要求(只找到任何一个单词的匹配项),你实际上想要反过来排序,首先更短 - 然后找到任何共享该词干的单词更快。但我们并不真正了解实际用例,所以我只提了一下。【参考方案2】:

正如@zdim 回答的评论中所提到的,通过确保您的单词组合到匹配模式中的顺序不会让您感到困扰,可以更进一步。如果文件中的单词没有很仔细地排序开始,我在构建匹配字符串时使用这样的子程序:

# Returns a list of alternative match patterns in tight matching order.
# E.g., TRUSTEES before TRUSTEE before TRUST   
# TRUSTEES|TRUSTEE|TRUST

sub tight_match_order 
    return @_ unless @_ > 1;
    my (@alts, @ordered_alts, %alts_seen);
    @alts   = map  $alts_seen$_++ ? () : $_  @_;
    TEST: 
        my $alt = shift @alts;
        if (grep m#$alt#, @alts) 
            push @alts => $alt;
         else 
            push @ordered_alts => $alt;
        
        redo TEST if @alts;
    
    @ordered_alts


所以遵循@zdim 的回答:

...
my @words = split ' ', path($file)->slurp;

@words = tight_match_order(@words); # add this line

my $exclude = join '|', map  quotemeta  @words;
...

HTH

【讨论】:

【参考方案3】:

要与文件中的单词不匹配,您可以检查 whether a string contains a substring 或使用否定前瞻和替代:

^(?!.*(?:tree|car|ship)).*$
^ 断言字符串开始 (?! 负前瞻,断言右边的不是 .*(?:tree|car|ship) 匹配除换行符以外的任何字符 0+ 次,并匹配 tree car 或 ship ) 关闭负前瞻 .* 匹配除换行符以外的任何字符 $断言字符串结束

Regex demo

为了不让一个字符串的字符重复超过 3 次,您可以使用:

\b(?!(?:\w*(\w)\1)3)\w+\b
\b字边界 (?!负前瞻,断言右边的不是 (?:非捕获组 \w*(\w)\1 匹配 0+ 次单词字符,然后捕获组中的单词 char,然后使用 \1 对该组进行反向引用 )3关闭非捕获组并重复3次 ) 关闭负前瞻 \w+ 匹配 1+ 个单词字符 \b字边界

Regex demo

更新

根据this posted answer(您可能会添加到问题中),您有 2 个要组合的模式,但它不起作用:

(?=^(?!(?:\w*(.)\1)3).+$)(?=^(?:(.)(?!(?:.*?\1)4))*$)

在这 2 个模式中,您使用 2 个捕获组,因此第二个模式必须指向第二个捕获组 \2

(?=^(?!(?:\w*(.)\1)3).+$)(?=^(?:(.)(?!(?:.*?\2)4))*$)
                                               ^  

Pattern demo

【讨论】:

【参考方案4】:

我的问题是我有 2 个正在工作的正则表达式:

不允许超过 3 对字符:

          (?=^(?!(?:\w*(.)\1)3).+$)

不允许一个字符重复超过 4 次:

        (?=^(?:(.)(?!(?:.*?\1)4))*$)

现在我想将它们组合成一行,例如:

      (?=^(?!(?:\w*(.)\1)3).+$)(?=^(?:(.)(?!(?:.*?\1)4))*$)

但它只适用于第一个而不是两者的正则表达式

【讨论】:

您应该指向第二个捕获组,而不是第二个模式。我已为我的答案添加了更新。【参考方案5】:

我希望其他人会提出更好的解决方案,但这似乎可以满足您的要求:

\b                          Match word boundary
  (?:                       Start capture group
    (?:([a-z0-9])(?!\1))*   Match all characters until it encounters a double
    (?:([a-z0-9])\2)+       Match all repeated characters until a different one is reached
  )0,2                    Match capture group 0 or 2 times
  (?:([a-z0-9])(?!\3))+     Match all characters until it encounters a double
\b                          Match end of word

我将[a-z] 更改为也匹配数字,因为您提供的示例似乎也包括数字。 Perl 正则表达式还有\w 速记,相当于[A-Za-z0-9_],如果你想匹配单词中的任何字符,它会很方便。

【讨论】:

感谢您的回复...我会尝试一下。实际上我最关心的是字典单词的第一个要求

以上是关于Perl 正则表达式 |如何从文件中排除单词的主要内容,如果未能解决你的问题,请参考以下文章

如何使正则表达式与 perl 命令一起使用并从文件中提取数字?

排除正则表达式搜索中的单词列表

perl 和 java 正则表达式功能有啥区别?

正则表达式匹配两个单词之一

如何使用正则表达式匹配不以某些字符开头或结尾的单词?

PERL:用破折号读取社会保障号的正则表达式