Jak zastąpić wiele podciągów ciągu?

284

Chciałbym użyć funkcji .replace do zastąpienia wielu ciągów.

Obecnie mam

string.replace("condition1", "")

ale chciałbym mieć coś takiego

string.replace("condition1", "").replace("condition2", "text")

chociaż nie wydaje się to dobrą składnią

jaki jest właściwy sposób to zrobić? tak jak w grep / regex możesz zrobić \1i \2zamienić pola na określone ciągi wyszukiwania

CQM
źródło
7
Czy wypróbowałeś wszystkie dostarczone rozwiązania? Który jest szybszy?
tommy.carstensen
Poświęciłem czas na przetestowanie wszystkich odpowiedzi w różnych scenariuszach. Zobacz stackoverflow.com/questions/59072514/…
Pablo
1
Szczerze, wolę twoje powiązane podejście od wszystkich innych. Wylądowałem tutaj, szukając rozwiązania i skorzystałem z twojego i działa dobrze.
frakman1
@ frakman1 +1. nie ma pojęcia, dlaczego nie jest to już bardziej doceniane. Wszystkie pozostałe metody utrudniają czytanie kodu. Gdyby do zastąpienia były tablice przekazywania funkcji, działałoby to. Ale twoja metoda łańcuchowa jest najbardziej przejrzysta (przynajmniej przy statycznej liczbie zamienników)
IceFire

Odpowiedzi:

269

Oto krótki przykład, który powinien załatwić sprawę z wyrażeniami regularnymi:

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()) 
#Python 3 renamed dict.iteritems to dict.items so use rep.items() for latest versions
pattern = re.compile("|".join(rep.keys()))
text = pattern.sub(lambda m: rep[re.escape(m.group(0))], text)

Na przykład:

>>> pattern.sub(lambda m: rep[re.escape(m.group(0))], "(condition1) and --condition2--")
'() and --text--'
Andrew Clark
źródło
7
Wymiana następuje w jednym przebiegu.
Andrew Clark,
26
dkaminy: nie jest zbyt sprytny, nie jest nawet tak sprytny, jak powinien być (powinniśmy ponownie uciec klawiszami przed dołączeniem ich za pomocą „|”). dlaczego nie jest to nadinżynieria? ponieważ w ten sposób robimy to w jednym przejściu (= szybko) i wykonujemy wszystkie zamiany w tym samym czasie, unikając starć takich jak "spamham sha".replace("spam", "eggs").replace("sha","md5")bycie "eggmd5m md5"zamiast"eggsham md5"
latanie owiec
8
@AndrewClark Byłbym bardzo wdzięczny, gdybyś mógł wyjaśnić, co dzieje się w ostatnim wierszu z lambda.
minerały
11
Cześć, stworzyłem małą treść z wyraźniejszą wersją tego fragmentu. Powinien być również nieco bardziej wydajny: gist.github.com/bgusach/a967e0587d6e01e889fd1d776c5f3729
bgusach
15
W przypadku Pythona 3 użyj item () zamiast iteritems ().
Jangari,
127

Możesz po prostu zrobić przyjemną małą funkcję zapętlania.

def replace_all(text, dic):
    for i, j in dic.iteritems():
        text = text.replace(i, j)
    return text

gdzie textjest kompletnym ciągiem i dicsłownikiem - każda definicja jest ciągiem, który zastąpi dopasowanie do terminu.

Uwaga : w Pythonie 3 iteritems()został zastąpiony przezitems()


Ostrożnie: słowniki Pythona nie mają niezawodnej kolejności iteracji. To rozwiązanie rozwiązuje problem tylko wtedy, gdy:

  • kolejność wymiany nie ma znaczenia
  • wymiana może zmienić wyniki poprzednich wymian

Na przykład:

d = { "cat": "dog", "dog": "pig"}
my_sentence = "This is my cat and this is my dog."
replace_all(my_sentence, d)
print(my_sentence)

Możliwe wyjście nr 1:

„To jest moja świnia, a to moja świnia”.

Możliwe wyjście # 2

„To jest mój pies, a to moja świnia”.

Jedną z możliwych poprawek jest użycie polecenia OragedDict.

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")])
my_sentence = "This is my cat and this is my dog."
replace_all(my_sentence, od)
print(my_sentence)

