建立一个正则表达式来查找彼此靠近的文本

Building a regular expression to find text near each other

我在使用此搜索时遇到问题:

import re

word1 = 'this'
word2 = 'that'
sentence = 'this and that'

print(re.search('(?:\b(word1)\b(?: +[^ \n]*){0,5} *\b(word2)\b)|(?:\b(word2)\b(?: +[^ \n]*){0,5} *\b(word1)\b)',sentence))

我需要构建一个正则表达式搜索来查找一个字符串是否在一定数量的其他单词中以任意顺序最多包含 5 个不同的子字符串(因此两个字符串可能相隔 3 个单词,三个字符串总共相隔 6 个字等)。

我发现了很多类似的问题,例如 or ,但其中 none 确实是这样做的。

因此,如果搜索词是 'this'、'that'、'these' 和 'those',并且它们以任意顺序出现在彼此的 9 个词以内,则脚本将输出 True.

似乎用各种不同的正则表达式语句编写一个 if/else 块来适应不同的排列会相当麻烦,所以我希望有一种更有效的方法来在 Python.

ANSWER CHANGED 因为我找到了一种只使用正则表达式的方法。该方法是从前瞻开始,要求所有目标词出现在接下来的 N 个词中。然后寻找目标词的模式(以任何顺序)由 0 个或更多其他词分隔(最多允许的最大中间词)

单词跨度 (N) 是允许所有目标单词处于最大允许距离的最大单词数。

例如,如果我们有 3 个目标词,并且我们允许它们之间最多有 4 个其他词,那么最大词跨度将为 11。所以 3 个目标词加上 2 个中间系列最多 4 个其他词 3 +4+4=11.

搜索模式由依赖于单词和允许的最大中间单词数的部分组装而成。

模式:\bALL((ANY)(\W+\w+\W*){0,INTER}){COUNT,COUNT}

细分:

  • \b 从单词边界开始
  • ALL 将被多个先行替换,这将确保在接下来的 N 个词中找到每个目标词。
  • 每个 lookahead 的形式为 (?=(\w+\W*){0,SPAN}WORD\b),其中 WORD 是目标词,SPAN 是最长可能的词序列中其他词的数量。每个目标词都会有一个这样的前瞻。从而确保N个词的序列包含所有目标词。
  • (\b(ANY)(\W+\w+\W*){0,INTER}) 匹配任何目标词后跟零到 maxInter 中间词。其中,ANY 将被替换为与任何目标词匹配的模式(即由竖线分隔的词)。并且 INTER 将被允许的中间词数替换。
  • {COUNT,COUNT} 确保上面的重复次数与目标词一样多。这对应于模式:targetWord+intermediates+targetWord+intermediates...+targetWord
  • 通过将前瞻性置于重复模式之前,我们可以保证词序列中的所有目标词都包含准确数量的目标词,并且中间词的数量不超过允许的数量。

...

import re

words    = {"this","that","other"}
maxInter = 3 # maximum intermediate words between the target words

wordSpan = len(words)+maxInter*(len(words)-1)

anyWord  = "|".join(words)
allWords = "".join(r"(?=(\w+\W*){0,SPAN}WORD\b)".replace("WORD",w) 
                    for w in words)
allWords = allWords.replace("SPAN",str(wordSpan-1))
                    
pattern = r"\bALL(\b(ANY)(\W+\w+\W*){0,INTER}){COUNT,COUNT}"
pattern = pattern.replace("COUNT",str(len(words)))
pattern = pattern.replace("INTER",str(maxInter))
pattern = pattern.replace("ALL",allWords)
pattern = pattern.replace("ANY",anyWord)


textList = [
   "looking for this and that and some other thing", # YES
   "that rod is longer than this other one",         # NO: 4 words apart
   "other than this, I have nothing",                # NO: missing "that"
   "ignore multiple words not before this and that or other", # YES
   "this and that or other, followed by a bunch of words",    # YES
           ] 

输出:

print(pattern)

\b(?=(\w*\b\W+){0,8}this\b)(?=(\w*\b\W+){0,8}other\b)(?=(\w*\b\W+){0,8}that\b)(\b(other|this|that)\b(\w*\b\W+){0,3}){3,3}

for text in textList:
    found = bool(re.search(pattern,text))
    print(found,"\t:",text)

True    : looking for this and that and some other thing
False   : that rod is longer than this other one
False   : other than this, I have nothing
True    : ignore multiple words not before this and that or other
True    : this and that or other, followed by a bunch of words

这可以使用支持条件、原子组的引擎来完成
并将组状态捕获为已标记,标记为 EMPTYNULL。其中 null 未定义。

所以这几乎是所有现代引擎。有些不完整,但像 JS.
Python 可以使用其替换引擎支持此 import regex.

基本上这样会支持乱序,可以限制在最短的
总字数从 4 到 9 不等。
底部 (?= ) 断言已找到所有必需的项目。
在没有原子组的情况下使用它可能会导致回溯问题,但由于它
在那里,这个正则表达式非常快。

更新: 添加了先行 (?= this | that | these | those ) 所以它开始匹配一个特殊的词。

Python代码

>>> import regex
>>>
>>> targ = 'this  sdgbsesfrgnh these meat ball those  nhwsgfr that sfdng  sfgnsefn sfgnndfsng'
>>> pat = r'(?=this|that|these|those)(?>\s*(?:(?(1)(?!))\bthis\b()|(?(2)(?!))\bthat\b()|(?(3)(?!))\bthese\b()|(?(4)(?!))\bthose\b()|(?(5)(?!))\b(.+?)\b|(?(6)(?!))\b(.+?)\b|(?(7)(?!))\b(.+?)\b|(?(8)(?!))\b(.+?)\b|(?(9)(?!))\b(.+?)\b)\s*){4,9
}?(?=)'
>>>
>>> regex.search(pat, targ).group()
'this  sdgbsesfrgnh these meat ball those  nhwsgfr that '

通用 PCRE / Perl 等(相同的正则表达式)

(?=this|that|these|those)(?>\s*(?:(?(1)(?!))\bthis\b()|(?(2)(?!))\bthat\b()|(?(3)(?!))\bthese\b()|(?(4)(?!))\bthose\b()|(?(5)(?!))\b(.+?)\b|(?(6)(?!))\b(.+?)\b|(?(7)(?!))\b(.+?)\b|(?(8)(?!))\b(.+?)\b|(?(9)(?!))\b(.+?)\b)\s*){4,9}?(?=)

https://regex101.com/r/zhSa64/1

 (?= this | that | these | those )
 (?>
    \s* 
    (?:
       (?(1)(?!))
       \b this \b ( )                # (1)
     |
       (?(2)(?!))
       \b that \b ( )                # (2)
     | 
       (?(3)(?!))
       \b these \b ( )               # (3)
     | 
       (?(4)(?!))
       \b those \b ( )               # (4)
     |
       (?(5)(?!))
       \b ( .+? ) \b                 # (5)
     |
       (?(6)(?!))
       \b ( .+? ) \b                 # (6)
     |
       (?(7)(?!))
       \b ( .+? ) \b                 # (7)
     |
       (?(8)(?!))
       \b ( .+? ) \b                 # (8)
     |
       (?(9)(?!))
       \b ( .+? ) \b                 # (9)
    )
    \s* 
 ){4,9}?
 (?=     )