Заменить несколько (специальных) символов - наиболее эффективный способ?

В текстах, которые у меня есть, я хочу заменить следующие специальные символы одним пробелом:

symbols = ["`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", " = ", "{", "[", "]", "}", "|", "\\", ":", ";", "\"", "<", ",", ">", ".", "?", "/"]

Каков наиболее эффективный способ (с точки зрения времени выполнения кода) сделать это?

Например, я хочу это:

(Hello World)] *!

стать таким:

Hello World

Методы-кандидаты выглядят следующим образом:

  1. понимание списка
  2. .replace()
  3. .translate()
  4. регулярные выражения

Просьба уточнить. Вы хотите заменить каждый символ пробелом? Или вы хотите убрать каждый символ целиком, ничем его не заменив? Потому что (Hello World)] *! не станет Hello World, если вы замените все его специальные символы пробелами. Становится [one space]Hello World[five spaces].

Kevin 30.05.2019 15:07

@ Кевин, можешь сделать оба или хотя бы последнее?

Outcast 30.05.2019 15:08
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
3
2
874
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Для эффективного решения вы можете использовать str.maketrans для этого. Обратите внимание, что после того, как таблица перевода определена, остается только сопоставить символы в строке. Вот как вы могли бы это сделать:

symbols = ["`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+",
           " = ", "{", "[", "]", "}", "|", "\\", ":", ";", "\"", "<", ",", ">", ".", "?", "/"]

Начните с создания словаря из символов, используя dict.fromkeys, установив один пробел в качестве значения для каждой записи, и создайте таблицу перевода из словаря:

d = dict.fromkeys(''.join(symbols), ' ')
# {'`': ' ', ',': ' ', '~': ' ', '!': ' ', '@': ' '...
t = str.maketrans(d)

Затем вызовите метод строки translate, чтобы сопоставить символы в приведенном выше словаре с пустым пространством:

s = '~this@is!a^test@'
s.translate(t)
# ' this is a test '

Спасибо за ответ (плюс). Не могли бы вы немного объяснить "\\" и "\""? Мне совершенно непонятно, что они удаляют. По-видимому, то, как они написаны, также связано с тем, что для экранирования используется обратная косая черта.

Outcast 30.05.2019 17:12

Они будут удалять символы \ и " соответственно, однако их необходимо убрать, поскольку они имеют особое значение. Это делается путем добавления обратной косой черты @PoeteMaudit.

yatu 30.05.2019 17:25

Пожалуйста. Не забывайте, что вы можете согласиться, если это решило это для вас :) @PoeteMaudit

yatu 30.05.2019 17:27

Конечно, это решило для меня, но некоторые ребята ниже сделали полное сравнение, поэтому не уверены, какое решение принять: D

Outcast 30.05.2019 17:35

Все варианты str.translate() будут иметь примерно одинаковую производительность и будут намного быстрее, чем другие альтернативы. Вы должны принять этот ответ, если хотите заменить символы пробелами (или мой, если хотите полностью удалить их :).

Alain T. 30.05.2019 17:40

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

yatu 30.05.2019 18:06
s = '''
def translate_():
    symbols = '`,~,!,@,#,$,%,^,&,*,(,),_,-,+,=,{,[,],},|,\,:,;,",<,,,>,.,?,/'
    s = '~this@is!a^test @'
    t = str.maketrans(dict.fromkeys(symbols, ' '))
    s.translate(t)
    return s

def replace_():
    symbols = '`,~,!,@,#,$,%,^,&,*,(,),_,-,+,=,{,[,],},|,\,:,;,",<,,,>,.,?,/'
    s = '~this@is!a^test @'
    for symbol in symbols:
        s = s.replace(symbol, ' ')
    return s
'''

print(timeit.timeit('replace_()', setup=s, number=100000))
print(timeit.timeit('translate_()', setup=s, number=100000))

Будет печатать:

0.7663131961598992

0.4139239452779293

Таким образом, замена на translate почти в 2 раза быстрее, чем использование нескольких replace.

Проголосуйте за показ timeit результатов. Но обратите внимание, что результаты зависят от длины строки и количества символов, которые необходимо заменить.

Ralf 30.05.2019 15:49

Проведя несколько тестов, могу сказать, что str.translate() — лучший вариант.

Входные данные:

symbols = {"`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", " = ", "{", "[", "]", "}", "|", "\\", ":", ";", "\"", "<", ",", ">", ".", "?", "/"}
translate_table = {126: None, 93: None, 91: None, 125: None, 92: None, 42: None, 45: None, 94: None, 62: None, 47: None, 35: None, 59: None, 44: None, 58: None, 60: None, 124: None, 61: None, 36: None, 95: None, 43: None, 96: None, 123: None, 64: None, 33: None, 38: None, 63: None, 46: None, 34: None, 41: None, 37: None, 40: None}
regular_expression = "[`~!@#$%^&*()_\-+ = {[\]}|\\:;\"<,>.?/]"
small_document = "Some**r@an]]\"dom t##xt"
normal_document = "TbsX^Kt$FZ%haZe+sLxu:Al\"xNAL\\Kix[mHp_gn]PrG`DqGd~GdNc;BoEq.SYD?Rp>ukq,UfO<XdTc=RUH}oifc&oP!CB*me@Qv{Qf-Li)gmXL/IQH#mne(Khaj|"
big_document = "QOfY+dymyoGBAxTAoIeM+jEWlaECUZEUXuMvprJOqFtQR*OiHtTFZkUNbYipSTTDPOVkIdGTcjWrQmbmthKBHBSEOZ)lQAIJOrVgmGGFdtqbuFfj<Dls<JWtKczAFMPYMemiJBJHdPeeul\\x>lGIBvUsxBokagvVovrrdxdKMtAKx>MEexYv>DGqPUXYaBQKwiSIUobrPQYjilhHMQunE;RiqOZPTnyOEgRrpxcuobvvmGkFpTqgMxYYhrmRRnauiqgvCmZ\"UauceaXsgAMSakxewzPrlIrYkVCVZaEGh]qiizYyzbkcHPF@qQsQMfHPDEbEnWtrCFoARUYAloOcctqmL@hegZbfhsHaJOxOxzQhZAVjVDgokosATfhKMT!WYyPWKcKAHKCzQGGJOCglYGZbftsuyntXZUKNqgGlsLJqgN,pUcOoA/tStXFXgpoSErgvw/OUMPWjJwt=bhMAIDayOZXJm=ifYYUuAvSIZjwnBfktNvEvZmvQso%HiNZEVqoDR%nQBtCkhjSfVfDuRSRsvp-sCunjDDUYSEVLICQdisxhEfqkUTkiPlLiUNNwrvO#WTDmweZyMeIbgNXkIsvaJeHYXV(HvRcGNZM(PPRIAyyLWivGiqMVBtwObqLfEEISyyjGNEdUU:ys`dXcVawkIEAjFXky`RUXNTm`LDM}mwTOcmsSo}haJXPnkwOhKLYwve}SWifzKq}grw}fMSQXXWguUQtlWpPZQymR^wBKEyolFlZnzEEmehSNenOqDOHWRit[Npm?R?DIPXAmQYYBbmJofxUzzWBsVCoPI?VmpXhoMxCfXyHEHowXzIJvExThiffLhBTtma_jk_NrbkPCGGypXvOuBqBxDYfC{bwIHoaqnJSKytxwWXBNnKG~PKuQklGblEwH~rJoGpKZmm~tTEFnPLdmzfrqJibMYIykzL$RZLPmsZjB$AAbZwFnByOydEOIfFvTaEQaSjbpeBZuUGY&ZfPQgLihmPYrhZxSwMzLrNF.WjFiDCLyXksdkLeMHVCfrdgCAotElQ|"
no_match_document = "XOtasggWqhtSLJpHEGoCmMRepFBlRfAGKTLPcEtKonFVsPgvWgAbvJVeMWILPgLapwAmTgXWVbxOJtUFmMygzIqYPqyAxzwElTFyYcGdtnNa"

Код:

def func1(doc):
    for c in symbols:
        doc = doc.replace(c, "")
    return doc


def func2(doc):
    return doc.translate(translate_table)


def func3(doc):
    return re.sub(regular_expression, "", doc)


def func4(doc):
    return "".join(c for c in doc if c not in symbols)

Результаты теста:

func1(small_document):      0.701037002
func1(normal_document):     1.1260866900000002
func1(big_document):        3.4234831459999997
func1(no_match_document):   0.7740780450000004