Wynik:

"This is my pig and this is my pig."

Ostrożnie # 2: Niewystarczające, jeśli textłańcuch jest zbyt duży lub w słowniku jest wiele par.

Joseph Hansen
źródło
37
Kolejność stosowania różnych zamienników będzie miała znaczenie - więc zamiast używać standardowego słownika, rozważ użycie OrderedDict- lub listy 2-krotek.
slothrop
5
To sprawia, że ​​wielokrotne powtarzanie łańcucha ... nie jest dobre dla występów.
Valentin Lorentz,
6
Pod względem wydajności jest gorszy niż mówi Valentin - przesuwa tekst tyle razy, ile jest elementów w dic! Dobrze, jeśli „tekst” jest mały, ale okropny dla dużego tekstu.
JDonner,
3
To dobre rozwiązanie w niektórych przypadkach. Na przykład chcę po prostu podmienić 2 znaki i nie dbam o kolejność, w jakiej się znajdują, ponieważ klucze podstawienia nie pasują do żadnych wartości. Ale chcę, żeby było jasne, co się dzieje.
Nathan Garabedian
5
Zauważ, że może to dać nieoczekiwane wyniki, ponieważ nowo wstawiony tekst w pierwszej iteracji można dopasować w drugiej iteracji. Na przykład, jeśli naiwnie spróbujemy zamienić wszystkie „A” na „B”, a wszystkie „B” na „C”, łańcuch „AB” zostanie przekształcony w „CC”, a nie „BC”.
Ambroz Bizjak
106

Dlaczego nie jedno takie rozwiązanie?

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
Enrico Bianchi
źródło
2
Jest to bardzo przydatne, proste i przenośne.
Shred
Wyglądało ładnie, ale nie zastępuje wyrażenia regularnego jak w: for r in ((r '\ s.', '.'), (R '\ s,', ',')):
Martin
2
zrobić to 1-liniowy: ss = [s.replace (* r) dla r in ((„brązowy”, „czerwony”), („leniwy”, „szybki”))] [0]
Mark K
95

Oto wariant pierwszego rozwiązania wykorzystującego redukcję, na wypadek gdybyś chciał być funkcjonalny. :)

repls = {'hello' : 'goodbye', 'world' : 'earth'}
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls.iteritems(), s)

jeszcze lepsza wersja martineau:

repls = ('hello', 'goodbye'), ('world', 'earth')
s = 'hello, world'
reduce(lambda a, kv: a.replace(*kv), repls, s)
Björn Lindqvist
źródło
8
Byłoby łatwiej zrobić replssekwencję krotek i zrezygnować z iteritems()połączenia. tj . repls = ('hello', 'goodbye'), ('world', 'earth')i reduce(lambda a, kv: a.replace(*kv), repls, s).
Działa
miły! jeśli używasz Python3, użyj elementów zamiast iteritems (teraz usuniętych w dicts stuff).
e.arbitrio
2
@martineau: To nieprawda, że ​​działa w niezmienionej postaci w python3, ponieważ reducezostał usunięty .
normanius
5
@normanius: reducenadal istnieje, jednak stał się częścią functoolsmodułu (patrz dokumentacja ) w Pythonie 3, więc kiedy powiedziałem bez zmian, miałem na myśli, że można uruchomić ten sam kod - choć trzeba by reducebyło, importgdyby był konieczny ponieważ nie jest już wbudowany.
martineau,
35

To tylko bardziej zwięzłe podsumowanie świetnych odpowiedzi FJ i MiniQuark. Wszystko, czego potrzebujesz, aby uzyskać jednoczesne zastępowanie ciągów, to następująca funkcja:

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)

Stosowanie:

>>>multiple_replace("Do you like cafe? No, I prefer tea.", {'cafe':'tea', 'tea':'cafe', 'like':'prefer'})
'Do you prefer tea? No, I prefer cafe.'

Jeśli chcesz, możesz stworzyć własne dedykowane funkcje zastępcze, zaczynając od tej prostszej.

