Найкращий спосіб замінити кілька символів у рядку?


199

Мені потрібно замінити деякі символи так: &\&, #\#, ...

Я кодував так, але, мабуть, має бути якийсь кращий спосіб. Якісь підказки?

strs = strs.replace('&', '\&')
strs = strs.replace('#', '\#')
...

Відповіді:


434

Заміна двох символів

Я приурочив усі методи в поточних відповідях разом з одним додатковим.

З вхідного рядка abc&def#ghiі замінюючи & -> \ & і # -> \ #, найшвидший спосіб був прикувати разом ці заміни , як це: text.replace('&', '\&').replace('#', '\#').

Час виконання кожної функції:

  • a) 1000000 петель, найкраще 3: 1,47 мкс на цикл
  • б) 1000000 петель, найкраще 3: 1,51 мкс на петлю
  • в) 100000 петель, найкраще 3: 12,3 мкс на цикл
  • г) 100000 петель, найкраще 3: 12 мкс на цикл
  • д) 100000 петель, найкраще 3: 3,27 мкс на цикл
  • f) 1000000 петель, найкраще 3: 0,817 мкс на цикл
  • g) 100000 петель, найкраще 3: 3,64 мкс на цикл
  • h) 1000000 петель, найкраще 3: 0,927 мкс на цикл
  • i) 1000000 петель, найкраще 3: 0,814 мкс на цикл

Ось функції:

def a(text):
    chars = "&#"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['&','#']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([&#])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('&#')
def e(text):
    esc(text)


def f(text):
    text = text.replace('&', '\&').replace('#', '\#')


def g(text):
    replacements = {"&": "\&", "#": "\#"}
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('&', r'\&')
    text = text.replace('#', r'\#')


def i(text):
    text = text.replace('&', r'\&').replace('#', r'\#')

Призначається так:

python -mtimeit -s"import time_functions" "time_functions.a('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.b('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.c('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.d('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.e('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.f('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.g('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.h('abc&def#ghi')"
python -mtimeit -s"import time_functions" "time_functions.i('abc&def#ghi')"

Заміна 17 символів

Ось подібний код зробити те ж саме, але для втечі більше символів (\ `* _ {}> # + -. $):

def a(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)


def b(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)


import re
def c(text):
    rx = re.compile('([&#])')
    text = rx.sub(r'\\\1', text)


RX = re.compile('([\\`*_{}[]()>#+-.!$])')
def d(text):
    text = RX.sub(r'\\\1', text)


def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])
esc = mk_esc('\\`*_{}[]()>#+-.!$')
def e(text):
    esc(text)


def f(text):
    text = text.replace('\\', '\\\\').replace('`', '\`').replace('*', '\*').replace('_', '\_').replace('{', '\{').replace('}', '\}').replace('[', '\[').replace(']', '\]').replace('(', '\(').replace(')', '\)').replace('>', '\>').replace('#', '\#').replace('+', '\+').replace('-', '\-').replace('.', '\.').replace('!', '\!').replace('$', '\$')


def g(text):
    replacements = {
        "\\": "\\\\",
        "`": "\`",
        "*": "\*",
        "_": "\_",
        "{": "\{",
        "}": "\}",
        "[": "\[",
        "]": "\]",
        "(": "\(",
        ")": "\)",
        ">": "\>",
        "#": "\#",
        "+": "\+",
        "-": "\-",
        ".": "\.",
        "!": "\!",
        "$": "\$",
    }
    text = "".join([replacements.get(c, c) for c in text])


def h(text):
    text = text.replace('\\', r'\\')
    text = text.replace('`', r'\`')
    text = text.replace('*', r'\*')
    text = text.replace('_', r'\_')
    text = text.replace('{', r'\{')
    text = text.replace('}', r'\}')
    text = text.replace('[', r'\[')
    text = text.replace(']', r'\]')
    text = text.replace('(', r'\(')
    text = text.replace(')', r'\)')
    text = text.replace('>', r'\>')
    text = text.replace('#', r'\#')
    text = text.replace('+', r'\+')
    text = text.replace('-', r'\-')
    text = text.replace('.', r'\.')
    text = text.replace('!', r'\!')
    text = text.replace('$', r'\$')


def i(text):
    text = text.replace('\\', r'\\').replace('`', r'\`').replace('*', r'\*').replace('_', r'\_').replace('{', r'\{').replace('}', r'\}').replace('[', r'\[').replace(']', r'\]').replace('(', r'\(').replace(')', r'\)').replace('>', r'\>').replace('#', r'\#').replace('+', r'\+').replace('-', r'\-').replace('.', r'\.').replace('!', r'\!').replace('$', r'\$')

Ось результати для тієї ж вхідної рядок abc&def#ghi:

  • a) 100000 петель, найкраще 3: 6,72 мкс на цикл
  • б) 100000 петель, найкраще 3: 2,64 мкс на цикл
  • в) 100000 петель, найкраще 3: 11,9 мкс на петлю
  • г) 100000 петель, найкраще 3: 4,92 мкс на петлю
  • д) 100000 петель, найкраще 3: 2,96 мкс на цикл
  • f) 100000 петель, найкраще 3: 4,29 мкс на цикл
  • g) 100000 петель, найкраще 3: 4,68 мкс на петлю
  • h) 100000 петель, найкраще 3: 4,73 мкс на цикл
  • i) 100000 петель, найкраще 3: 4,24 мкс на цикл

