Как заменить несколько подстрок строки?
Я хотел бы использовать функцию.replace для замены нескольких строк.
У меня сейчас
string.replace("condition1", "")
но хотелось бы что то типа
string.replace("condition1", "").replace("condition2", "text")
хотя это не похоже на хороший синтаксис
Как правильно это сделать? вроде как в grep/regex вы можете сделать \1
а также \2
заменить поля на определенные строки поиска
30 ответов
Вот краткий пример того, что нужно делать с регулярными выражениями:
import re
rep = {"condition1": "", "condition2": "text"} # define desired replacements here
# use these three lines to do the replacement
rep = dict((re.escape(k), v) for k, v in rep.iteritems())
pattern = re.compile("|".join(rep.keys()))
text = pattern.sub(lambda m: rep[re.escape(m.group(0))], text)
Например:
>>> pattern.sub(lambda m: rep[re.escape(m.group(0))], "(condition1) and --condition2--")
'() and --text--'
Вы можете просто сделать хорошую маленькую функцию зацикливания.
def replace_all(text, dic):
for i, j in dic.iteritems():
text = text.replace(i, j)
return text
где text
полная строка и dic
словарь - каждое определение является строкой, которая заменит соответствие термину.
Примечание: в Python 3 iteritems()
был заменен на items()
Осторожно: словари Python не имеют надежного заказа для итерации. Это решение решает вашу проблему, только если:
- порядок замен не имеет значения
- это нормально для замены, чтобы изменить результаты предыдущих замен
Например:
d = { "cat": "dog", "dog": "pig"}
mySentence = "This is my cat and this is my dog."
replace_all(mySentence, d)
print(mySentence)
Возможный вывод № 1:
"Это моя свинья, и это моя свинья".
Возможный вывод № 2
"Это моя собака, а это моя свинья".
Одним из возможных исправлений является использование OrderedDict.
from collections import OrderedDict
def replace_all(text, dic):
for i, j in dic.items():
text = text.replace(i, j)
return text
od = OrderedDict([("cat", "dog"), ("dog", "pig")])
mySentence = "This is my cat and this is my dog."
replace_all(mySentence, od)
print(mySentence)
Выход:
"This is my pig and this is my pig."
Осторожно № 2: неэффективно, если ваш text
строка слишком велика или в словаре много пар.
Почему бы не одно решение, как это?
s = "The quick brown fox jumps over the lazy dog"
for r in (("brown", "red"), ("lazy", "quick")):
s = s.replace(*r)
#output will be: The quick red fox jumps over the quick dog
Вот вариант первого решения, использующего Reduce, если вам нравится работать.:)
repls = {'hello' : 'goodbye', 'world' : 'earth'}
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls.iteritems(), s)
еще лучшая версия Мартино:
repls = ('hello', 'goodbye'), ('world', 'earth')
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls, s)
Это просто более краткий обзор отличных ответов FJ и MiniQuark. Все, что вам нужно для одновременной замены нескольких строк, это следующая функция:
def multiple_replace(string, rep_dict):
pattern = re.compile("|".join([re.escape(k) for k in sorted(rep_dict,key=len,reverse=True)]), flags=re.DOTALL)
return pattern.sub(lambda x: rep_dict[x.group(0)], string)
Использование:
>>>multiple_replace("Do you like cafe? No, I prefer tea.", {'cafe':'tea', 'tea':'cafe', 'like':'prefer'})
'Do you prefer tea? No, I prefer cafe.'
Если вы хотите, вы можете сделать свои собственные специальные функции замены, начиная с этой более простой.
Начало Python 3.8
и введение выражений присваивания (PEP 572) (:=
оператор), мы можем применить замены в пределах понимания списка:
# text = "The quick brown fox jumps over the lazy dog"
# replacements = [("brown", "red"), ("lazy", "quick")]
[text := text.replace(a, b) for a, b in replacements]
# text = 'The quick red fox jumps over the quick dog'
Я построил это на превосходном ответе FJs:
import re
def multiple_replacer(*key_values):
replace_dict = dict(key_values)
replacement_function = lambda match: replace_dict[match.group(0)]
pattern = re.compile("|".join([re.escape(k) for k, v in key_values]), re.M)
return lambda string: pattern.sub(replacement_function, string)
def multiple_replace(string, *key_values):
return multiple_replacer(*key_values)(string)
Использование одного выстрела:
>>> replacements = (u"café", u"tea"), (u"tea", u"café"), (u"like", u"love")
>>> print multiple_replace(u"Do you like café? No, I prefer tea.", *replacements)
Do you love tea? No, I prefer café.
Обратите внимание, что поскольку замена выполняется всего за один проход, "кафе" меняется на "чай", но не возвращается обратно к "кафе".
Если вам нужно выполнять одну и ту же замену много раз, вы можете легко создать функцию замены:
>>> my_escaper = multiple_replacer(('"','\\"'), ('\t', '\\t'))
>>> many_many_strings = (u'This text will be escaped by "my_escaper"',
u'Does this work?\tYes it does',
u'And can we span\nmultiple lines?\t"Yes\twe\tcan!"')
>>> for line in many_many_strings:
... print my_escaper(line)
...
This text will be escaped by \"my_escaper\"
Does this work?\tYes it does
And can we span
multiple lines?\t\"Yes\twe\tcan!\"
Улучшения:
- превратил код в функцию
- добавлена многострочная поддержка
- исправлена ошибка в побеге
- легко создать функцию для конкретной множественной замены
Наслаждайтесь! :-)
Я хотел бы предложить использовать строковые шаблоны. Просто поместите строку, которую нужно заменить, в словарь, и все готово! Пример из docs.python.org
>>> from string import Template
>>> s = Template('$who likes $what')
>>> s.substitute(who='tim', what='kung pao')
'tim likes kung pao'
>>> d = dict(who='tim')
>>> Template('Give $who $100').substitute(d)
Traceback (most recent call last):
[...]
ValueError: Invalid placeholder in string: line 1, col 10
>>> Template('$who likes $what').substitute(d)
Traceback (most recent call last):
[...]
KeyError: 'what'
>>> Template('$who likes $what').safe_substitute(d)
'tim likes $what'
В моем случае мне потребовалась простая замена уникальных ключей именами, поэтому я подумал:
a = 'This is a test string.'
b = {'i': 'I', 's': 'S'}
for x,y in b.items():
a = a.replace(x, y)
>>> a
'ThIS IS a teSt StrIng.'
Здесь мои $0,02. Он основан на ответе Эндрю Кларка, немного яснее, и также охватывает случай, когда заменяемая строка является подстрокой другой заменяемой строки (более длинные строки выигрывают)
def multireplace(string, replacements):
"""
Given a string and a replacement map, it returns the replaced string.
:param str string: string to execute replacements on
:param dict replacements: replacement dictionary {value to find: value to replace}
:rtype: str
"""
# Place longer ones first to keep shorter substrings from matching
# where the longer ones should take place
# For instance given the replacements {'ab': 'AB', 'abc': 'ABC'} against
# the string 'hey abc', it should produce 'hey ABC' and not 'hey ABc'
substrs = sorted(replacements, key=len, reverse=True)
# Create a big OR regex that matches any of the substrings to replace
regexp = re.compile('|'.join(map(re.escape, substrs)))
# For each match, look up the new string in the replacements
return regexp.sub(lambda match: replacements[match.group(0)], string)
Именно в этой этой сути, не стесняйтесь изменять его, если у вас есть какие-либо предложения.
Мне нужно было решение, где заменяемые строки могут быть регулярными выражениями, например, чтобы помочь нормализовать длинный текст, заменив несколько пробельных символов одним. Основываясь на цепочке ответов от других, включая MiniQuark и mmj, я пришел к такому выводу:
def multiple_replace(string, reps, re_flags = 0):
""" Transforms string, replacing keys from re_str_dict with values.
reps: dictionary, or list of key-value pairs (to enforce ordering;
earlier items have higher priority).
Keys are used as regular expressions.
re_flags: interpretation of regular expressions, such as re.DOTALL
"""
if isinstance(reps, dict):
reps = reps.items()
pattern = re.compile("|".join("(?P<_%d>%s)" % (i, re_str[0])
for i, re_str in enumerate(reps)),
re_flags)
return pattern.sub(lambda x: reps[int(x.lastgroup[1:])][1], string)
Это работает для примеров, приведенных в других ответах, например:
>>> multiple_replace("(condition1) and --condition2--",
... {"condition1": "", "condition2": "text"})
'() and --text--'
>>> multiple_replace('hello, world', {'hello' : 'goodbye', 'world' : 'earth'})
'goodbye, earth'
>>> multiple_replace("Do you like cafe? No, I prefer tea.",
... {'cafe': 'tea', 'tea': 'cafe', 'like': 'prefer'})
'Do you prefer tea? No, I prefer cafe.'
Для меня главное, что вы также можете использовать регулярные выражения, например, чтобы заменить только целые слова или нормализовать пробел:
>>> s = "I don't want to change this name:\n Philip II of Spain"
>>> re_str_dict = {r'\bI\b': 'You', r'[\n\t ]+': ' '}
>>> multiple_replace(s, re_str_dict)
"You don't want to change this name: Philip II of Spain"
Если вы хотите использовать словарные ключи как обычные строки, вы можете избежать их перед вызовом multip_replace, используя, например, эту функцию:
def escape_keys(d):
""" transform dictionary d by applying re.escape to the keys """
return dict((re.escape(k), v) for k, v in d.items())
>>> multiple_replace(s, escape_keys(re_str_dict))
"I don't want to change this name:\n Philip II of Spain"
Следующая функция может помочь в поиске ошибочных регулярных выражений среди ключей вашего словаря (поскольку сообщение об ошибке из множественного_реклама не очень красноречиво):
def check_re_list(re_list):
""" Checks if each regular expression in list is well-formed. """
for i, e in enumerate(re_list):
try:
re.compile(e)
except (TypeError, re.error):
print("Invalid regular expression string "
"at position {}: '{}'".format(i, e))
>>> check_re_list(re_str_dict.keys())
Обратите внимание, что он не связывает замены, а выполняет их одновременно. Это делает его более эффективным, не ограничивая возможности. Чтобы имитировать эффект сцепления, вам, возможно, просто нужно добавить больше пар замены строк и обеспечить ожидаемое упорядочение пар:
>>> multiple_replace("button", {"but": "mut", "mutton": "lamb"})
'mutton'
>>> multiple_replace("button", [("button", "lamb"),
... ("but", "mut"), ("mutton", "lamb")])
'lamb'
Вот пример, который более эффективен на длинных строках с множеством небольших замен.
source = "Here is foo, it does moo!"
replacements = {
'is': 'was', # replace 'is' with 'was'
'does': 'did',
'!': '?'
}
def replace(source, replacements):
finder = re.compile("|".join(re.escape(k) for k in replacements.keys())) # matches every string we want replaced
result = []
pos = 0
while True:
match = finder.search(source, pos)
if match:
# cut off the part up until match
result.append(source[pos : match.start()])
# cut off the matched part and replace it in place
result.append(replacements[source[match.start() : match.end()]])
pos = match.end()
else:
# the rest after the last match
result.append(source[pos:])
break
return "".join(result)
print replace(source, replacements)
Дело в том, чтобы избежать многих конкатенаций длинных строк. Мы разрезаем исходную строку на фрагменты, заменяя некоторые фрагменты при формировании списка, а затем соединяем все это обратно в строку.
Я делал похожее упражнение в одном из моих школьных домашних заданий. Это было мое решение
dictionary = {1: ['hate', 'love'],
2: ['salad', 'burger'],
3: ['vegetables', 'pizza']}
def normalize(text):
for i in dictionary:
text = text.replace(dictionary[i][0], dictionary[i][1])
return text
Смотрите результат сами на тестовой строке
string_to_change = 'I hate salad and vegetables'
print(normalize(string_to_change))
Сегодня я столкнулся с подобной проблемой, когда мне пришлось несколько раз использовать метод .replace(), но мне это не понравилось. Я сделал что-то вроде этого:
REPLACEMENTS = {'<': '<', '>': '>', '&': '&'}
event_title = ''.join([REPLACEMENTS.get(c,c) for c in event['summary']])
Вы можете использовать pandas
библиотека и replace
функция, которая поддерживает как точное совпадение, так и замену регулярных выражений. Например:
df = pd.DataFrame({'text': ['Billy is going to visit Rome in November', 'I was born in 10/10/2010', 'I will be there at 20:00']})
to_replace=['Billy','Rome','January|February|March|April|May|June|July|August|September|October|November|December', '\d{2}:\d{2}', '\d{2}/\d{2}/\d{4}']
replace_with=['name','city','month','time', 'date']
print(df.text.replace(to_replace, replace_with, regex=True))
И измененный текст:
0 name is going to visit city in month
1 I was born in date
2 I will be there at time
Вы можете найти пример здесь. Обратите внимание, что замены в тексте выполняются в том порядке, в котором они появляются в списках.
Я тоже боролся с этой проблемой. Регулярные выражения борются со многими подстановками и работают примерно в четыре раза медленнее, чем цикл.string.replace
(в условиях моего эксперимента).
Вы обязательно должны попробовать использовать библиотеку Flashtext ( сообщение в блоге здесь, Github здесь). В моем случае это было чуть более чем на два порядка быстрее, с 1,8 до 0,015 с (регулярные выражения занимали 7,7 с) для каждого документа.
Примеры использования легко найти по ссылкам выше, но это рабочий пример:
from flashtext import KeywordProcessor
self.processor = KeywordProcessor(case_sensitive=False)
for k, v in self.my_dict.items():
self.processor.add_keyword(k, v)
new_string = self.processor.replace_keywords(string)
Обратите внимание, что Flashtext выполняет подстановки за один проход (чтобы избежать перевода a -> b и b -> c в "c"). Flashtext также ищет слова целиком (поэтому "есть" не будет соответствовать "this"). Он отлично работает, если ваша цель состоит из нескольких слов (заменив "Это" на "Привет").
Я считаю, что для полноты этого вопроса нужен однострочный рекурсивный ответ лямбда-функции, просто потому, что. Здесь:
>>> mrep = lambda s, d: s if not d else mrep(s.replace(*d.popitem()), d)
Применение:
>>> mrep('abcabc', {'a': '1', 'c': '2'})
'1b21b2'
Заметки:
- Это потребляет входной словарь.
- Python dicts сохраняет порядок ключей начиная с версии 3.6; соответствующие предостережения в других ответах больше не актуальны. Для обратной совместимости можно было прибегнуть к версии на основе кортежей:
>>> mrep = lambda s, d: s if not d else mrep(s.replace(*d.pop()), d)
>>> mrep('abcabc', [('a', '1'), ('c', '2')])
Примечание. Как и для всех рекурсивных функций в Python, слишком большая глубина рекурсии (т.е. слишком большие заменяющие словари) приведет к ошибке. См., Например, здесь.
Вы действительно не должны делать это таким образом, но я нахожу это слишком крутым:
>>> replacements = {'cond1':'text1', 'cond2':'text2'}
>>> cmd = 'answer = s'
>>> for k,v in replacements.iteritems():
>>> cmd += ".replace(%s, %s)" %(k,v)
>>> exec(cmd)
Сейчас, answer
является результатом всех замен по очереди
Опять же, это очень хакерское и не то, что вы должны регулярно использовать. Но просто приятно знать, что вы можете сделать что-то подобное, если вам когда-нибудь понадобится.
Для замены только одного символа используйте translate
а также str.maketrans
мой любимый метод.
tl; dr> result_string = your_string.translate(str.maketrans(dict_mapping))
демо
my_string = 'This is a test string.'
dict_mapping = {'i': 's', 's': 'S'}
result_good = my_string.translate(str.maketrans(dict_mapping))
result_bad = my_string
for x, y in dict_mapping.items():
result_bad = result_bad.replace(x, y)
print(result_good) # ThsS sS a teSt Strsng.
print(result_bad) # ThSS SS a teSt StrSng.
Я предлагаю, чтобы код был: Пример.
z = "My name is Ahmed, and I like coding "
print(z.replace(" Ahmed", " Dauda"). replace(" like", " Love" ))
Он распечатает все изменения в соответствии с запросом.
Вот версия с поддержкой базовой замены регулярных выражений . Основное ограничение заключается в том, что выражения не должны содержать подгрупп, и могут быть некоторые крайние случаи:
Код на основе @bgusach и др.
import re
class StringReplacer:
def __init__(self, replacements, ignore_case=False):
patterns = sorted(replacements, key=len, reverse=True)
self.replacements = [replacements[k] for k in patterns]
re_mode = re.IGNORECASE if ignore_case else 0
self.pattern = re.compile('|'.join(("({})".format(p) for p in patterns)), re_mode)
def tr(matcher):
index = next((index for index,value in enumerate(matcher.groups()) if value), None)
return self.replacements[index]
self.tr = tr
def __call__(self, string):
return self.pattern.sub(self.tr, string)
Тесты
table = {
"aaa" : "[This is three a]",
"b+" : "[This is one or more b]",
r"<\w+>" : "[This is a tag]"
}
replacer = StringReplacer(table, True)
sample1 = "whatever bb, aaa, <star> BBB <end>"
print(replacer(sample1))
# output:
# whatever [This is one or more b], [This is three a], [This is a tag] [This is one or more b] [This is a tag]
Уловка состоит в том, чтобы идентифицировать совпавшую группу по ее положению. Это не очень эффективно ( O (n) ), но работает.
index = next((index for index,value in enumerate(matcher.groups()) if value), None)
Замена производится за один проход.
Я не знаю о скорости, но это мое быстрое решение:
reduce(lambda a, b: a.replace(*b)
, [('o','W'), ('t','X')] #iterable of pairs: (oldval, newval)
, 'tomato' #The string from which to replace values
)
... но мне нравится ответ № 1 регулярное выражение выше. Примечание. Если одно новое значение является подстрокой другого, операция не является коммутативной.
А как насчет " StringUtils.replaceEach" для commons-lang3?
StringUtils.replaceEach(null, *, *) = null
StringUtils.replaceEach("", *, *) = ""
StringUtils.replaceEach("aba", null, null) = "aba"
StringUtils.replaceEach("aba", new String[0], null) = "aba"
StringUtils.replaceEach("aba", null, new String[0]) = "aba"
StringUtils.replaceEach("aba", new String[]{"a"}, null) = "aba"
StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}) = "b"
StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}) = "aba"
StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
(example of how it does not repeat)
StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "dcte"
Другой пример: список ввода
error_list = ['[br]', '[ex]', 'Something']
words = ['how', 'much[ex]', 'is[br]', 'the', 'fish[br]', 'noSomething', 'really']
Желаемый результат будет
words = ['how', 'much', 'is', 'the', 'fish', 'no', 'really']
Код:
[n[0][0] if len(n[0]) else n[1] for n in [[[w.replace(e,"") for e in error_list if e in w],w] for w in words]]
Начиная с драгоценного ответа Эндрю, я разработал сценарий, который загружает словарь из файла и разрабатывает все файлы в открытой папке для выполнения замен. Скрипт загружает сопоставления из внешнего файла, в котором вы можете установить разделитель. Я новичок, но я нашел этот скрипт очень полезным при выполнении нескольких замен в нескольких файлах. Он загрузил словарь с более чем 1000 записей в секундах. Это не элегантно, но это сработало для меня
import glob
import re
mapfile = input("Enter map file name with extension eg. codifica.txt: ")
sep = input("Enter map file column separator eg. |: ")
mask = input("Enter search mask with extension eg. 2010*txt for all files to be processed: ")
suff = input("Enter suffix with extension eg. _NEW.txt for newly generated files: ")
rep = {} # creation of empy dictionary
with open(mapfile) as temprep: # loading of definitions in the dictionary using input file, separator is prompted
for line in temprep:
(key, val) = line.strip('\n').split(sep)
rep[key] = val
for filename in glob.iglob(mask): # recursion on all the files with the mask prompted
with open (filename, "r") as textfile: # load each file in the variable text
text = textfile.read()
# start replacement
#rep = dict((re.escape(k), v) for k, v in rep.items()) commented to enable the use in the mapping of re reserved characters
pattern = re.compile("|".join(rep.keys()))
text = pattern.sub(lambda m: rep[m.group(0)], text)
#write of te output files with the prompted suffice
target = open(filename[:-4]+"_NEW.txt", "w")
target.write(text)
target.close()
Это мое решение проблемы. Я использовал его в чате, чтобы заменить разные слова сразу.
def mass_replace(text, dct):
new_string = ""
old_string = text
while len(old_string) > 0:
s = ""
sk = ""
for k in dct.keys():
if old_string.startswith(k):
s = dct[k]
sk = k
if s:
new_string+=s
old_string = old_string[len(sk):]
else:
new_string+=old_string[0]
old_string = old_string[1:]
return new_string
print mass_replace("The dog hunts the cat", {"dog":"cat", "cat":"dog"})
это станет The cat hunts the dog
Мой подход заключался бы в том, чтобы сначала разметить строку, а затем решить для каждого токена, включать ее или нет.
Потенциально может быть более производительным, если мы предположим, что поиск O(1) для хэш-карты / набора:
remove_words = {"we", "this"}
target_sent = "we should modify this string"
target_sent_words = target_sent.split()
filtered_sent = " ".join(list(filter(lambda word: word not in remove_words, target_sent_words)))
filtered_sent
сейчас
'should modify string'
Вот еще один способ сделать это с помощью словаря:
listA="The cat jumped over the house".split()
modify = {word:word for number,word in enumerate(listA)}
modify["cat"],modify["jumped"]="dog","walked"
print " ".join(modify[x] for x in listA)
sentence='its some sentence with a something text'
def replaceAll(f,Array1,Array2):
if len(Array1)==len(Array2):
for x in range(len(Array1)):
return f.replace(Array1[x],Array2[x])
newSentence=replaceAll(sentence,['a','sentence','something'],['another','sentence','something something'])
print(newSentence)
Или просто для быстрого взлома:
for line in to_read:
read_buffer = line
stripped_buffer1 = read_buffer.replace("term1", " ")
stripped_buffer2 = stripped_buffer1.replace("term2", " ")
write_to_file = to_write.write(stripped_buffer2)