mmj
źródło
1
Chociaż jest to dobre rozwiązanie, równoczesne zamiany łańcuchów nie dadzą dokładnie takich samych rezultatów, jak wykonywanie ich sekwencyjnie (łączenie łańcuchowe) - chociaż może to nie mieć znaczenia.
martineau,
2
Jasne, rep_dict = {"but": "mut", "mutton": "lamb"}że ciąg znaków "button"kończy się na "mutton"twoim kodzie, ale dałby, "lamb"gdyby zamienniki były powiązane łańcuchem, jeden po drugim.
martineau,
2
To jest główna cecha tego kodu, a nie wada. Przy łańcuchowych zamianach nie można było osiągnąć pożądanego zachowania polegającego na zamianie dwóch słów jednocześnie i wzajemnie, jak w moim przykładzie.
mmj
1
To nie może wydawać się świetną funkcją, jeśli jej nie potrzebujesz. Ale tutaj mówimy o jednoczesnych wymianach , to jest rzeczywiście główna cecha. Przy „łańcuchowych” zamianach wynik tego przykładu byłby Do you prefer cafe? No, I prefer cafe., co wcale nie jest pożądane.
mmj
@ David napisał własną odpowiedź, edycja jest zbyt radykalna
UmNyobe
29

Zbudowałem to na doskonałej odpowiedzi FJ:

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)

Użycie jednego strzału:

>>> 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é.

Zauważ, że ponieważ wymiana odbywa się w jednym przejściu, „kawiarnia” zmienia się w „herbata”, ale nie zmienia się z powrotem w „kawiarnia”.

Jeśli musisz wykonać tę samą wymianę wiele razy, możesz łatwo utworzyć funkcję zamiany:

>>> 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!\"

Ulepszenia:

  • zamienił kod w funkcję
  • dodano obsługę multilinii
  • naprawiono błąd w ucieczce
  • łatwo utworzyć funkcję dla określonej wielokrotnej zamiany

Cieszyć się! :-)

MiniQuark
źródło
1
Czy ktoś mógłby wyjaśnić ten krok po kroku dla pytbona nooba takiego jak ja?
Julian Suarez,
Koleś pyton noob tutaj, więc zrobię niepełną próbę zrozumienia tego ... a. rozbić wartości_klucza na elementy do zastąpienia (klucze połączone znakiem „|”) i logikę (jeśli dopasowanie jest kluczem, zwróć wartość) b. utwórz parser wyrażeń regularnych („wzorzec”, który szuka kluczy i używa podanej logiki) - zawiń to w funkcję lambda i zwróć. Rzeczy, na które patrzę teraz: re.M i konieczność lambda do wymiany logiki.
Lis
1
@ Fox Masz to. Możesz zdefiniować funkcję zamiast używać lambda, to po prostu skrócić kod. Należy jednak pamiętać, że pattern.suboczekuje funkcji z tylko jednym parametrem (tekst do zastąpienia), więc funkcja musi mieć dostęp do replace_dict. re.Mzezwala na zamiany Multiline (jest to dobrze wyjaśnione w dokumencie: docs.python.org/2/library/re.html#re.M ).
MiniQuark,
22

Chciałbym zaproponować użycie szablonów ciągów. Wystarczy umieścić ciąg do zamiany w słowniku i wszystko jest ustawione! Przykład z 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'
Fredrik Pihl
źródło
Wygląda dobrze, ale podczas dodawania klucza, który nie został podany, pojawia substitutesię wyjątek, więc zachowaj ostrożność podczas pobierania szablonów od użytkowników.
Bart Friederichs
2
Wadą tego podejścia jest to, że szablon musi zawierać wszystkie ciągi $ do zastąpienia, ale nie więcej niż wszystkie, patrz tutaj
RolfBly
17

W moim przypadku potrzebowałem prostej zamiany unikatowych kluczy na nazwy, więc wymyśliłem to:

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.'
James Koss
źródło
3
Działa to tak długo, jak długo nie występuje konflikt zastępczy. Jeśli zastąpisz igo s, otrzymasz dziwne zachowanie.
bgusach
1
Jeśli kolejność jest znacząca, zamiast powyższego dykta możesz użyć tablicy: b = [ ['i', 'Z'], ['s', 'Y'] ]; for x,y in (b): a = a.replace(x, y) Jeśli więc ostrożnie zamawiasz pary tablic, możesz upewnić się, że nie zastąpisz () rekurencyjnie.
CODE-REaD
Wygląda na to, że dykty utrzymują teraz porządek z Python 3.7.0. Przetestowałem to i działa po kolei na moim komputerze z najnowszym stabilnym Pythonem 3.
James Koss,
15

Zaczynając Python 3.8i wprowadzając wyrażenia przypisania (PEP 572) ( :=operator), możemy zastosować zamiany w ramach zrozumienia listy:

# 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'
Xavier Guihot
źródło
Czy wiesz, czy jest to bardziej wydajne niż używanie zamiany w pętli? Testuję wszystkie odpowiedzi pod kątem wydajności, ale nie mam jeszcze wersji 3.8.
Pablo
Dlaczego otrzymuję dane wyjściowe na liście?
johnrao07
1
@ johnrao07 Cóż, zrozumienie listy tworzy listę. Właśnie dlatego w tym przypadku dostajesz ['The quick red fox jumps over the lazy dog', 'The quick red fox jumps over the quick dog']. Ale wyrażenie przypisania ( text := text.replace) również iteracyjnie buduje nowe wersje text, mutując je. Po zapoznaniu się z listą możesz użyć textzmiennej zawierającej zmodyfikowany tekst.
Xavier Guihot
1
Jeśli chcesz zwrócić nową wersję textjako jednowierszową, możesz także użyć [text := text.replace(a, b) for a, b in replacements][-1](zwróć uwagę na [-1]), który wyodrębnia ostatni element zrozumienia listy; tj. ostatnia wersja text.
Xavier Guihot
13

Oto moje 0,02 $. Opiera się na odpowiedzi Andrew Clarka, nieco jaśniej, i obejmuje również przypadek, gdy ciąg do zastąpienia jest podciągiem innego ciągu do zastąpienia (dłuższy ciąg wygrywa)

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)