func2(small_document):      0.14135037500000003
func2(normal_document):     0.5368806810000004
func2(big_document):        0.8128472860000002
func2(no_match_document):   0.394245089

func3(small_document):      0.3157141610000007
func3(normal_document):     0.927359323000001
func3(big_document):        1.9310377590000005
func3(no_match_document):   0.18656399199999996

func4(small_document):      0.3034549070000008
func4(normal_document):     1.3695875739999988
func4(big_document):        10.115730064
func4(no_match_document):   1.2086623230000022

УПД.

Входные данные, которые я предоставил, были «подготовлены» специально для тестирования чистого метода.

Чтобы сгенерировать translate_table, я использовал следующее понимание dict:

translate_table = {ord(s): None for s in symbols}

Здесь — это ссылка на веб-сайт для проверки регулярных выражений (может быть полезно).


В случае, если вы хотите пересчитать тесты самостоятельно, вот код:

    if __name__ == '__main__':
    import timeit
    print("func1(small_document)", timeit.timeit("func1(small_document)", setup = "from __main__ import func1, small_document", number=100000))
    print("func1(normal_document): ", timeit.timeit("func1(normal_document)", setup = "from __main__ import func1, normal_document", number=100000))
    print("func1(big_document): ", timeit.timeit("func1(big_document)", setup = "from __main__ import func1, big_document", number=100000))
    print("func1(no_match_document): ", timeit.timeit("func1(no_match_document)", setup = "from __main__ import func1, no_match_document", number=100000))

    print("func2(small_document): ", timeit.timeit("func2(small_document)", setup = "from __main__ import func2, small_document", number=100000))
    print("func2(normal_document): ", timeit.timeit("func2(normal_document)", setup = "from __main__ import func2, normal_document", number=100000))
    print("func2(big_document): ", timeit.timeit("func2(big_document)", setup = "from __main__ import func2, big_document", number=100000))
    print("func2(no_match_document): ", timeit.timeit("func2(no_match_document)", setup = "from __main__ import func2, no_match_document", number=100000))

    print("func3(small_document): ", timeit.timeit("func3(small_document)", setup = "from __main__ import func3, small_document", number=100000))
    print("func3(normal_document): ", timeit.timeit("func3(normal_document)", setup = "from __main__ import func3, normal_document", number=100000))
    print("func3(big_document): ", timeit.timeit("func3(big_document)", setup = "from __main__ import func3, big_document", number=100000))
    print("func3(no_match_document): ", timeit.timeit("func3(no_match_document)", setup = "from __main__ import func3, no_match_document", number=100000))

    print("func4(small_document): ", timeit.timeit("func4(small_document)", setup = "from __main__ import func4, small_document", number=100000))
    print("func4(normal_document): ", timeit.timeit("func4(normal_document)", setup = "from __main__ import func4, normal_document", number=100000))
    print("func4(big_document): ", timeit.timeit("func4(big_document)", setup = "from __main__ import func4, big_document", number=100000))
    print("func4(no_match_document): ", timeit.timeit("func4(no_match_document)", setup = "from __main__ import func4, no_match_document", number=100000))

Эй, спасибо, я действительно хотел увидеть это полное сравнение (голос). Однако из вашего кода неясно, каковы значения ваших переменных, например. translate_table и т. д.

Outcast 30.05.2019 15:22

какое регулярное выражение вы используете? Это определит сложность, т.е. время. например, используйте re.sub('(?i)[^a-z ]+','',doc)

KU99 30.05.2019 15:24

@PoeteMaudit, я добавил входные данные в сообщение.

Olvin Roght 30.05.2019 15:24

также с вашей таблицей перевода у вас уже есть значения в ascii. Используйте исходные значения вместо asci для сравнения

KU99 30.05.2019 15:25

@Onyambu, если честно, я еще не использовал его, потому что трудно придумать его без ошибок - это проблема с регулярным выражением; довольно трудно учиться и писать. Вы можете посмотреть здесь: stackoverflow.com/questions/56376461/…

Outcast 30.05.2019 15:26

@Onyambu, я сделал это, чтобы минимизировать затраты времени в процессе тестирования. Конечно, это не то, как вы должны использовать в реальном коде.

Olvin Roght 30.05.2019 15:26

Хорошо @OlvinRoght, поэтому я предполагаю, что тогда сравнение времени точное.

Outcast 30.05.2019 15:27

