Лучший способ заменить несколько символов в строке?

201

Мне нужно заменить некоторые символы следующим образом: &\&, #\#, ...

Я кодировал следующим образом, но я думаю, что должен быть какой-то лучший способ. Есть намеки?

strs = strs.replace('&', '\&')
strs = strs.replace('#', '\#')
...
prosseek
источник

Ответы:

436

Замена двух символов

Я рассчитал все методы в текущих ответах вместе с одним дополнительным.

С входной строки abc&def#ghiи заменяя & -> \ & и # -> \ #, самый быстрый способ был приковать вместе эти замены , как это: text.replace('&', '\&').replace('#', '\#').

Сроки для каждой функции:

  • а) 1000000 петель, лучшее из 3: 1,47 мкс на петлю
  • б) 1000000 петель, лучшее из 3: 1,51 мкс на петлю
  • в) 100000 петель, лучшее из 3: 12,3 мкс на петлю
  • г) 100000 петель, лучшее из 3: 12 мкс на петлю
  • д) 100000 петель, лучшее из 3: 3,27 мкс на петлю
  • f) 1000000 петель, лучшее из 3: 0,817 мкс на петлю
  • г) 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:

  • а) 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 мкс на петлю
  • г) 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$):

  • а) 100000 петель, лучшее из 3: 7,59 мкс на петлю
  • б) 100000 петель, лучшее из 3: 6,54 мкс на петлю
  • в) 100000 петель, лучшее из 3: 16,9 мкс на петлю
  • d) 100000 петель, лучшее из 3: 7,29 мкс на петлю
  • e) 100000 петель, лучшее из 3: 12,2 мкс на петлю
  • f) 100000 петель, лучшее из 3: 5,38 мкс на петлю
  • г) 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)

С более коротким вводом:

  • а.б) 100000 петель, лучшее из 3: 7,05 мкс на петлю
  • ba) 100000 петель, лучшее из 3: 2,4 мкс на петлю

С более длинным вводом:

  • а.б) 100000 циклов, лучшее из 3: 7,71 мкс на цикл
  • ba) 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?
хак
@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.
хак
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
ghostdog74
источник
Зачем нужен двойной обратный слеш? Почему просто "\" не работает?
Аксолотль
3
Двойная обратная косая черта экранирует обратную косую черту, в противном случае python интерпретирует символ "\" как буквальный символ кавычки в еще открытой строке.
Riet
Зачем тебе это string=string.replace(ch,"\\"+ch)? Разве не string.replace(ch,"\\"+ch)достаточно?
MattSom
1
@MattSom replace () не изменяет исходную строку, но возвращает копию. Таким образом, вам нужно присвоение кода, чтобы иметь какой-либо эффект.
Бен Брайан
3
Вам действительно нужно, если? Это похоже на дублирование того, что замена будет делать в любом случае.
Лоренцо
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
thefourtheye
источник
30

Вот метод python3, использующий str.translateи str.maketrans:

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

Печататься строка abc\&def\#ghi.

tommy.carstensen
источник
2
Это хороший ответ, но на практике выполнение одного .translate()происходит медленнее, чем три .replace()(с использованием CPython 3.6.4).
Чангако,
@Changaco Спасибо за выбор времени timing На практике я бы использовал replace()себя, но я добавил этот ответ для полноты картины.
tommy.carstensen
Для больших строк и многих замен это должно быть быстрее, хотя некоторые тесты были бы хорошими ...
Graipher
Ну, это не на моей машине (то же самое для 2 и 17 замен).
Graipher
как '\#'действует? не должно быть r'\#'или '\\#'? Может быть проблема форматирования блока кода, возможно.
parity3
16

Вы всегда собираетесь добавить обратную косую черту? Если так, попробуйте

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

Возможно, это не самый эффективный метод, но я думаю, что он самый простой.

kennytm
источник
15
aarrgghh попробуйr'\\\1'
Джон Мачин
10

Опоздал на вечеринку, но я потерял много времени на эту проблему, пока не нашел свой ответ.

Короткий и сладкий, translateпревосходящийreplace . Если вас больше интересует функциональность с течением времени, не используйте оптимизацию replace.

Также используйте, translateесли вы не знаете, перекрывает ли набор заменяемых символов набор символов, используемых для замены.

Дело в точке:

При использовании replaceвы наивно ожидаете, что фрагмент "1234".replace("1", "2").replace("2", "3").replace("3", "4")вернется "2344", но на самом деле он вернется "4444".

Похоже, что перевод выполняет то, что изначально хотел OP.

Sebastialonso
источник
6

Вы можете написать общую функцию escape:

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

К вашему сведению, это мало что значит для ОП, но может быть полезно другим читателям (пожалуйста, не понижайте голос, я знаю об этом).

Как смешное, но интересное упражнение, я хотел посмотреть, смогу ли я использовать функциональное программирование на Python для замены нескольких символов. Я почти уверен, что это НЕ бьет просто вызов replace () дважды. И если производительность была проблемой, вы могли бы легко победить это в ржавчине, C, Джулия, Perl, Java, Javascript и, возможно, даже awk. Он использует внешний пакет помощников, называемый pytoolz , ускоренный через cython ( 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),))))))

Я даже не собираюсь объяснять это, потому что никто не потрудится использовать это для выполнения множественной замены. Тем не менее, я почувствовал, что достиг этого, и подумал, что это может вдохновить других читателей или выиграть конкурс обфускации кода.

parity3
источник
1
Знаете, «функциональное программирование» не означает «использование как можно большего количества функций».
Крейг Эндрюс
1
Это очень хороший, чисто функциональный заменитель из нескольких символов: gist.github.com/anonymous/4577424f586173fc6b91a215ea2ce89e Нет выделений, нет мутаций, нет побочных эффектов. Читаемый тоже.
Крейг Эндрюс
1

Используя Reduction, который доступен в python2.7 и python3. *, Вы можете легко заменить множественные подстроки чистым и pythonic способом.

# 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 вам не нужно импортировать Reduce, но в Python3. * Вы должны импортировать его из модуля functools.

CasualCoder3
источник
1

Может быть, простой цикл для замены символов:

a = '&#'

to_replace = ['&', '#']

for char in to_replace:
    a = a.replace(char, "\\"+char)

print(a)

>>> \&\#
Тиаго Вуцке де Оливейра
источник
1

Как насчет этого?

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

затем

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

вывод

\&\#

похоже на ответ

jewishmoses
источник
0
>>> a = '&#'
>>> print a.replace('&', r'\&')
\&#
>>> print a.replace('#', r'\#')
&\#
>>> 

Вы хотите использовать «сырую» строку (обозначенную префиксом «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)
Ahmed4end
источник