【问题标题】:Match a list of words preceded and followed by some special character匹配一些特殊字符前后的单词列表
【发布时间】:2021-09-17 15:03:44
【问题描述】:

我正在尝试编写一个匹配很长的单词列表(4000 个单词)的正则表达式,如果单词位于字符串的开头或字符串的结尾,或者前后都有一个特殊字符,当前我正在使用的正则表达式是这样的:

((?:[^a-zA-Z0-9]|^)FIND(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)ANY(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)MATCHING(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)WORD(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)BY(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)THIS(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)VERY(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)LONG(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)REGEX(?:[^a-zA-Z0-9]|$))|((?:[^a-zA-Z0-9]|^)PATTERN(?:[^a-zA-Z0-9]|$))

这个正则表达式持续了大约 4000 个单词,我使用 python re 模块 / ripgrep 检查一些字符串是否匹配,我想知道每个字符串的匹配词。

我使用非捕获组,因为我真的不介意单词之前或之后的内容,只有单词匹配自己。

但是,对于我测试过的一些通用字符串,在树莓派上每次迭代大约需要 3-4 秒,我想知道我是否可以以某种方式为这种用法生成更快的模式。

谢谢。

【问题讨论】:

  • 也许构建一个像(?<![^\W_])(?:FIND|ANY|etc.)(?![^\W_]) 这样的模式就足够了?如果FINDANY 等字数过多,您甚至可以从中创建一个正则表达式树,以便更快地进行全字搜索。
  • @WiktorStribiżew 谢谢,确实更快!

标签: python regex


【解决方案1】:

首先,(?:[^a-zA-Z0-9]|^)(?:[^a-zA-Z0-9]|$) 模式在这里用作单词边界,不包括 _。精简它们并分别使用(?<![^\W_])(?![^\W_]) 是有意义的。

接下来,可以对您拥有的单词进行处理,以从中创建一个正则表达式,以进行高效搜索。

这是一个示例代码:

from trieregex import TrieRegEx
keywords = ['FIND', 'ANY', 'MATCHING', 'WORD', 'BY', 'THIS', 'VERY', 'LONG', 'REGEX', 'PATTERN', 'PARROT', 'FIGHT']
pattern = fr'(?<![^\W_])({TrieRegEx(*keywords).regex()})(?![^\W_])'
# => (?<![^\W_])((?:PA(?:TTERN|RROT)|FI(?:GHT|ND)|MATCHING|REGEX|LONG|THIS|VERY|WORD|ANY|BY))(?![^\W_])

只要确保你事先安装了trieregex

请参阅resulting regex pattern

另请参阅基于this regex trie solutionanother demo

import re

class Trie():
    """Regex::Trie in Python. Creates a Trie out of a list of words. The trie can be exported to a Regex pattern.
    The corresponding Regex should match much faster than a simple Regex union."""

    def __init__(self):
        self.data = {}

    def add(self, word):
        ref = self.data
        for char in word:
            ref[char] = char in ref and ref[char] or {}
            ref = ref[char]
        ref[''] = 1

    def dump(self):
        return self.data

    def quote(self, char):
        return re.escape(char)

    def _pattern(self, pData):
        data = pData
        if "" in data and len(data.keys()) == 1:
            return None

        alt = []
        cc = []
        q = 0
        for char in sorted(data.keys()):
            if isinstance(data[char], dict):
                try:
                    recurse = self._pattern(data[char])
                    alt.append(self.quote(char) + recurse)
                except:
                    cc.append(self.quote(char))
            else:
                q = 1
        cconly = not len(alt) > 0

        if len(cc) > 0:
            if len(cc) == 1:
                alt.append(cc[0])
            else:
                alt.append('[' + ''.join(cc) + ']')

        if len(alt) == 1:
            result = alt[0]
        else:
            result = "(?:" + "|".join(alt) + ")"

        if q:
            if cconly:
                result += "?"
            else:
                result = "(?:%s)?" % result
        return result

    def pattern(self):
        return self._pattern(self.dump())


text = r'FIND ANY MATCHING WORD BY THIS VERY LONG REGEX PATTERN FIGHT FIGHTER PARROT PARROT_ING'
keywords = ['FIND', 'ANY', 'MATCHING', 'WORD', 'BY', 'THIS', 'VERY', 'LONG', 'REGEX', 'PATTERN', 'PARROT', 'FIGHT']
trie = Trie()
for word in keywords:
    trie.add(word)
pattern = fr'(?<![^\W_])({trie.pattern()})(?![^\W_])'
print(re.findall(pattern, text))

输出:

['FIND', 'ANY', 'MATCHING', 'WORD', 'BY', 'THIS', 'VERY', 'LONG', 'REGEX', 'PATTERN', 'FIGHT', 'PARROT', 'PARROT']

注意PARROT的两次出现,最后一次来自PARROT_ING字符串部分。

【讨论】:

  • 谢谢,我以前从未听说过 trieregex。我的脚本现在需要 30 分钟而不是 3 天 :)
  • 只是提到,虽然这是一个有趣的项目,但有时从不同的关键字创建两个 TrieRegEx 对象会导致相同的正则表达式(尽管使用了不同的关键字),就像它使用某种缓存/对象创建一样错了..另外,有时对象正则表达式是空的..我正在使用您建议的正则表达式模式,但现在没有 trieregex ..
  • @user3731180 您不必依赖该库。您可以使用this solution 创建正则表达式尝试。
猜你喜欢
  • 2021-11-05
  • 1970-01-01
  • 2020-06-05
  • 1970-01-01
  • 2017-07-05
  • 2016-12-08
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多