To właśnie w tym tego GIST , nie krępuj się go zmodyfikować, jeśli masz jakieś propozycję.

bgusach
źródło
1
To powinna być zaakceptowana odpowiedź, ponieważ wyrażenie regularne jest konstruowane ze wszystkich kluczy, sortując je w malejącej kolejności i łącząc je z | regex operator alternacji. A sortowanie jest konieczne, aby wybrać najdłuższy ze wszystkich możliwych wyborów, jeśli istnieją jakieś alternatywy.
Sachin S
Zgadzam się, że dzięki sortowaniu jest to najlepsze rozwiązanie. Poza tym sortowanie jest identyczne z moją pierwotną odpowiedzią, więc pożyczyłem sortowanie również dla mojego rozwiązania, aby mieć pewność, że nikt nie przegapi tak ważnej funkcji.
mmj
6

Potrzebowałem rozwiązania, w którym ciągi do zamiany mogą być wyrażeniami regularnymi, na przykład, aby pomóc w normalizacji długiego tekstu przez zastąpienie wielu białych znaków jednym. Opierając się na szeregu odpowiedzi od innych, w tym MiniQuark i mmj, wymyśliłem:

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)

Działa z przykładami podanymi w innych odpowiedziach, na przykład:

>>> 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.'

Najważniejsze dla mnie jest to, że możesz również używać wyrażeń regularnych, na przykład, aby zamieniać tylko całe słowa lub normalizować białe znaki:

>>> 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"

Jeśli chcesz używać kluczy słownika jako normalnych ciągów znaków, możesz uciec przed nimi przed wywołaniem wielu_replace przy użyciu np. Tej funkcji:

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"

Poniższa funkcja może pomóc w znalezieniu błędnych wyrażeń regularnych wśród kluczy słownika (ponieważ komunikat o błędzie z wielu_replace nie jest zbyt wymowny):

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())

Pamiętaj, że nie łączy łańcuchów zamienników, zamiast tego wykonuje je jednocześnie. To sprawia, że ​​jest bardziej wydajny bez ograniczania tego, co może zrobić. Aby naśladować efekt łączenia, konieczne może być dodanie większej liczby par zamieniających łańcuchy i zapewnienie oczekiwanego uporządkowania par:

>>> multiple_replace("button", {"but": "mut", "mutton": "lamb"})
'mutton'
>>> multiple_replace("button", [("button", "lamb"),
...                             ("but", "mut"), ("mutton", "lamb")])
'lamb'

źródło
To miło, dzięki. Czy można to ulepszyć, aby umożliwić również stosowanie odwołań wstecznych w zamianach? Nie od razu wymyśliłem, jak to dodać.
cmarqu,
Odpowiedź na moje powyższe pytanie to stackoverflow.com/questions/45630940/…
cmarqu
4

Oto próbka, która jest bardziej wydajna na długich ciągach z wieloma małymi zamiennikami.

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)

Chodzi o to, by uniknąć wielu połączeń długich łańcuchów. Przecinamy łańcuch źródłowy na fragmenty, zastępując niektóre fragmenty podczas tworzenia listy, a następnie łączymy całość z powrotem w łańcuch.

9000
źródło
2

Naprawdę nie powinieneś tego robić w ten sposób, ale uważam to za zbyt fajne:

>>> replacements = {'cond1':'text1', 'cond2':'text2'}
>>> cmd = 'answer = s'
>>> for k,v in replacements.iteritems():
>>>     cmd += ".replace(%s, %s)" %(k,v)
>>> exec(cmd)

Teraz answerjest wynikiem wszystkich zamian po kolei

znowu, jest to bardzo zuchwałe i nie jest czymś, z czego powinieneś regularnie korzystać. Ale miło jest wiedzieć, że możesz zrobić coś takiego, jeśli zajdzie taka potrzeba.

inspectorG4dget
źródło
2

Walczyłem również z tym problemem. Przy wielu podstawieniach wyrażenia regularne zmagają się i są około czterokrotnie wolniejsze niż zapętlenie string.replace(w warunkach mojego eksperymentu).

Powinieneś bezwzględnie spróbować użyć biblioteki Flashtext ( post na blogu tutaj , Github tutaj ). W moim przypadku było to nieco ponad dwa rzędy wielkości szybciej, od 1,8 s do 0,015 s (wyrażenia regularne trwały 7,7 s) dla każdego dokumentu.

Łatwo jest znaleźć przykłady użycia w powyższych linkach, ale jest to działający przykład:

    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)

Zauważ, że Flashtext dokonuje podstawień w jednym przebiegu (aby uniknąć a -> b i b -> c tłumaczenia „a” na „c”). Flashtext szuka również całych słów (więc „is” nie pasuje do „th is ”). Działa dobrze, jeśli twój cel składa się z kilku słów (zamieniając „To jest” na „Cześć”).

Pablo
źródło
Jak to działa, jeśli musisz zastąpić tagi HTML? Np zastąpić <p>z /n. Próbowałem twojego podejścia, ale z tagami flashtext nie wydaje się go analizować?
alias51
1
Nie jestem pewien, dlaczego nie działa zgodnie z oczekiwaniami. Jedną z możliwości jest to, że te znaczniki nie są oddzielone spacjami i pamiętaj, że Flashtext szuka całych słów. Można to obejść, używając najpierw prostej zamiany, tak aby „Cześć <p> tam” zmieniło się na „Cześć <p> tam”. Gdy skończysz, musisz zachować ostrożność, aby usunąć niechciane spacje (również po prostu wymienić?). Mam nadzieję, że to pomaga.
Pablo
Dzięki, czy możesz ustawić <i >oznaczyć koniec słowa (ale zostać uwzględnionym w zastępowaniu)?
alias51
1
Uważam, że „słowa” są oznaczone tylko spacjami. Być może istnieją parametry opcjonalne, które można ustawić w „KeywordProcessor”. W przeciwnym razie rozważ powyższe podejście: zamień „<” na „<”, zastosuj Flashtext, a następnie zamień z powrotem (w twoim przypadku, na przykład, „<” na „<” i „\ n” na „\ n” może działać).
Pablo
2

Wydaje mi się, że to pytanie wymaga jednokreskowej rekurencyjnej odpowiedzi na funkcję lambda dla kompletności, tylko dlatego. Więc tam:

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

Stosowanie:

>>> mrep('abcabc', {'a': '1', 'c': '2'})
'1b21b2'

