当前位置: 首页 > 面试题库 >

有效地在字符串中进行多次替换

王成化
2023-03-14
问题内容

人们已经解决了如何基于字典对字符串进行多次替换的问题(例如,参见)。似乎有一组基于选项string.replace,一组基于正则表达式的选项,还有更多选项。

但是我对取决于字典大小的不同方法的效率感兴趣,我发现这具有非常重要的影响。

my_subs = {'Hello world': 'apple', 'is': 'ship'}
string = 'Hello world! This is nice.'
new_string = my_efficient_method(string, my_subs)

明确地说,这个问题 不是 关于如何进行这些替换,而是关于哪种方法在哪些条件下以及哪些警告适用的情况下更有效。特别是,当有很多(>
100k)字符串很长(10-20k个字符)并且字典很大(> 80k对)时,我正在寻找最实用的方法。在这些情况下,基于正则表达式的首选方法执行效果非常差。


问题答案:

如前所述,有不同的方法,每种方法都有不同的优势。我正在使用三种不同的情况进行比较。

  1. 短字典(847个替换对)
  2. 中型词典(2528对)
  3. 长字典(80430对)

对于字典1和2(较短的字典),我将每个方法循环重复50次,以获得更一致的时间安排
。如果文件越长,则一次通过一个文档所花费的时间就越长(可悲)。我使用在线服务tio和Python
3.8测试了1和2 。长长的一个在我的笔记本电脑上使用Python 3.6进行了测试。方法之间的相对性能是相关的,因此次要细节并不重要。

我的字符串介于28k和29k之间。

所有时间以秒为单位。

更新:Flashtext

一位同事找到了Flashtext,这是专门针对此问题的Python库。它允许通过查询进行搜索,也可以应用替换。它比其他替代方案快两个数量级。
在实验3中,我目前的最佳时间是1.8秒。 Flashtext需要0.015秒

常用表达

有很多变体,但是最好的变体与此非常相似:

import re
rep = dict((re.escape(k), v) for k, v in my_dict.items())
pattern = re.compile("|".join(rep.keys()))
new_string = pattern.sub(lambda m: rep[re.escape(m.group(0))], string)

执行时间为:

  1. 1.63
  2. 5.03
  3. 7.7

更换

此方法仅适用string.replace于循环。(稍后我讨论与此有关的问题。)

for original, replacement in self.my_dict.items():
    string = string.replace(original, replacement)

此解决方案使用提出了一个变体reduce,该变体可迭代地应用Lambda表达式。通过官方文档中的示例可以最好地理解这一点。表达方式

reduce(lambda x, y: x+y, [1, 2, 3, 4, 5])