Это просто означает, что вы даете ему преимущество. Так у него будет большая производительность. Если это так, вы можете использовать ur'\p{P}+' для регулярного выражения, чтобы удалить все знаки препинания. Я имею в виду, что ваше сравнение несправедливо

KU99 30.05.2019 15:29

@Onyambu, я удалил «headstart», чтобы рассчитать, сколько потребуется конкретному методу для замены. Во всяком случае, я добавил tt = {ord(s): None for s in symbols}, и это все еще быстрее, чем re ~ 50%.

Olvin Roght 30.05.2019 15:35

Хотя имейте в виду, что ваше время сильно отличается от времени @vurmux выше (если я что-то не упустил). (Пожалуйста, не поймите меня неправильно; это выглядит хорошо, но я просто хочу быть уверенным в этом)

Outcast 30.05.2019 15:36

@PoeteMaudit, я проверял на Mac mini, у него, наверное, комп помощнее :D

Olvin Roght 30.05.2019 15:37

@PoeteMaudit, я добавил код для теста, вы можете проверить все сами;)

Olvin Roght 30.05.2019 15:41

@PoeteMaudit, я добавил тесты для строк разной длины, можете проверить.

Olvin Roght 30.05.2019 16:10

Кажется, это действительно зависит от таблицы ввода и перевода. Я только что попробовал для нас (входные данные - это несколько тестовых журналов размером до ~ 60 МБ, а перевод избегает символов HTML + удалить ~ 4 «плохих» символа, поэтому всего ~ 10 замен), и translate на самом деле был в ~ 2 раза медленнее чем replace.

The Godfather 03.02.2022 12:29

@Крестный отец, действительно. Ожидаемое применение .translate() сильно отличается от замены «плохих» символов. Его можно использовать для замены, но, поскольку он требует перебора таблицы перевода, в некоторых случаях он может быть медленнее, чем .replace().

Olvin Roght 03.02.2022 12:38