І з більш довгим рядком введення ( ## *Something* and [another] thing in a longer sentence with {more} things to replace$):

  • a) 100000 петель, найкраще 3: 7,59 мкс на цикл
  • б) 100000 петель, найкраще 3: 6,54 мкс на цикл
  • в) 100000 петель, найкраще 3: 16,9 мкс на цикл
  • г) 100000 петель, найкраще 3: 7,29 мкс на цикл
  • д) 100000 петель, найкраще 3: 12,2 мкс на цикл
  • f) 100000 петель, найкраще 3: 5,38 мкс на цикл
  • g) 10000 петель, найкраще 3: 21,7 мкс на цикл
  • h) 100000 петель, найкраще 3: 5,7 мкс на петлю
  • i) 100000 петель, найкраще 3: 5,13 мкс на цикл

Додавання декількох варіантів:

def ab(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        text = text.replace(ch,"\\"+ch)


def ba(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        if c in text:
            text = text.replace(c, "\\" + c)

З коротшим введенням:

  • ab) 100000 петель, найкраще 3: 7,05 мкс на петлю
  • ба) 100000 петель, найкраще 3: 2,4 мкс на петлю

При більш тривалому введенні:

  • ab) 100000 петель, найкраще 3: 7,71 мкс на цикл
  • ба) 100000 петель, найкраще 3: 6,08 мкс на петлю

Тому я збираюся використовувати baдля читабельності та швидкості.

Додаток

Запропоновані хаками в коментарях, одна різниця між abі ba- це if c in text:чек. Давайте перевіримо їх на ще два варіанти:

def ab_with_check(text):
    for ch in ['\\','`','*','_','{','}','[',']','(',')','>','#','+','-','.','!','$','\'']:
        if ch in text:
            text = text.replace(ch,"\\"+ch)

def ba_without_check(text):
    chars = "\\`*_{}[]()>#+-.!$"
    for c in chars:
        text = text.replace(c, "\\" + c)

Часи в мкс на цикл на Python 2.7.14 та 3.6.3 та на іншій машині, ніж попередній набір, тому неможливо порівняти безпосередньо.

╭────────────╥──────┬───────────────┬──────┬──────────────────╮
 Py, input    ab   ab_with_check   ba   ba_without_check 
╞════════════╬══════╪═══════════════╪══════╪══════════════════╡
 Py2, short  8.81     4.22        3.45     8.01          
 Py3, short  5.54     1.34        1.46     5.34          
├────────────╫──────┼───────────────┼──────┼──────────────────┤
 Py2, long   9.3      7.15        6.85     8.55          
 Py3, long   7.43     4.38        4.41     7.02          
└────────────╨──────┴───────────────┴──────┴──────────────────┘