等于(((((1 + 2)+3)+4)+5)

import functools
new_string = functools.reduce(lambda a, k: a.replace(*k), 
                              my_dict.items(), string)

像这种方法一样,Python
3.8允许赋值表达式。它的核心也依赖于。string.replace

[string := string.replace(f' {a} ', f' {b} ') for a, b in my_dict.items()]

执行时间为(括号 结果 为reduce和 assigning expression的 变体):

  1. 1.37(1.39)(1.50)
  2. 4.10(4.12)(4.07)
  3. 1.9(1.8)(机器上没有Python 3.8)

递归Lambda

该建议涉及使用递归Lambda。

mrep = lambda s, d: s if not d else mrep(s.replace(*d.popitem()), d)
new_string = mrep(string, my_dict)

执行时间为:

  1. 0.07
  2. RecursionError
  3. RecursionError

请参阅上面的更新:Flashtext 比其他替代方法快得多。

从执行时间可以看出,递归方法显然是最快的,但仅适用于小型词典。这是不建议增加递归深度多在Python,所以这种方式完全抛弃更长的字典。

正则表达式可以更好地控制您的替换。例如,您可以\b在元素之前或之后使用来确保目标子字符串的该侧没有单词字符(以防止将{‘a’:‘1’}应用于’apple’)。代价是,对于较长的词典来说,性能会急剧下降,几乎是其他选项的四倍。

赋值表达式reduce 和简单地循环 替换
提供了类似的性能(赋值表达式无法使用更长的字典进行测试)。考虑到可读性,这string.replace似乎是最佳选择。与正则表达式相比,这样做的问题是替换是顺序发生的,而不是单步执行。因此{‘a’:’b’,’b’:’c’}为字符串’a’返回’c’。现在已经在Python中对字典进行了排序(但是您可能希望使用OrderedDict保持),因此您可以仔细设置替换顺序以避免出现问题。当然,对于80k替换,您不能依靠它。

我目前正在使用带有替换的循环,并进行了一些预处理以最大程度地减少麻烦。我在标点符号的两侧都添加了空格(也在字典中包含标点符号的项目中)。然后,我可以搜索用空格包围的子字符串,并用空格插入替换项。当您的目标是多个单词时,这也适用:

string = 'This is: an island'
my_dict = {'is': 'is not', 'an island': 'a museum'}

通过使用replace和正则表达式,我得到string = ' This is : an island '了replace循环

for original, replacement in self.my_dict.items():
    string = string.replace(f' {original} ', f' {replacement} ')

返回' This is not : a museum '预期的结果。请注意,“ This”和“ island”中的“
is”是单独保留的。尽管我不需要此步骤,但是可以使用正则表达式来修复标点符号。



 类似资料:
  • 在卸载时,需要恢复从注册中的某个项目中删除掉添加的内容,这可以使用字符串替换来实现,即使用空串替换掉添加的内容就可以了。NSIS本身提供的字符串函数是很少的,大多数字符串函数都是借助它强大的宏功能实现的。字符串替换可以使用StrRep来实现。StrRep函数包含在StrFunc.nsh文件中。在NSIS的例子中,有一个StrFunc.nsi就是介绍如何使用这些函数的。要实现字符串函数,需要包含St

  • 问题内容: 在JavaScript中有一个简单的等效项吗? 这是使用PHP的,它允许您使用单词数组来查找和替换。我可以使用JavaScript / jQuery做类似的事情吗? 问题答案: 您可以使用自己的函数来扩展String对象,该函数可以满足您的需要(如果缺少功能,则很有用): 对于全局替换,您可以使用正则表达式: 要使用该功能,它将类似于您的PHP示例:

  • 问题内容: 说我有一个文件,其中包含一些文本。其中包含子字符串,例如“ substr1”,“ substr2”,“ substr3”等。我需要将所有这些子字符串替换为其他文本,例如“ repl1”,“ repl2”,“ repl3”。在Python中,我将创建一个这样的字典: 并创建用’|’组合键的模式,然后替换为function。在Java中有类似的简单方法吗? 问题答案: 这是您的Python

  • 问题内容: 我需要以最有效的方式替换字符串中的许多不同子字符串。除了使用string.replace替换每个字段的强力方法以外,还有其他方法吗? 问题答案: 如果你要处理的字符串很长,或者你要处理许多字符串,那么使用java.util.regex.Matcher可能是值得的(这需要花很长时间进行编译,因此效率不高) (如果你的输入很小或搜索模式经常更改)。 以下是一个完整的示例,基于从地图中获取的

  • 我需要以最有效的方式替换一个字符串中许多不同的子字符串。除了使用string.replace替换每个字段的蛮力方法之外,还有其他方法吗?

  • 问题内容: 对于穷人在客户端上实现近似排序规则正确排序的实现,我需要一个JavaScript函数,该函数可以 有效地 替换字符串中的单个字符。 这就是我的意思(请注意,这适用于德语文本,其他语言则有不同的排序方式): 基本上,我需要将给定字符串的所有出现的“ä”替换为“ a”(依此类推)。这样,本机排序的结果将非常接近用户的期望(或数据库将返回的结果)。 其他语言也具有执行此操作的功能:Pytho

  • 问题内容: 在Go中,a 是原始类型,这意味着它是只读的,对其的每次操作都会创建一个新的字符串。 因此,如果我想多次连接字符串而又不知道结果字符串的长度,那么最好的方法是什么? 天真的方法是: 但这似乎不是很有效。 问题答案: 新方法: 在Go 1.10+中strings.Builder,这里是。 生成器用于使用Write方法有效地构建字符串。它最大程度地减少了内存复制。零值可以使用了。 与几乎相

  • 问题内容: 我正在编写一个小型JAVA程序,该程序: 将文本作为字符串 需要2个字符数组 我试图做的事情听起来像是“查找并替换”,但是并不相同,因此我认为清除它很重要。 无论如何,我想获取此文本,查找第一个数组中的任何字符是否与文本中的字符匹配,如果是,则将其替换为第二个字符数组中的匹配字符(根据索引)。 我将举一个例子来说明:假设我的文本(字符串)是:“ java很棒!”;我有2个数组(char