在Python 3中加速数百万个正则表达式替换

193

我有两个列表:

  • 一个包含大约750K个“句子”(长字符串)的列表
  • 我想从我的750K个句子中删除约20K个“单词”的列表

所以,我必须遍历750K个句子并执行约20K个替换,但仅当我的单词确实是“单词”,而不是更大的字符字符串的一部分时才替换。

我通过预编译我的单词使它们被“\b”单词边界元字符包围来做到这一点:

compiled_words = [re.compile(r'\b' + word + r'\b') for word in my20000words]

然后我循环遍历我的"句子":

import re

for sentence in sentences:
  for word in compiled_words:
    sentence = re.sub(word, "", sentence)
  # put sentence into a growing list

这个嵌套循环每秒处理了50个句子,速度很不错,但仍需要几个小时来处理所有的句子。

  • 有没有一种方法可以使用str.replace方法(我相信它更快),但仍要求替换只发生在单词边界上?

  • 或者,有没有一种加速re.sub方法的方法?我已经通过跳过re.sub来稍微提高了速度,如果我的单词长度>句子长度,但这并没有多大改进。

我正在使用Python 3.5.2


2
这里的第一个答案有一些很好的示例代码:https://dev59.com/BXE85IYBdhLWcg3wSxkv 只需按照你拥有的CPU核数将句子数组分割,然后运行那么多个线程。 - Mohammad Ali
7
你也可以尝试非正则表达式的实现方式——逐个单词遍历输入,并与一组进行匹配。这是单次扫描,哈希查找非常快速。 - pvg
2
顺便问一下,这些句子有多长?750k行的数据集不应该需要几个小时来处理。 - pvg
2
@MohammadAli:不要为CPU密集型工作的示例烦恼。Python在执行字节码时会使用一个大锁(全局解释器锁),因此您无法从线程中受益以进行CPU工作。您需要使用“multiprocessing”(即多个Python进程)。 - Kevin
2
你需要一款工业级工具来完成这个任务。从字符串列表的三叉树中生成正则表达式字典树,最多只需5步即可匹配成功,这是目前最快的匹配方法之一。例如:175,000单词词典,或者类似于您的禁用列表,只包含20,000个S-words - user12097764
显示剩余17条评论
10个回答

192

简短摘要

如果您想要最快的基于正则表达式的解决方案,请使用此方法。对于类似于 OP 的数据集,它比被接受的答案快大约 1000 倍。

如果您不关心正则表达式,请使用 这个基于集合的版本,它比正则表达式联合快 2000 倍。

优化的正则表达式与 Trie

当禁止词汇很多时,简单的正则表达式联合方法会变得很慢,因为正则表达式引擎无法很好地优化模式

可以创建一个包含所有禁止词汇的Trie并编写相应的正则表达式。生成的 Trie 或正则表达式并不是人类可读的,但它们确实允许非常快速的查找和匹配。

示例

['foobar', 'foobah', 'fooxar', 'foozap', 'fooza']

Regex union

将列表转换为Trie树:

{
    'f': {
        'o': {
            'o': {
                'x': {
                    'a': {
                        'r': {
                            '': 1
                        }
                    }
                },
                'b': {
                    'a': {
                        'r': {
                            '': 1
                        },
                        'h': {
                            '': 1
                        }
                    }
                },
                'z': {
                    'a': {
                        '': 1,
                        'p': {
                            '': 1
                        }
                    }
                }
            }
        }
    }
}

然后是这个正则表达式模式:

r"\bfoo(?:ba[hr]|xar|zap?)\b"

Regex trie

巨大的优势在于,为了测试zoo是否匹配,正则表达式引擎只需要比较第一个字符(它不匹配),而不是尝试5个单词。对于5个单词来说,这是一个预处理过度,但对于成千上万个单词来说,它显示出了有希望的结果。

请注意,使用(?:)非捕获组的原因是:

代码

这里有一个稍微修改过的 gist,我们可以将其作为 trie.py 库使用:

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())

测试

这是一个小测试(与此链接相同):

# Encoding: utf-8
import re
import timeit
import random
from trie import Trie

with open('/usr/share/dict/american-english') as wordbook:
    banned_words = [word.strip().lower() for word in wordbook]
    random.shuffle(banned_words)