Можна зробити висновок, що:

  • Ті, хто має чек, на 4 рази швидші, ніж ті, що не мають чека

  • ab_with_checkтрохи попереду на Python 3, але ba(з перевіркою) має більший потенціал на Python 2

  • Однак найбільший урок Python 3 - це в 3 рази швидше, ніж Python 2 ! Немає великої різниці між найповільнішими на Python 3 та найшвидшими на Python 2!


4
Чому це не виключена відповідь?
Курячий суп

Чи if c in text:потрібно в ba?
hackcks

@haccks Це не обов'язково, але це на 2-3 рази швидше. Коротка рядок, з: 1.45 usec per loopі без: 5.3 usec per loop, довжини рядка, з: 4.38 usec per loopі без: 7.03 usec per loop. (Зауважте, що вони безпосередньо не порівнянні з результатами вище, тому що це інша машина тощо).
Hugo

1
@Hugo; Я думаю, що ця різниця у часі replaceвикликається лише тоді, коли cвона виявляється textу випадку, baпоки вона викликається в кожній ітерації в ab.
hackcks

2
@haccks Спасибі, я оновив свою відповідь з подальшими термінами: додавання чека краще для обох, але найбільший урок - Python 3 - до 3 разів швидше!
Гюго

73
>>> string="abc&def#ghi"
>>> for ch in ['&','#']:
...   if ch in string:
...      string=string.replace(ch,"\\"+ch)
...
>>> print string
abc\&def\#ghi

Для чого потрібен був подвійний нахил? Чому просто "\" не працює?
аксолотль

3
Подвійний кут нахилу уникає зворотної косої риски, інакше python інтерпретуватиме "\" як буквальний символ цитати у все ще відкритому рядку.
Ріет

Навіщо це потрібно string=string.replace(ch,"\\"+ch)? Не string.replace(ch,"\\"+ch)достатньо просто ?
MattSom

1
@MattSom substitu () не змінює початковий рядок, але повертає копію. Тож вам потрібне призначення коду для будь-якого ефекту.
Бен Брайан

3
Вам справді потрібен if? Це виглядає як дублювання того, що буде робити заміна в будь-якому випадку.
lorenzo

32

Просто ланцюг replaceподібних функцій

strs = "abc&def#ghi"
print strs.replace('&', '\&').replace('#', '\#')
# abc\&def\#ghi

Якщо заміни буде більше, ви можете зробити це загальним способом

strs, replacements = "abc&def#ghi", {"&": "\&", "#": "\#"}
print "".join([replacements.get(c, c) for c in strs])
# abc\&def\#ghi

30

Ось метод python3 з використанням str.translateта str.maketrans:

s = "abc&def#ghi"
print(s.translate(str.maketrans({'&': '\&', '#': '\#'})))

Друкований рядок є abc\&def\#ghi.


2
Це гарна відповідь, але на практиці це робиться .translate()повільніше, ніж три ланцюгові .replace()(використовуючи CPython 3.6.4).
Чангако

@Changaco Спасибі за те, що він приурочився 👍 На практиці я би використовував replace()себе, але додав цю відповідь заради повноти.
tommy.carstensen

Для великих струн та багатьох замін це має бути швидше, хоча деякі тестування були б непогані ...
Graipher

Ну, це не на моїй машині (те ж саме для 2 і 17 замін).
Грайфер

як '\#'діє? не повинно бути r'\#'чи '\\#'? Можливо, може виникнути проблема із форматуванням блоку коду.
паритет3

16

Ви завжди збираєтесь відкласти зворотний нахил? Якщо так, спробуйте

import re
rx = re.compile('([&#])')
#                  ^^ fill in the characters here.
strs = rx.sub('\\\\\\1', strs)

Це може бути не найефективнішим методом, але я думаю, що це найпростіший.


15
aarrgghh tryr'\\\1'
Джон

10

Пізно на вечірку, але я втратив багато часу з цим питанням, поки не знайшов своєї відповіді.

Короткий і милий, translateперевершуєreplace . Якщо вас більше цікавить функціональність з часом оптимізації, не використовуйте replace.

