如何使用字典对字符串进行多次替换?

How can I make multiple replacements in a string using a dictionary?

提问人:meder omuraliev 提问时间:3/8/2010 最后编辑:Karl Knechtelmeder omuraliev 更新时间:11/18/2023 访问量:78805

问:

假设我们有:

d = {
    'Спорт':'Досуг',
    'russianA':'englishA'
}

s = 'Спорт russianA'

我怎样才能用相应的值替换任何键中的每个外观(在这种情况下,结果将是)?sd'Досуг englishA'

Python 正则表达式

评论

1赞 Joe 3/8/2010
这可能不是那么简单。您可能应该有一个显式的分词器(例如和“caterpillar”)。还有重叠的词(和“地毯”)。{'cat': 'russiancat'}{'car':'russiancar', 'pet' : 'russianpet'}
2赞 ChristopheD 3/8/2010
另请参阅 code.activestate.com/recipes/81330-single-pass-multiple-replace

答:

5赞 ghostdog74 3/8/2010 #1

一种方式,没有 RE

d = {
'Спорт':'Досуг',
'russianA':'englishA'
}

s = 'Спорт russianA'.split()
for n,i in enumerate(s):
    if i in d:
        s[n]=d[i]
print ' '.join(s)

评论

3赞 James 2/2/2016
如果字典的键中有空格,这将失败
3赞 extraneon 3/8/2010 #2

与 ghostdog74 几乎相同,但独立创建。一个区别, 使用 d.get() 代替 d[] 可以处理不在字典中的项目。

>>> d = {'a':'b', 'c':'d'}
>>> s = "a c x"
>>> foo = s.split()
>>> ret = []
>>> for item in foo:
...   ret.append(d.get(item,item)) # Try to get from dict, otherwise keep value
... 
>>> " ".join(ret)
'b d x'
26赞 codeape 3/8/2010 #3

您可以使用reduce函数:

reduce(lambda x, y: x.replace(y, dict[y]), dict, s)

评论

17赞 jochen 11/16/2012
与 @Max Shawabkeh 的解决方案不同,使用 一个接一个地应用替换。因此,使用字典交换单词不适用于基于 - 的方法,并且重叠的匹配项会以不可预测的方式转换。reduce{ 'red': 'green', 'green': 'red'}reduce
2赞 Mattie 6/26/2013
一个很好的例子,说明为什么重复调用可能会产生意想不到的后果: —试一试。.replace()html.replace('"', '"').replace('&', '&')html = '"foo"'
1赞 poke 8/7/2017
ChristopheDuser2769207 的回答中所示的展开循环相比,这是不必要的复杂和不可读的。
111赞 Max Shawabkeh 3/8/2010 #4

使用 re:

import re

s = 'Спорт not russianA'
d = {
'Спорт':'Досуг',
'russianA':'englishA'
}
keys = (re.escape(k) for k in d.keys())
pattern = re.compile(r'\b(' + '|'.join(keys) + r')\b')
result = pattern.sub(lambda x: d[x.group()], s)
# Output: 'Досуг not englishA'

这将仅匹配整个单词。如果不需要,请使用以下模式:

pattern = re.compile('|'.join(re.escape(k) for k in d.keys()))

请注意,在这种情况下,如果某些字典条目是其他字典条目的子字符串,则应按长度对单词进行降序排序。

评论

27赞 jochen 11/16/2012
如果字典键包含“^”、“$”和“/”等字符,则需要在组装正则表达式之前对键进行转义。为此,可以替换为 ..join(d.keys()).join(re.escape(key) for key in d.keys())
0赞 林果皞 12/30/2014
请注意,第一个示例(Досуг not englishA)仅适用于 python3。在 python2 中,它仍然返回我“Спорт not englishA”
0赞 Peter.k 3/14/2019
当字典中的单词有点时,它似乎失败了 - - 我需要在最后删除,但不确定它是否正确。https://regex101.com/r/bliVUS/1\b
0赞 Galigator 7/13/2023
通过 x2 因子进行替换的最快方法。这也是最正确的方法。
21赞 ChristopheD 3/8/2010 #5

在这里找到的解决方案(我喜欢它的简单性):

def multipleReplace(text, wordDict):
    for key in wordDict:
        text = text.replace(key, wordDict[key])
    return text

评论

12赞 Chris 2/18/2013
同样,正如@jochen所描述的,如果有一个键也是一个值,这可能会导致错误的翻译。最好是单程更换。
0赞 user2769207 9/12/2013 #6

我在类似的情况下使用它(我的字符串都是大写的):

def translate(string, wdict):
    for key in wdict:
        string = string.replace(key, wdict[key].lower())
    return string.upper()

希望这在某种程度上有所帮助...... :)

评论