test_words = [
    ("Surely not a word", "#surely_NöTäWORD_so_regex_engine_can_return_fast"),
    ("First word", banned_words[0]),
    ("Last word", banned_words[-1]),
    ("Almost a word", "couldbeaword")
]

def trie_regex_from_words(words):
    trie = Trie()
    for word in words:
        trie.add(word)
    return re.compile(r"\b" + trie.pattern() + r"\b", re.IGNORECASE)

def find(word):
    def fun():
        return union.match(word)
    return fun

for exp in range(1, 6):
    print("\nTrieRegex of %d words" % 10**exp)
    union = trie_regex_from_words(banned_words[:10**exp])
    for description, test_word in test_words:
        time = timeit.timeit(find(test_word), number=1000) * 1000
        print("  %s : %.1fms" % (description, time))

它输出:

TrieRegex of 10 words
  Surely not a word : 0.3ms
  First word : 0.4ms
  Last word : 0.5ms
  Almost a word : 0.5ms

TrieRegex of 100 words
  Surely not a word : 0.3ms
  First word : 0.5ms
  Last word : 0.9ms
  Almost a word : 0.6ms

TrieRegex of 1000 words
  Surely not a word : 0.3ms
  First word : 0.7ms
  Last word : 0.9ms
  Almost a word : 1.1ms

TrieRegex of 10000 words
  Surely not a word : 0.1ms
  First word : 1.0ms
  Last word : 1.2ms
  Almost a word : 1.2ms

TrieRegex of 100000 words
  Surely not a word : 0.3ms
  First word : 1.2ms
  Last word : 0.9ms
  Almost a word : 1.6ms

关于信息,正则表达式是这样开始的:

不需要翻译,这是一个正则表达式。

虽然它看起来很难读,但对于一份包含100000个禁用词的列表,这个Trie正则表达式比简单的正则表达式联合快1000倍!

这是完整Trie的图表,使用trie-python-graphviz和graphviz twopi导出:

Enter image description here


4
@XavierCombelle:你说得对,我应该提到捕获组:已更新答案。不过我却持相反的看法:使用|进行正则表达式选择时需要圆括号,但对于我们的目的并不需要捕获组。使用捕获组只会减慢处理速度,并增加内存占用而没有任何好处。 - Eric Duminil
4
@EricDuminil 这篇帖子非常完美,非常感谢 :) - Mohamed AL ANI
1
@MohamedALANI:与哪种解决方案相比? - Eric Duminil
如何将禁用的前缀添加到 Trie 中?例如,任何以前缀“bull”开头的单词都将与正则表达式匹配。 - Hyrial
1
@PV8:它只应匹配完整的单词,是的,这要归功于\b单词边界)。如果列表是['apple', 'banana'],它将替换完全是applebanana的单词,但不是nanabanapineapple - Eric Duminil
显示剩余11条评论

168

简述

如果您想要最快的解决方案,请使用此方法(使用set查找)。对于类似于OP的数据集,它的速度大约比接受的答案快2000倍。

如果您坚持使用正则表达式进行查找,请使用基于trie的版本,它仍然比正则表达式联合快1000倍。

理论

如果您的句子不是非常长的字符串,则可能可以每秒处理多个句子。

如果将所有被禁止的单词保存到一个set中,则检查另一个单词是否包含在该set中将非常快速。

将逻辑打包成一个函数,将此函数作为参数传递给re.sub,您就完成了!

代码

import re
with open('/usr/share/dict/american-english') as wordbook:
    banned_words = set(word.strip().lower() for word in wordbook)


def delete_banned_words(matchobj):
    word = matchobj.group(0)
    if word.lower() in banned_words:
        return ""
    else:
        return word

sentences = ["I'm eric. Welcome here!", "Another boring sentence.",
             "GiraffeElephantBoat", "sfgsdg sdwerha aswertwe"] * 250000

word_pattern = re.compile('\w+')

for sentence in sentences:
    sentence = word_pattern.sub(delete_banned_words, sentence)

转换后的句子如下:

' .  !
  .
GiraffeElephantBoat
sfgsdg sdwerha aswertwe

