如何使用字典进行多次搜索和替换操作?

96

我需要在地址字段中替换类似于“north”、“south”等的文本为“N”、“S”等。我想过用字典来保存要替换的内容。假设我们有:

replacements = {'NORTH':'N','SOUTH':'S','EAST':'E','WEST':'W'}
address = "123 north anywhere street"

我可以使用replacements字典来进行所有替换,例如通过迭代它吗?这样做的代码是什么样子?


3
如果匹配可以重叠,这就相当棘手。参见这个问题 - georg
问题的一个很大的部分是,字符串replace()方法会返回一个替换了出现次数的字符串的副本 - 它不会原地进行替换。 - martineau
4
你可以简单地使用str.translate - Neel Patel
3
请查看以下链接以获取最佳解决方案:https://dev59.com/5HE95IYBdhLWcg3wOLU1 - Ethan Bradford
经过审核:在删除代码尝试后(由于存在多个问题,几乎可以视为伪代码;而且并没有帮助理解问题,因为这显然是一个如何问题,而不是一个调试问题),很明显这是@EthanBradford找到的问题的重复。即使在我的编辑之后,我认为另一个问题(及其答案)总体上更高质量,所以我将其关闭为重复。 - Karl Knechtel
13个回答

69
address = "123 north anywhere street"

for word, initial in {"NORTH":"N", "SOUTH":"S" }.items():
    address = address.replace(word.lower(), initial)
print address

好的,简洁易读。


这似乎是标准方法。我很好奇XML解析器是如何做到的,而且在import xml.sax.saxutils as su; print(inspect.getsource(su.escape))中看到了相同的方法,这让我们想到了print(inspect.getsource(su.__dict_replace)) - C8H10N4O2
如果你的字典比较大,考虑反转字符串并迭代它。 - Akaisteph7

30

你很接近了,实际上是:

dictionary = {"NORTH":"N", "SOUTH":"S" } 
for key in dictionary.iterkeys():
    address = address.upper().replace(key, dictionary[key])

注意: 对于Python 3用户,您应该使用.keys()而不是.iterkeys():

dictionary = {"NORTH":"N", "SOUTH":"S" } 
for key in dictionary.keys():
    address = address.upper().replace(key, dictionary[key])

1
非常简单且有效的方法是使用字典进行替换。对我来说,这已经足够了。 - Alexandre Andrade
简洁易懂,对我来说恰到好处。 - msarafzadeh
12
这句话的意思是什么?address.upper().replace(...)并不会就地修改任何内容,它只是返回一个值,并且没有被分配给任何变量。 - Enrico Borba
2
如果你想的话,可以使用 for key, value in dictionary.items() 同时迭代字典的键和值。我不知道这样做是否有性能上的优势,但我认为这更符合 Python 的风格。 - gionni
for循环的缺点是它会创建替换排序问题,例如当你有字符串Do you like café? No, I prefer tea.并且你执行.replace("café", "tea")和.replace("tea", "café")时,你将得到Do you like café? No, I prefer café.。如果替换只在一次操作中完成,"café"会变成"tea",但不会再变回"café"。例如,请参考这个问题:https://dev59.com/tW025IYBdhLWcg3wRDm8#15221068 - mouwsy

26

我认为还没有人提出的一种选择是构建一个包含所有关键词的正则表达式,然后在字符串上执行一次替换:

>>> import re
>>> l = {'NORTH':'N','SOUTH':'S','EAST':'E','WEST':'W'}
>>> pattern = '|'.join(sorted(re.escape(k) for k in l))
>>> address = "123 north anywhere street"
>>> re.sub(pattern, lambda m: l.get(m.group(0).upper()), address, flags=re.IGNORECASE)
'123 N anywhere street'
>>> 

这种方法的优势在于正则表达式可以忽略输入字符串的大小写而不修改它。

如果你想只操作完整的单词,那么你也可以通过简单修改模式实现:

>>> pattern = r'\b({})\b'.format('|'.join(sorted(re.escape(k) for k in l)))
>>> address2 = "123 north anywhere southstreet"
>>> re.sub(pattern, lambda m: l.get(m.group(0).upper()), address2, flags=re.IGNORECASE)
'123 N anywhere southstreet'

我对正则表达式还比较陌生,希望您能解释一下lambda和group函数的具体作用。我注意到您还使用了sorted函数。在有多个键需要将单词替换为它们的值的情况下,sorted函数会影响任何内容吗?例如,在文本文件中可能存在不同间隔/行上出现的某些单词,这样做真的有必要吗? - trillion

