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),两个词不可能发生冲突。
在构造字符串时(在将其输入到正则表达式引擎之前)它仍然占用线性空间。最坏情况下的性能也是线性的。
极端情况
-
原话:
aaaaaaaaaaaaaaaaaaa0 aaaaaaaaaaaaaaaaaaa1 ... aaaaaaaaaaaaaaaaaaa9 aaaaaaaaaaaaaaaaaaaa ... aaaaaaaaaaaaaaaaaaaz aaaaaaaaaaaaaaaaaaaA ... aaaaaaaaaaaaaaaaaaaZ
(每个单词20个字符,最后一个字符从0-9变化,然后是a-z,然后是A-Z)
要搜索的字符串(不匹配):
aaaaaaaaaaaaaaaaaaaz aaaaaaaaaaaaaaaaaaay
(y只能在z之前)
-
原话:
patterns used in Perl pattern matching evolved from those supplied
(一些正常的词)
要搜索的字符串(不匹配):
patterns used in Perl pattern matching evolved from those suppliedd
(末尾多d)
-
原话:
aaaaaaaaaaaa aaaaaaaaaaa aaaaaaaaaa aaaaaaaaa aaaaaaaa aaaaaaa aaaaaa aaaaa aaaa
(Word只包含a,长度不同。)
要搜索的字符串(不匹配):
aaaaaaaaaaaa aaaaaaaaaaa aaaaaaaaaa aaaaaaaaa aaaaaaaa aaaaaaa aaaaaa aaaaa aaaaa
(末尾多a)
-
原话:
performance abc xyz performance 456 !@# performance
(同一个词出现多次)
要搜索的字符串(不匹配):
performance performance performance performance