请注意:
- 搜索不区分大小写(感谢lower()) - 用""替换单词可能会留下两个空格(就像您的代码中一样) - 在Python3中,\w+也匹配带重音符号的字符(例如"ångström")。 - 任何非单词字符(制表符、空格、换行符、标记等)都将保持不变。
性能:
有一百万个句子,banned_words有近十万个单词,脚本运行时间不到7秒。
相比之下,Liteye的答案需要160秒才能处理1万个句子。
对于总单词数为n和禁止单词数为m,OP和Liteye的代码是O(n*m)
相比之下,我的代码应该以O(n+m)运行。考虑到句子比禁用单词多得多,该算法变成了O(n)
正则表达式联合测试:
使用'\b(word1|word2|...|wordN)\b'模式进行正则表达式搜索的复杂度是多少?是O(N)还是O(1)
很难理解正则表达式引擎的工作方式,因此让我们编写一个简单的测试。
此代码将10 ** i个随机英文单词提取到列表中。它创建相应的正则表达式联合,并使用不同的单词进行测试:
- 其中一个显然不是单词(以#开头) - 其中一个是列表中的第一个单词 - 其中一个是列表中的最后一个单词 - 其中一个看起来像一个单词,但实际上不是
import re
import timeit
import random

with open('/usr/share/dict/american-english') as wordbook:
    english_words = [word.strip().lower() for word in wordbook]
    random.shuffle(english_words)

print("First 10 words :")
print(english_words[:10])

test_words = [
    ("Surely not a word", "#surely_NöTäWORD_so_regex_engine_can_return_fast"),
    ("First word", english_words[0]),
    ("Last word", english_words[-1]),
    ("Almost a word", "couldbeaword")
]


def find(word):
    def fun():
        return union.match(word)
    return fun

for exp in range(1, 6):
    print("\nUnion of %d words" % 10**exp)
    union = re.compile(r"\b(%s)\b" % '|'.join(english_words[:10**exp]))
    for description, test_word in test_words:
        time = timeit.timeit(find(test_word), number=1000) * 1000
        print("  %-17s : %.1fms" % (description, time))

它的输出结果是:
First 10 words :
["geritol's", "sunstroke's", 'fib', 'fergus', 'charms', 'canning', 'supervisor', 'fallaciously', "heritage's", 'pastime']

Union of 10 words
  Surely not a word : 0.7ms
  First word        : 0.8ms
  Last word         : 0.7ms
  Almost a word     : 0.7ms

Union of 100 words
  Surely not a word : 0.7ms
  First word        : 1.1ms
  Last word         : 1.2ms
  Almost a word     : 1.2ms

Union of 1000 words
  Surely not a word : 0.7ms
  First word        : 0.8ms
  Last word         : 9.6ms
  Almost a word     : 10.1ms

Union of 10000 words
  Surely not a word : 1.4ms
  First word        : 1.8ms
  Last word         : 96.3ms
  Almost a word     : 116.6ms

Union of 100000 words
  Surely not a word : 0.7ms
  First word        : 0.8ms
  Last word         : 1227.1ms
  Almost a word     : 1404.1ms

看起来搜索一个符合 '\b(word1|word2|...|wordN)\b' 模式的单词具有以下性能:

  • O(1) 最好情况
  • O(n/2) 平均情况,仍然是 O(n)
  • O(n) 最坏情况

这些结果与简单循环搜索一致。

一个比正则表达式联合更快的替代方法是从 trie 中创建正则表达式模式


1
你是正确的。我的缩进有误。我已经在原问题中修正了它。至于每秒50个句子速度慢的评论,我只能说我提供的是一个简化的例子。真实数据集比我描述的更复杂,但似乎不相关。此外,将我的“单词”连接成一个正则表达式大大提高了速度。同时,在替换后我还去除了双空格。 - pdanese
1
@user36476 感谢您的反馈,我已经删除了相应部分。您能否试一下我的建议?我敢说它比被接受的答案快得多。 - Eric Duminil
3
既然你删除了那个误导性的 O(1) 声称,你的回答绝对值得点赞。 - idmean
2
@idmean:确实,那句话表达得不太清楚。它只是指查找:“这个词是被禁用的吗?” - Eric Duminil
2
@EricDuminil:做得好!希望我能再次点赞。 - Matthieu M.
显示剩余7条评论

140

你可以尝试的一件事是编译一个单一的模式,例如"\b(word1|word2|word3)\b"

因为re依赖于C代码来进行实际匹配,因此节约的时间可能是惊人的。

正如@pvg在评论中指出的那样,它也受益于单次匹配。

如果你的单词不是正则表达式,Eric的答案更快。


4
这不仅仅与C实现有关(这很重要),还因为你只需要一次匹配就能完成。类似的问题经常出现,但奇怪的是(或许有,只是隐藏得比较深?)没有一个权威的SO答案提出这个很明智的想法。 - pvg
48
@Liteye您的建议将一个需要4小时完成的工作转变为了只需4分钟!我成功地将所有20,000多个正则表达式合并为一个巨大的正则表达式,而我的笔记本电脑没有出现任何问题。再次感谢您。 - pdanese
2
@Bakuriu: s/They actually use/They actually could in theory sometimes use/. 你有没有理由相信Python的实现在这里做了除了循环以外的其他事情? - user541686
2
@Bakuriu:我真的很想知道是否是这种情况,但我不认为正则表达式解决方案需要线性时间。如果它没有将Union建立为Trie,我不明白它怎么可能发生。 - Eric Duminil
2
@Bakuriu:那不是一个理由。我问的是你有没有理由相信实现确实会以那种方式运行,而不是你有没有理由相信它可能会以那种方式运行。就个人而言,我还没有遇到过任何一种主流编程语言的正则表达式实现,能够像经典的正则表达式一样在线性时间内工作,所以如果你知道Python可以做到这一点,你应该提供一些证据。 - user541686
显示剩余11条评论

17

你可能想尝试的一件事是对句子进行预处理,以编码单词边界。基本上,通过在单词边界处分割,将每个句子转换为单词列表。

这样应该更快,因为要处理一个句子,只需要逐个遍历每个单词并检查是否匹配。

目前,正则表达式搜索需要每次重新遍历整个字符串,寻找单词边界,然后“丢弃”此工作的结果,才能进行下一次搜索。


15

好的,这里是一个带有测试集的快速简单解决方案。

最佳策略:

  • re.sub("\w+",repl,sentence) 搜索单词。
  • "repl" 可以是可调用对象。我使用了一个执行字典查找的函数,并且该字典包含要搜索和替换的单词。
  • 这是最简单、最快速的解决方案 (参见下面示例代码中的 replace4 函数)。

次佳策略:

  • 思路是使用 re.split 将句子分割为单词,同时保留分隔符以后重构句子。然后,使用简单的字典查找进行替换。
  • 实现方法: (参见下面示例代码中的 replace3 函数)。

示例函数计时:

replace1:     0.62 sentences/s
replace2:     7.43 sentences/s
replace3: 48498.03 sentences/s
replace4: 61374.97 sentences/s (...and 240,000/s with PyPy)

...和代码:

#! /bin/env python3
# -*- coding: utf-8

import time, random, re

def replace1( sentences ):
    for n, sentence in enumerate( sentences ):
        for search, repl in patterns:
            sentence = re.sub( "\\b"+search+"\\b", repl, sentence )

def replace2( sentences ):
    for n, sentence in enumerate( sentences ):
        for search, repl in patterns_comp:
            sentence = re.sub( search, repl, sentence )

def replace3( sentences ):
    pd = patterns_dict.get
    for n, sentence in enumerate( sentences ):
        #~ print( n, sentence )
        # Split the sentence on non-word characters.
        # Note: () in split patterns ensure the non-word characters ARE kept
        # and returned in the result list, so we don't mangle the sentence.
        # If ALL separators are spaces, use string.split instead or something.
        # Example:
        #~ >>> re.split(r"([^\w]+)", "ab céé? . d2eéf")
        #~ ['ab', ' ', 'céé', '? . ', 'd2eéf']
        words = re.split(r"([^\w]+)", sentence)
        
        # and... done.
        sentence = "".join( pd(w,w) for w in words )

        #~ print( n, sentence )

def replace4( sentences ):
    pd = patterns_dict.get
    def repl(m):
        w = m.group()
        return pd(w,w)
    
    for n, sentence in enumerate( sentences ):
        sentence = re.sub(r"\w+", repl, sentence)



# Build test set
test_words = [ ("word%d" % _) for _ in range(50000) ]
test_sentences = [ " ".join( random.sample( test_words, 10 )) for _ in range(1000) ]

# Create search and replace patterns
patterns = [ (("word%d" % _), ("repl%d" % _)) for _ in range(20000) ]
patterns_dict = dict( patterns )
patterns_comp = [ (re.compile("\\b"+search+"\\b"), repl) for search, repl in patterns ]


def test( func, num ):
    t = time.time()
    func( test_sentences[:num] )
    print( "%30s: %.02f sentences/s" % (func.__name__, num/(time.time()-t)))

print( "Sentences", len(test_sentences) )
print( "Words    ", len(test_words) )

test( replace1, 1 )
test( replace2, 10 )
test( replace3, 1000 )
test( replace4, 1000 )

编辑:当您检查是否通过小写句子列表并编辑repl时,还可以忽略小写字母。

def replace4( sentences ):
pd = patterns_dict.get
def repl(m):
    w = m.group()
    return pd(w.lower(),w)

1
测试点赞。replace4 和我的代码性能相似。 - Eric Duminil
1
不确定 def repl(m): 函数的作用以及如何在 replace4 函数中分配 m - StatguyUser
同时,我在以下代码行中遇到了错误 error: unbalanced parenthesispatterns_comp = [ (re.compile("\\b"+search+"\\b"), repl) for search, repl in patterns ] - StatguyUser
虽然 replace3replace4 函数处理了原始问题(替换词语),但 replace1replace2 更通用,因为它们即使针对的是短语(一系列词语)而不仅仅是单个单词也能正常工作。 - Zoltan Fedor

7
也许 Python 并不是这里的正确工具。这里有一个带有 Unix 工具链的工具。
sed G file         |
tr ' ' '\n'        |
grep -vf blacklist |
awk -v RS= -v OFS=' ' '{$1=$1}1'

假设您的黑名单文件已添加了单词边界。操作步骤如下:将文件转换为双倍间距,将每个句子拆分为每行一个单词,从文件中批量删除黑名单单词,并将行合并回去。
这样可以至少快十倍。
预处理黑名单文件的方法是:将单词(每个单词一行)转换成文件。
sed 's/.*/\\b&\\b/' words > blacklist

5
这个怎么样:
#!/usr/bin/env python3

from __future__ import unicode_literals, print_function
import re
import time
import io

def replace_sentences_1(sentences, banned_words):
    # faster on CPython, but does not use \b as the word separator
    # so result is slightly different than replace_sentences_2()
    def filter_sentence(sentence):
        words = WORD_SPLITTER.split(sentence)
        words_iter = iter(words)
        for word in words_iter:
            norm_word = word.lower()
            if norm_word not in banned_words:
                yield word
            yield next(words_iter) # yield the word separator

    WORD_SPLITTER = re.compile(r'(\W+)')
    banned_words = set(banned_words)
    for sentence in sentences:
        yield ''.join(filter_sentence(sentence))


def replace_sentences_2(sentences, banned_words):
    # slower on CPython, uses \b as separator
    def filter_sentence(sentence):
        boundaries = WORD_BOUNDARY.finditer(sentence)
        current_boundary = 0
        while True:
            last_word_boundary, current_boundary = current_boundary, next(boundaries).start()
            yield sentence[last_word_boundary:current_boundary] # yield the separators
            last_word_boundary, current_boundary = current_boundary, next(boundaries).start()
            word = sentence[last_word_boundary:current_boundary]
            norm_word = word.lower()
            if norm_word not in banned_words:
                yield word

    WORD_BOUNDARY = re.compile(r'\b')
    banned_words = set(banned_words)
    for sentence in sentences:
        yield ''.join(filter_sentence(sentence))


corpus = io.open('corpus2.txt').read()
banned_words = [l.lower() for l in open('banned_words.txt').read().splitlines()]
sentences = corpus.split('. ')
output = io.open('output.txt', 'wb')
print('number of sentences:', len(sentences))
start = time.time()
for sentence in replace_sentences_1(sentences, banned_words):
    output.write(sentence.encode('utf-8'))
    output.write(b' .')
print('time:', time.time() - start)

这些解决方案根据单词边界拆分并查找每个单词所在的集合。它们应该比基于单词替代的re.sub(Liteyes的解决方案)更快,因为这些解决方案是O(n),其中n是输入大小,由于摊销的O(1)集合查找,而使用正则表达式替代将导致正则表达式引擎必须在每个字符上检查单词匹配,而不仅仅是在单词边界上匹配。我的解决方案特别注意保留原始文本中使用的空格(即不压缩空格并保留制表符、换行符和其他空格字符),但如果您决定不在输出中保存它们,那么删除它们应该很简单。
我测试了corpus.txt,它是从古腾堡计划下载的多个电子书的串联,banned_words.txt中随机选择了20000个单词,这些单词来自Ubuntu的单词列表(/usr/share/dict/american-english)。处理862462个句子需要约30秒(在PyPy上只需一半时间)。我将句子定义为用“.”分隔的任何东西。
$ # replace_sentences_1()
$ python3 filter_words.py 
number of sentences: 862462
time: 24.46173644065857
$ pypy filter_words.py 
number of sentences: 862462
time: 15.9370770454

$ # replace_sentences_2()
$ python3 filter_words.py 
number of sentences: 862462
time: 40.2742919921875
$ pypy filter_words.py 
number of sentences: 862462
time: 13.1190629005

PyPy从第二种方法中受益更多,而CPython在第一种方法中表现更好。上面的代码应该适用于Python 2和3。


Python 3是问题中的必备条件。我点了赞,但我认为牺牲一些细节和“最优”实现来使代码更简洁可能是值得的。 - pvg
如果我理解正确的话,这基本上是和我的答案相同的原理,只不过更冗长了?在\W+上进行分割和连接基本上就像在\w+上进行sub操作,对吧? - Eric Duminil
我想知道我的解决方案 below (function replace4) 是否比pypy快;) 我想在您的文件上测试! - bobflux