11

使用字典翻译字符串是一个非常普遍的需求。我建议您在工具箱中保留以下函数:

def translate(text, conversion_dict, before=None):
    """
    Translate words from a text using a conversion dictionary

    Arguments:
        text: the text to be translated
        conversion_dict: the conversion dictionary
        before: a function to transform the input
        (by default it will to a lowercase)
    """
    # if empty:
    if not text: return text
    # preliminary transformation:
    before = before or str.lower
    t = before(text)
    for key, value in conversion_dict.items():
        t = t.replace(key, value)
    return t

然后您可以编写以下内容:
>>> a = {'hello':'bonjour', 'world':'tout-le-monde'}
>>> translate('hello world', a)
'bonjour tout-le-monde'

11

您可能正在寻找 iteritems()

d = {'NORTH':'N','SOUTH':'S','EAST':'E','WEST':'W'}
address = "123 north anywhere street"

for k,v in d.iteritems():
    address = address.upper().replace(k, v)

地址现在为'123 N ANYWHERE STREET'


好的,如果您想保留大小写、空格和嵌套单词(例如Southstreet不应转换为Sstreet),请考虑使用这个简单的列表推导式:

import re

l = {'NORTH':'N','SOUTH':'S','EAST':'E','WEST':'W'}

address = "North 123 East Anywhere Southstreet    West"

new_address = ''.join(l[p.upper()] if p.upper() in l else p for p in re.split(r'(\W+)', address))

新地址现在是

N 123 E Anywhere Southstreet    W

但这将会改变地址的整个大小写。 - Abhijit
取决于问题是“遍历字典”还是“为我完成所有工作”。 - sloth
@Abhijit 尽管如此,我添加了一个示例,展示如何保留大小写、空格和嵌套匹配。 - sloth
@Dominic - 很好的建议关于无意中扭曲地址,例如Southstreet Rd。在重新考虑此问题时,是否有一种方法可以忽略如果我有一个地址,例如South St.,那么这个替换呢?是否有一种RE会在这种情况下忽略替换? - user1947457

6
如果您正在寻找简洁的方法,可以使用 functools 中的 reduce:
from functools import reduce

str_to_replace = "The string for replacement."
replacement_dict = {"The ": "A new ", "for ": "after "}

str_replaced = reduce(lambda x, y: x.replace(*y), [str_to_replace, *list(replacement_dict.items())])
print(str_replaced)

6
我建议使用正则表达式而不是简单的替换。使用替换的风险在于替换掉词语的子部分,这可能不是您想要的结果。
import json
import re

with open('filePath.txt') as f:
   data = f.read()

with open('filePath.json') as f:
   glossar = json.load(f)

for word, initial in glossar.items():
   data = re.sub(r'\b' + word + r'\b', initial, data)

print(data)

5
def replace_values_in_string(text, args_dict):
    for key in args_dict.keys():
        text = text.replace(key, str(args_dict[key]))
    return text

2
请提供您的代码解释以提高答案质量 - Yuca

3
尝试一下:
import re
l = {'NORTH':'N','SOUTH':'S','EAST':'E','WEST':'W'}

address = "123 north anywhere street"

for k, v in l.iteritems():
    t = re.compile(re.escape(k), re.IGNORECASE)
    address = t.sub(v, address)
print(address)

2

使用replace()format()都不是很精确:

data =  '{content} {address}'
for k,v in {"{content}":"some {address}", "{address}":"New York" }.items():
    data = data.replace(k,v)
# results: some New York New York

'{ {content} {address}'.format(**{'content':'str1', 'address':'str2'})
# results: ValueError: unexpected '{' in field name

如果您需要更加精确的位置,最好使用 re.sub() 进行翻译:

import re
def translate(text, kw, ignore_case=False):
    search_keys = map(lambda x:re.escape(x), kw.keys())
    if ignore_case:
        kw = {k.lower():kw[k] for k in kw}
        regex = re.compile('|'.join(search_keys), re.IGNORECASE)
        res = regex.sub( lambda m:kw[m.group().lower()], text)
    else:
        regex = re.compile('|'.join(search_keys))
        res = regex.sub( lambda m:kw[m.group()], text)

    return res

#'score: 99.5% name:%(name)s' %{'name':'foo'}
res = translate( 'score: 99.5% name:{name}', {'{name}':'foo'})
print(res)

res = translate( 'score: 99.5% name:{NAME}', {'{name}':'foo'}, ignore_case=True)
print(res)

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