如前所述,有不同的方法,每种方法都有不同的优点。我使用三种不同的情况进行比较。
- 短字典(847个替换对)
- 中等字典(2528个替换对)
- 长字典(80430个替换对)
对于字典1和2(较短的字典),我在循环中重复每种方法50次,以获得更一致的时间。 对于较长的字典,一个文档的单次通过需要足够长的时间(可悲的是)。 我使用Python 3.8在
service tio上测试了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.63
- 5.03
- 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和赋值表达式的结果):
- 1.37(1.39)(1.50)
- 4.10(4.12)(4.07)
- 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)
执行时间为:
- 0.07
RecursionError
RecursionError
实用备注
请参见上面的更新:Flashtext比其他替代方案快得多。
从执行时间可以看出,递归方法显然是最快的,但它只适用于小型字典。在Python中不建议增加递归深度,因此这种方法对于更长的字典完全被舍弃。
正则表达式可提供更多控制替换的选项。例如,您可以在元素前或后使用\b
来确保目标子字符串的那一侧没有单词字符(以防止{'a': '1'}被应用于'apple')。代价是性能急剧下降,比其他选项慢近四倍。
赋值表达式、
reduce 和简单的循环
replace 提供了类似的性能(无法使用较长的字典测试赋值表达式)。考虑到可读性,
string.replace
似乎是最好的选择。与正则表达式相比,问题在于替换是按顺序进行的,而不是一次性完成的。因此,对于字符串 'a',{'a': 'b', 'b': 'c'} 返回 'c'。Python 中现在有序字典(但您可能希望继续使用 OrderedDict),因此您可以仔细设置替换的顺序以避免出现问题。当然,对于 80k 个替换,您不能依靠这一点。
我目前正在使用带有 replace 的循环,并进行一些预处理以最小化麻烦。我在标点符号两侧添加空格(包含标点符号的项也在字典中),然后可以搜索由空格包围的子字符串,并插入带有空格的替换。当您的目标是多个单词时,这也适用:
string = 'This is: an island'
my_dict = {'is': 'is not', 'an island': 'a museum'}
使用replace和正则表达式,我得到了
string = ' This is : an island '
,这样我的替换循环就可以进行了。
for original, replacement in self.my_dict.items():
string = string.replace(f' {original} ', f' {replacement} ')
返回
'这不是博物馆'
,如预期。请注意,“This”和“island”中的“is”未更改。可以使用正则表达式将标点符号修复回来,但我不需要这一步骤。
"abc", {"ab":"x","bc":"y"}
更改为"there is a", {"there is":"there was","is a":"isn't a"}
。否则,一个简单的split -> lookup -> join
方法将具有更好的性能。 - tsh