3

实用方法

下面描述的解决方案使用大量内存来将所有文本存储在同一字符串中并降低复杂度级别。如果RAM是一个问题,请三思而后行。

使用 join/split 技巧可以避免循环,从而加快算法的速度。

  • 使用一个特殊的分隔符将句子连接起来,该分隔符不包含在句子中:
  • merged_sentences = ' * '.join(sentences)
    

    编译一个正则表达式,使用“|”或运算符号来清除句子中所有需要去除的单词。
    regex = re.compile(r'\b({})\b'.format('|'.join(words)), re.I) # re.I is a case insensitive flag
    

  • 使用编译后的正则表达式对单词进行下标标记,然后通过特殊分隔符将其拆分成独立的句子:
  • clean_sentences = re.sub(regex, "", merged_sentences).split(' * ')
    

    性能

    "".join 的复杂度为 O(n)。这很直观,但是我们还是引用了一份缩略版的来源:

    for (i = 0; i < seqlen; i++) {
        [...]
        sz += PyUnicode_GET_LENGTH(item);
    

    因此,使用 join/split,您的复杂度为 O(words) + 2*O(sentences),仍然是线性复杂度,相对于初始方法的 2*O(N2)。
    顺便说一下,请不要使用多线程。因为您的任务是严格绑定在 CPU 上的,所以 GIL 将阻止每个操作,但是每个线程将同时发送 ticks,这会导致额外的工作甚至导致操作无限期地进行下去。

    如果句子已存储在文本文件中,则它们已经通过换行符分隔。因此,整个文件可以作为一个大字符串(或缓冲区)读取,删除单词,然后再写回去(或者可以直接在文件中使用内存映射来完成此操作)。另一方面,要删除一个单词,字符串的其余部分必须向后移动以填补空白,因此在处理一个非常大的字符串时会遇到问题。另一种选择是将单词之间的部分写回到另一个字符串或文件中(其中包括换行符),或者只是在 mmapped 文件中移动这些部分。 - Danny_ds
    那种最后的方法(将单词之间的部分移动/写入),再加上Eric Duminil的集合查找,可能非常快,甚至可以完全不使用正则表达式。 - Danny_ds
    或许正则表达式已经针对替换多个单词时仅移动这些部分进行了优化,我不确定。 - Danny_ds

    1
    将所有句子连接成一个文档。使用任何一种实现Aho-Corasick算法的方法(这里有一个)来定位所有“坏”单词。遍历文件,替换每个坏单词,更新后续发现的单词的偏移量等。

    1
    使用LRU缓存!在生产环境中,我对正则表达式搜索获得了60倍的速度提升。缓存(也称为记忆化)通过保存昂贵计算的结果来工作,因此如果数据集中存在重复项(很可能存在),您将获得最佳的速度提升。
    import re
    from functools import lru_cache
    
    @lru_cache
    def expensive_regex(str):
       return re.findall('pattern', str)
    

    快来看看:2行代码加速127倍的正则表达式,由我亲自撰写 :)

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接