【问题标题】:Case-insenstive ordered word search via regular expression通过正则表达式进行不区分大小写的顺序词搜索
【发布时间】:2013-02-10 17:47:41
【问题描述】:

我刚开始使用 perl 中的正则表达式。在玩过各种在线教程之后,我想写一个匹配顺序指定不区分大小写的单词匹配的正则表达式。

我正在尝试确定字符串“A”是由一个单词还是字符串“B”的单词序列组成,并且我想不区分大小写。

例如,如果字符串“B”是“John Von Neumann”,那么“JOhn”、“Von NeumaNn”、“VoN”、“john neuMann”将是一个匹配,但字符串像 "Joh"、"NeumaNn VoN"、"Vonn" 将匹配。

我不知道如何使用正则表达式来做到这一点,知道吗?

【问题讨论】:

  • @ogzd 发布了/^(?=.)(?:\bjohn\b)?\s*(?:\bvon\b)?\s*(?:\bneumann\b)?\z/i(在一些帮助下)。我不知道他为什么删除它。它不是通用解决方案,但 OP 可能不需要通用解决方案。
  • @ikegami: (?=.) 必须是 (?=\S) 否则该模式将匹配一个空格字符串。

标签: regex string perl search


【解决方案1】:

让我们暂时忽略大小写。

John Von Neumann

可以匹配

John Von Neumann    1 1 1
John Von            1 1 0
John     Neumann    1 0 1
John                1 0 0
     Von Neumann    0 1 1
     Von            0 1 0
         Neumann    0 0 1

所以你正在寻找的正则表达式模式是

/^(?:John Von Neumann|John Von|John Newmann|John|...)\z/i

创建列表的方法如下:

sub true_indexes {
   my ($n) = @_;
   my $i = 0;
   my @indexes;
   while ($n) {
      push @indexes, $i if $n & 1;
      ++$i;
      $n >>= 1;
   }
   return @indexes;
}

my @words = split(' ', 'John Von Neumann');

my @patterns;
unshift @patterns, join ' ', @words[ true_indexes($_) ]
   for 1 .. (2**@words)-1;

最后,我们可以生成模式:

my $pat = join '|', map quotemeta, @patterns;
my $re = qr/$pat/i;

你会这样使用它:

if ($input =~ /^$re\z/) {
   print "match\n";
} else {
   print "no match\n";
}

【讨论】:

  • @kamata 很好地使用 Perl 语法
  • 我认为 ogzd 的解决方案比这个解决方案要好得多,如果用作构建广义正则表达式的基础。该解决方案将为更长的短语构建一个巨大的正则表达式(可以减少 DFA,但正则表达式是巨大的事实不会改变)。不知道为什么这会得到如此多的支持。
  • @nhahtdh,ogzd 的解决方案不能用于构建通用正则表达式。它只能由^..\z^..$ 锚定使用,因为它不能匹配任何内容。另外,您对尺寸有误。正则表达式引擎将为交替创建一个 trie,这将减少“无”所需的时间和内存。最后,我的速度要快得多,因为他可以进行大量的回溯。
  • @ikegami:正则表达式引擎是一回事(对于像这样的简单情况,实现可能会实现一些有效的东西),但是在馈送到正则表达式引擎之前的字符串可能非常大。而且由于单词是有序的,因此可以只使用可选运算符来匹配它们。
  • @nhahtdh,字数越多,我的解决方案就越好。我认为 ogzd 可以回溯到宇宙的热寂。 (可以通过预处理$input 并更改模式来避免这种情况。)
【解决方案2】:

ikegami 的解决方案会占用指数空间来存储字符串,然后再转化为正则表达式(每个单词会出现 2n - 1 次,其中 n 是单词数,所以总空间至少为 2n - 1 * Sum(单词长度))。这与正则表达式引擎相关 - 因为问题出在字符串转换为正则表达式之前。


与 ikegami 的解决方案等效的正则表达式构造(就其匹配的字符串集而言)是:

^(?=[^ ])(?:(?: |^)John(?= |\z))?+(?:(?: |^)Von(?= |\z))?+(?:(?: |^)Neumann(?= |\z))?+\z

这仅占用线性空间,就单词数和所有单词的总长度而言。

为了清楚起见:

^
(?=[^ ])
(?:(?: |^)John(?= |\z))?+
(?:(?: |^)Von(?= |\z))?+
(?:(?: |^)Neumann(?= |\z))?+
\z

前瞻断言(?=[^ ])有两个目的:防止空字符串被匹配,并确保第一个字符不是空格字符。

注意?+,它使量词占有(或原子),因为我们在这里不需要回溯。 为什么?如果我们要正常执行此操作,我们将遍历单词列表并将其与输入中最左边的单词进行比较。找到匹配项后,我们将继续循环将其与输入中的下一个单词进行比较,直到找到输入中的所有单词或我们完成了单词列表的循环。

占有量词也可以防止回溯地狱的发生。如果一个词被认为是匹配的,它将永远不会被重新考虑。

对于每个单词,它们前面可以有一个空格,或者是字符串的开头。前瞻断言(?= |\z) 的目的是确保具有相同前缀的单词在第一次尝试时不会被错误匹配(例如"John Von Vone",尝试匹配"John Vone")。

由于没有回溯,最坏情况下的性能在所有单词的长度和输入字符串的长度方面是线性的(与没有正则表达式的情况相同)。


我们可以稍微改变一下正则表达式以允许灵活的间距:

^(?= *+[^ ])(?: *+John(?= |\z))?+(?: *+Von(?= |\z))?+(?: *+Neumann(?= |\z))?+ *+\z

为了清楚起见(前导空格很重要):

^
(?= *+[^ ])
(?: *+John(?= |\z))?+
(?: *+Von(?= |\z))?+
(?: *+Neumann(?= |\z))?+
 *+
\z

开头的前瞻(?= *+[^ ]) 确保输入字符串不只包含空格。

更改正则表达式以允许单词前有任意数量的空格(所有格量词不允许回溯)。使用 0 个或多个量词 *,因为单词正好在字符串的开头。由于前瞻断言(?= |\z),两个词不可能发生冲突。

在构造字符串时(在将其输入到正则表达式引擎之前)它仍然占用线性空间。最坏情况下的性能也是线性的。


极端情况

  1. 原话:

    aaaaaaaaaaaaaaaaaaa0 aaaaaaaaaaaaaaaaaaa1 ... aaaaaaaaaaaaaaaaaaa9 aaaaaaaaaaaaaaaaaaaa ... aaaaaaaaaaaaaaaaaaaz aaaaaaaaaaaaaaaaaaaA ... aaaaaaaaaaaaaaaaaaaZ
    

    (每个单词20个字符,最后一个字符从0-9变化,然后是a-z,然后是A-Z

    要搜索的字符串(不匹配):

    aaaaaaaaaaaaaaaaaaaz aaaaaaaaaaaaaaaaaaay
    

    y只能在z之前)

  2. 原话:

    patterns used in Perl pattern matching evolved from those supplied
    

    (一些正常的词)

    要搜索的字符串(不匹配):

    patterns used in Perl pattern matching evolved from those suppliedd
    

    (末尾多d

  3. 原话:

    aaaaaaaaaaaa aaaaaaaaaaa aaaaaaaaaa aaaaaaaaa aaaaaaaa aaaaaaa aaaaaa aaaaa aaaa
    

    (Word只包含a,长度不同。)

    要搜索的字符串(不匹配):

    aaaaaaaaaaaa aaaaaaaaaaa aaaaaaaaaa aaaaaaaaa aaaaaaaa aaaaaaa aaaaaa aaaaa aaaaa
    

    (末尾多a

  4. 原话:

    performance abc xyz performance 456 !@# performance
    

    (同一个词出现多次)

    要搜索的字符串(不匹配):

    performance performance performance performance
    

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-03-11
    • 1970-01-01
    • 2011-04-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多