@OlvinRoght Я добавил свой ответ с моими выводами. Это было нелогично, потому что я ожидаю, что replace будет много раз перебирать всю строку, а translate будет перебирать строку только один раз, вместо этого выполняя итерацию по таблице переводов... в то время как replace - это O(K*N) (где K - длина таблицы перевода, а N - длина строки), а translate — это O(N) (учитывая, что поиск в таблице перевода — это O(1), я бы действительно ожидал, что перевод будет быстрее.

The Godfather 03.02.2022 12:54

Мой код заменяет символы пробелами и НЕ удаляет эти пробелы.

Для коротких строк .join() работает быстро, но для больших строк .translate() быстрее, если нужно много заменить. Удивительно, но .replace() все еще очень быстро, если нужно сделать несколько замен.

text: '(Hello World)] *!'
using_replace                     0.046
using_join                        0.016
using_translate                   0.031

text: '~this@is!a^test@'
using_replace                     0.046
using_join                        0.017
using_translate                   0.029

text: '~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@~/()&this@isasd!&=)(/as/dw&%#a^test@'
using_replace                     0.195
using_join                        2.327
using_translate                   0.061

text: 'a long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replacea long text without chars to replace'
using_replace                     0.051
using_join                        2.100
using_translate                   0.064

Сравнение некоторых стратегий:

def using_replace(text, symbols_to_replace, replacement=' '):
    for char in symbols_to_replace:
        text = text.replace(char, replacement)

    return text

def using_join(text, symbols_to_replace, replacement=' '):
    return ''.join(
        replacement if char in symbols_to_replace else char
        for char in text)

def using_translate(text, symbols_to_replace, replacement=' '):
    translation_dict = str.maketrans(
        dict.fromkeys(symbols_to_replace, replacement))

    return text.translate(translation_dict)

с этим кодом timeit для разных текстов:

    # a 'set' for faster lookup
    symbols = {
        '`', '~', '!', '@', '#', '$', '%', '^', '&', '*',
        '(', ')', '_', '-', '+', '=', '{', '[', ']', '}',
        '|', '/', ':', ';', '"', '<', ',', '>', '.', '?',
        '\\',
    }

    text_list = [
        '(Hello World)] *!',
        '~this@is!a^test@',
        '~/()&this@isasd!&=)(/as/dw&%#a^test@' * 1000,
        'a long text without chars to replace' * 1000,
    ]
    for s in text_list:
        assert (
                using_replace(s, symbols)
                == using_join(s, symbols)
                == using_translate(s, symbols))

    for s in text_list:
        print()
        print('text:', repr(s))
        for func in [using_replace, using_join, using_translate]:
            t = timeit.timeit(
                'func(s, symbols)',
                'from __main__ import func, s, symbols',
                number=10000)
            print('{:30s} {:8.3f}'.format(func.__name__, t))

Примечание: .translate() показывает линейное время в зависимости от длины строки (O(n)).

Ralf 30.05.2019 15:38

С другой стороны, .replace() работает быстро, если замен мало, и медленно, если нужно сделать много замен.

Ralf 30.05.2019 15:39

Выглядит интересно и исчерпывающе, спасибо (upvote). Итак, не могли бы вы сказать, какова вычислительная сложность этих методов?

Outcast 30.05.2019 15:42
.translate() является линейным (но с очень маленьким коэффициентом, меньше 1) и зависит от длины строки и (в меньшей степени) от размера таблицы перевода.
Ralf 30.05.2019 15:44
.join() и replace() определить сложнее, но, вероятно, они тоже линейны (с коэффициентом 1). И на них также сильно влияет то, сколько символов нужно заменить, поэтому это очень изменчиво, а НЕ просто линейная сложность.
Ralf 30.05.2019 15:46

str.translate() действительно самый быстрый метод. Вот краткий способ построить таблицу перевода для исключения символов:

symbols = ["`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", " = ", "{", "[", "]", "}", "|", "\\", ":", ";", "\"", "<", ",", ">", ".", "?", "/"]
removeSymbols = str.maketrans("","","".join(symbols))

cleanText = "[Hello World] *!".translate(removeSymbols)
print(cleanText) # "Hello World "

Функции maketrans() могут принимать 3 параметра, первый — это строка с заменяемыми символами, второй — их замены, а третий — список символов, которые нужно удалить. Чтобы просто удалить все символы, нам просто нужно предоставить 3-му параметру строку, содержащую символы, которые нужно удалить.

Затем таблица перевода removeSymbols выполняет полное удаление символов из списка символов.

Чтобы заменить пробелами, постройте таблицу перевода следующим образом:

removeSymbols = str.maketrans("".join(symbols)," "*len(symbols))

Хм, выглядит интересно и работает (проголосуйте). Но я хотел бы немного понять, что он делает. В чем смысл *3? Также вы заменяете эти символы пробелами или ничего туда не добавляете?

Outcast 30.05.2019 17:20

@Poete Maudit, на самом деле это было сложнее, чем нужно. Мне не нужно было вводить строку символов 3 раза. Достаточно было использовать его только для третьего параметра maketrans(). Я поправил пример.

Alain T. 30.05.2019 17:29

Да, на самом деле я думаю, что в этом отношении ответ @yatu выше является самым кратким.

Outcast 30.05.2019 17:36

Хотя ответ Рохта - лучший ИМО и показывает объективный подход, я хотел бы отметить, что translate не всегда лучший! Вам действительно нужно проверить это самостоятельно, результат будет зависеть от ваших входных данных.

Немного теории сложности

(отказ от ответственности: я не просматривал исходный код Python, поэтому ниже приведено то, что я ожидал), учитывая, что у нас есть K символы для замены и N символы в исходной строке:

str.replace должен в основном перебирать всю строку, проверяя каждый символ и заменяя его, если он соответствует параметру. Выглядит как чистый O(N) , поэтому для K замен будет O(K*N).

С другой стороны, translate должен перебирать всю строку только один раз, проверяя каждый символ на соответствие в таблице перевода. Так как таблица переводов представляет собой хэш-карту, поиск там происходит O(1), поэтому весь перевод вообще не зависит от K, должен быть O(N)

Вопрос - почему тогда replace быстрее в моем случае??? Я не знаю :(


Я столкнулся с этим, когда рефакторил наш скрипт, анализируя тестовые журналы (довольно большие файлы, думаю, 60 МБ+), и он очищал его от некоторых случайных символов, а также выполнял некоторую HTML-санитизацию, вот словарь замены:

replace_dict = {
        "&": "&amp;",
        "\"": "&quot;",
        "<": "&lt;",
        ">": "&gt;",
        "\u0000": "",
        "\u0007": "",
        "\u0008": "",
        "\u001a": "",
        "\u001b": "",
    }

Когда я увидел в исходном коде всего 9 вызовов str.replace в строке, это была моя первая мысль — «Что за хрень, а давайте вместо этого будем использовать translate», это должно быть намного быстрее. Однако в моем случае я обнаружил, что replace на самом деле самый быстрый метод.

Тестовый сценарий:

replace_dict = {
    "&": "&amp;",
    "\"": "&quot;",
    "<": "&lt;",
    ">": "&gt;",
    "\u0000": "",
    "\u0007": "",
    "\u0008": "",
    "\u001a": "",
    "\u001b": "",
}

symbols = list(replace_dict.keys())
translate_table = {ord(k): v if v else None for k, v in replace_dict.items()}
with open("myhuge.log") as f:
    big_document = f.read()


def func_replace(doc):
    for k, v in replace_dict.items():
        doc = doc.replace(k, v)
    return doc


def func_trans(doc):
    return doc.translate(translate_table)


def func_list_comp(doc):
    # That's not really equivalent to two methods above, but still good for perf comparison
    return "".join(c for c in doc if c not in symbols)


if __name__ == '__main__':
    import timeit
    number = 5
    print("func_replace(big_document): ", timeit.timeit("func_replace(big_document)",
          setup = "from __main__ import func_replace, big_document", number=number))

    print("func_trans(big_document): ", timeit.timeit("func_trans(big_document)",
          setup = "from __main__ import func_trans, big_document", number=number))

    print("func_list_comp(big_document): ", timeit.timeit("func_list_comp(big_document)",
          setup = "from __main__ import func_list_comp, big_document", number=number))

Итак, вот результаты:

func_replace(big_document): 4.945449151098728

func_trans(big_document): 15.22288554534316

func_list_comp(big_document): 45.01621600985527

Из этого я могу сделать два вывода:

  • Понимание списка очень медленное, не используйте его.
  • Вопреки интуиции, в некоторых случаях replace может быть в несколько раз быстрее, чем translate. Если ваша таблица замены не слишком велика, а строки, над которыми вы работаете, слишком велики, кажется, что replace будет лучше.
symbols = list(replace_dict.keys()) => symbols = set(replace_dict)
Olvin Roght 03.02.2022 13:07

Вы также можете попробовать прочитать файл как двоичный ("rb") и использовать bytes.translate(), что может быть значительно быстрее.

Olvin Roght 03.02.2022 13:15

@OlvinRoght Я не уверен, что понимаю, как использовать bytes.translate в этом случае, поскольку кажется, что он работает только для замены одного символа, а не для замены строки (и, например, &quot; - это строка из 6 символов). А насчет смены символов \u.. - это вообще имеет какое-то значение? Я чувствую, что \u-формат выглядит более последовательным и читабельным...

The Godfather 03.02.2022 13:34

Я нашел вариант с \xFF более читаемым, хорошо. Но рекомендация из первого комментария по-прежнему важна. Кроме того, измените "".join(...) на "".join([...]), это также немного улучшит последний метод.

Olvin Roght 03.02.2022 14:03

@OlvinRoght, но это совсем не имеет значения, не так ли? Мы оптимизируем не саму тестовую программу, а фактические методы замены. Это общий код, выполняемый для всех тестовых методов в setup, поэтому даже если я добавлю sleep(1) вверху файла, это ничего не изменит.

The Godfather 03.02.2022 14:09
func_list_comp использует symbols, и он продемонстрирует немного лучшую производительность, если symbols будет инициализирован как set.
Olvin Roght 03.02.2022 14:11

Хорошо, я нашел, что происходит. Вы также узнаете, будете ли вы использовать следующую таблицу перевода {ord("&"): "a", ord('"'): "q", ord("<"): "l", ord(">"): "g", 0: None, 7: None, 8: None, 0x1a: None, 0x1b: None}. Есть две функции перевода: unicode_fast_translate() и _PyUnicode_TranslateCharmap(), которые вызываются первыми при выполнении определенных условий.

Olvin Roght 03.02.2022 15:57

По сути, если ваша строка замены не имеет длины 1 символ, str.translate будет использовать сложный метод, который демонстрирует значительно более низкую производительность. Вы можете покопаться в источниках и сравнить с функции замены строк, чтобы включить некоторые пояснения в свой ответ.

Olvin Roght 03.02.2022 16:02

Другие вопросы по теме