Uwagi:

  • To zużywa słownik wejściowy.
  • Python dyktuje zachowanie kolejności kluczy od wersji 3.6; odpowiednie zastrzeżenia w innych odpowiedziach nie są już istotne. Dla kompatybilności wstecznej można zastosować wersję krotkową:
>>> mrep = lambda s, d: s if not d else mrep(s.replace(*d.pop()), d)
>>> mrep('abcabc', [('a', '1'), ('c', '2')])

Uwaga: Podobnie jak w przypadku wszystkich funkcji rekurencyjnych w pythonie, zbyt duża głębokość rekurencji (tj. Zbyt duże słowniki zastępcze) spowoduje błąd. Zobacz np . Tutaj .

Mcsoini
źródło
Podczas używania dużego słownika wpadam na RecursionError!
Pablo
@Pablo Interesujące. Jak duży? Pamiętaj, że dzieje się tak w przypadku wszystkich funkcji rekurencyjnych. Zobacz na przykład tutaj: stackoverflow.com/questions/3323001/…
mcsoini
Mój słownik podstawień jest zbliżony do 100 000 haseł ... jak dotąd używanie string.replace jest zdecydowanie najlepszym podejściem.
Pablo
1
@Pablo w takim przypadku nie można używać funkcji rekurencyjnych. Ogólnie rzecz biorąc, sys.getrecursionlimit()to kilka 1000, maks. użyj pętli lub czegoś podobnego lub spróbuj uprościć podstawienia.
mcsoini,
Tak, obawiam się, że tak naprawdę nie ma tutaj skrótu.
Pablo
1

Nie wiem o prędkości, ale to moja szybka poprawka w dniu pracy:

reduce(lambda a, b: a.replace(*b)
    , [('o','W'), ('t','X')] #iterable of pairs: (oldval, newval)
    , 'tomato' #The string from which to replace values
    )

... ale podoba mi się odpowiedź nr 1 powyżej. Uwaga - jeśli jedna nowa wartość jest podciągiem innej, wówczas operacja nie jest przemienna.

del_hol
źródło
1

Możesz użyć pandasbiblioteki i replacefunkcji, która obsługuje zarówno dokładne dopasowania, jak i zamiany wyrażeń regularnych. Na przykład:

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))

Zmodyfikowany tekst to:

0    name is going to visit city in month
1                      I was born in date
2                 I will be there at time

Można znaleźć przykład tutaj . Zauważ, że zamiany tekstu są wykonywane w kolejności, w jakiej pojawiają się na listach

George Pipis
źródło
1

Aby zastąpić tylko jeden znak, użyj translateistr.maketrans jest moją ulubioną metodą.

tl; dr> result_string = your_string.translate(str.maketrans(dict_mapping))


próbny

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.
Carson
źródło
0

Począwszy od cennej odpowiedzi Andrew, opracowałem skrypt, który ładuje słownik z pliku i opracowuje wszystkie pliki w otwartym folderze w celu wykonania zamiany. Skrypt ładuje mapowania z zewnętrznego pliku, w którym można ustawić separator. Jestem początkującym, ale uważam, że ten skrypt jest bardzo przydatny podczas wielokrotnego zastępowania w wielu plikach. Załadował słownik z ponad 1000 pozycji w ciągu kilku sekund. To nie jest eleganckie, ale działało dla mnie

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()
Tommaso Sandi
źródło
0

to jest moje rozwiązanie problemu. Użyłem go w chatbocie, aby zastąpić różne słowa naraz.

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"})

to się stanie The cat hunts the dog

emorjon2
źródło
0

Kolejny przykład: lista wprowadzania

error_list = ['[br]', '[ex]', 'Something']
words = ['how', 'much[ex]', 'is[br]', 'the', 'fish[br]', 'noSomething', 'really']

Pożądane wyjście byłoby

words = ['how', 'much', 'is', 'the', 'fish', 'no', 'really']

Kod :

[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]] 
Akhil Thayyil
źródło
-2

Lub po prostu szybki hack:

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)
Brandon H.
źródło
-2

Oto inny sposób zrobienia tego za pomocą słownika:

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)
Stefan Gruenwald
źródło