3赞 hynekcer 9/12/2013
这与ChristopheD的解决方案非常相似。你不同意他的观点吗?
1赞 Anton vBR 11/21/2017 #7

如果密钥有空间,则警告它失败,这是一个类似于 ghostdog74 的压缩解决方案,extaneons 回答:

d = {
'Спорт':'Досуг',
'russianA':'englishA'
}

s = 'Спорт russianA'

' '.join(d.get(i,i) for i in s.split())
0赞 Karl Knechtel 2/5/2023 #8

使用正则表达式

我们可以通过创建正则表达式来匹配每个单独的键并将它们与 .我们习惯于进行替换,通过给它一个函数来执行替换(当然,这个函数将执行字典查找)。把它放在一起:|re.sub

import re

# assuming global `d` and `s` as in the question

# a function that does the dict lookup with the global `d`.
def lookup(match):
    return d[match.group()]

# Make the regex.
joined = '|'.join(re.escape(key) for key in d.keys())
pattern = re.compile(joined)

result = pattern.sub(lookup, s)

此处,用于转义替换中具有特殊含义的任何字符(以便它们不会干扰正则表达式的构建,并且按字面意思匹配)。re.escape

此正则表达式模式将与子字符串出现的任何位置匹配,即使它们是单词的一部分或跨越多个单词。为避免这种情况,请修改正则表达式,使其检查单词边界:

# pattern = re.compile(joined)
pattern = re.compile(rf'\b({joined})\b')

迭代使用str.replace

只需遍历查找字典,然后调用每个字典。由于此方法返回一个新字符串,并且不会(不能)就地修改字符串,因此我们必须在循环中重新分配结果:.items().replace

for to_replace, replacement in d.items():
    s = s.replace(to_replace, replacement)

这种方法编写简单且易于理解,但它带有多个注意事项。

首先,它的缺点是它按特定顺序工作。也就是说,每个替换都有可能干扰其他替换。考虑:

s = 'one two'
s = s.replace('one', 'two')
s = s.replace('two', 'three')

这将产生 ,而不是 ,因为来自第一个替换的 本身将在第二步中被替换。这通常是不可取的;但是,在极少数情况下,当它应该以这种方式工作时,这种方法是唯一实用的方法。'three three''two three''two'

这种方法也不容易修复以尊重单词边界,因为它必须与文字文本匹配,并且“单词边界”可以以多种不同的方式标记 - 通过不同类型的空格,但也可以在字符串的开头和结尾没有文本

最后,请记住,对于这种方法来说,a 并不是理想的数据结构。如果我们要遍历字典,那么它进行键查找的能力是无用的;而在 Python 3.5 及以下版本中,dicts 的顺序是无法保证的(使顺序替换问题变得更糟)。相反,最好为替换指定一个元组列表:dict

d = [('Спорт', 'Досуг'), ('russianA', 'englishA')]
s = 'Спорт russianA'

for to_replace, replacement in d: # no more `.items()` call
    s = s.replace(to_replace, replacement)

通过标记化

如果首先将字符串切成碎片(标记化),问题就会变得简单得多,这样一来,任何应该被替换的东西现在都与字典键完全匹配。这将允许直接使用字典的查找,并一次性处理整个字符串,同时也不需要构建自定义正则表达式。

假设我们想要匹配完整的单词。我们可以使用一个更简单的硬编码正则表达式,它将匹配空格,并使用捕获组;通过将其传递给 ,我们将字符串拆分为空格和非空格部分。因此:re.split

import re

tokenizer = re.compile('([ \t\n]+)')
tokenized = tokenizer.split(s)

现在我们在字典中查找每个标记:如果存在,则应将其替换为相应的值,否则应将其保留(相当于将其替换为自身)。字典方法非常适合这项任务。最后,我们将各个部分重新连接起来。因此:.get

s = ''.join(d.get(token, token) for token in tokenized)

更一般地说,例如,如果要替换的字符串中可能包含空格,则需要不同的标记化规则。但是,通常可以提出一个比第一部分中的正则表达式更简单的标记化规则(通过蛮力匹配所有键)。

特殊情况:替换单个字符

如果字典的键都是一个字符(从技术上讲,Unicode 码位),则可以使用更具体的技术。有关详细信息,请参阅替换字符串中多个字符的最佳方法?

0赞 Robert Nowak 11/18/2023 #9

一点点 lambda ...

# list of tuples version
(mrep:=lambda s,l: s if not l else mrep(s.replace(*l.pop()), l))

# dictionary version
(lambda s,d: (mrep:=lambda s,l : s if not l else
    mrep(s.replace(*l.pop()), l))(s, list(d.items())))

# "universal" list/dict version
(lambda s,u: (mrep:=lambda s,l: s if not l else
    mrep(s.replace(*l.pop()), l))(s, u if type(u)==type(list())
       else list(u.items())))