Також використовуйте, translateякщо ви не знаєте, чи набір символів, які потрібно замінити, перекриває набір символів, які використовуються для заміни.

Справа в точці:

Використовуючи, replaceви наївно очікуєте, що фрагмент "1234".replace("1", "2").replace("2", "3").replace("3", "4")повернеться "2344", але він повернеться фактично "4444".

Переклад, здається, виконує те, що OP спочатку бажав.


6

Ви можете розглянути можливість створення загальної функції втечі:

def mk_esc(esc_chars):
    return lambda s: ''.join(['\\' + c if c in esc_chars else c for c in s])

>>> esc = mk_esc('&#')
>>> print esc('Learn & be #1')
Learn \& be \#1

Таким чином ви можете зробити свою функцію настроюваною зі списком символів, який слід уникати.


3

FYI, це дуже мало користі для ОП, але це може бути корисно іншим читачам (будь ласка, не зволікайте, я це знаю).

Як дещо смішна, але цікава вправа, хотілося побачити, чи можу я використати функціональне програмування python для заміни декількох символів. Я впевнений, що це НЕ перемагає просто виклик заміну () двічі. І якщо продуктивність була проблемою, ви можете легко перемогти це в іржі, С, Джулія, Перл, Ява, javascript і, можливо, навіть awk. Він використовує зовнішній пакет "помічників" під назвою pytoolz , прискорений за допомогою цитону ( cytoolz, це пакет pypi ).

from cytoolz.functoolz import compose
from cytoolz.itertoolz import chain,sliding_window
from itertools import starmap,imap,ifilter
from operator import itemgetter,contains
text='&hello#hi&yo&'
char_index_iter=compose(partial(imap, itemgetter(0)), partial(ifilter, compose(partial(contains, '#&'), itemgetter(1))), enumerate)
print '\\'.join(imap(text.__getitem__, starmap(slice, sliding_window(2, chain((0,), char_index_iter(text), (len(text),))))))

Я навіть не збираюся пояснювати це тим, що ніхто не заважає використовувати це для багаторазової заміни. Тим не менше, я відчував себе дещо досягнутим у цьому і думав, що це може надихнути інших читачів або виграти конкурс з придушення коду.


1
"функціональне програмування" не означає "використовувати якомога більше функцій", ви знаєте.
Крейг Ендрюс

1
Це ідеально хороший, чистий функціональний багатозарядний замінник: gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e Ніяких виділень, ні мутацій, ні побічних ефектів. Читаємо також.
Крейг Ендрюс

1

Використовуючи зменшення, яке доступне в python2.7 та python3. *, Ви можете легко замінити нескінченні підряди чистим та пітонічним способом.

# Lets define a helper method to make it easy to use
def replacer(text, replacements):
    return reduce(
        lambda text, ptuple: text.replace(ptuple[0], ptuple[1]), 
        replacements, text
    )

if __name__ == '__main__':
    uncleaned_str = "abc&def#ghi"
    cleaned_str = replacer(uncleaned_str, [("&","\&"),("#","\#")])
    print(cleaned_str) # "abc\&def\#ghi"

У python2.7 вам не потрібно імпортувати скорочення, а у python3. * Ви повинні імпортувати його з модуля functools.



1

Як щодо цього?

def replace_all(dict, str):
    for key in dict:
        str = str.replace(key, dict[key])
    return str

тоді

print(replace_all({"&":"\&", "#":"\#"}, "&#"))

вихід

\&\#

подібний до відповіді


0
>>> a = '&#'
>>> print a.replace('&', r'\&')
\&#
>>> print a.replace('#', r'\#')
&\#
>>> 

Ви хочете використовувати рядок 'raw' (позначається префіксом 'r', що замінює рядок), оскільки необроблені рядки не обробляють зворотну косу рису спеціально.


0

просунутий спосіб використання регексу

import re
text = "hello ,world!"
replaces = {"hello": "hi", "world":" 2020", "!":"."}
regex = re.sub("|".join(replaces.keys()), lambda match: replaces[match.string[match.start():match.end()]], text)
print(regex)
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.