В текстах, которые у меня есть, я хочу заменить следующие специальные символы одним пробелом:
symbols = ["`", "~", "!", "@", "#", "$", "%", "^", "&", "*", "(", ")", "_", "-", "+", " = ", "{", "[", "]", "}", "|", "\\", ":", ";", "\"", "<", ",", ">", ".", "?", "/"]
Каков наиболее эффективный способ (с точки зрения времени выполнения кода) сделать это?
Например, я хочу это:
(Hello World)] *!
стать таким:
Hello World
Методы-кандидаты выглядят следующим образом:
.replace()
.translate()
@ Кевин, можешь сделать оба или хотя бы последнее?
Для эффективного решения вы можете использовать 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 '
Спасибо за ответ (плюс). Не могли бы вы немного объяснить "\\"
и "\""
? Мне совершенно непонятно, что они удаляют. По-видимому, то, как они написаны, также связано с тем, что для экранирования используется обратная косая черта.
Они будут удалять символы \ и " соответственно, однако их необходимо убрать, поскольку они имеют особое значение. Это делается путем добавления обратной косой черты @PoeteMaudit.
Пожалуйста. Не забывайте, что вы можете согласиться, если это решило это для вас :) @PoeteMaudit
Конечно, это решило для меня, но некоторые ребята ниже сделали полное сравнение, поэтому не уверены, какое решение принять: D
Все варианты str.translate() будут иметь примерно одинаковую производительность и будут намного быстрее, чем другие альтернативы. Вы должны принять этот ответ, если хотите заменить символы пробелами (или мой, если хотите полностью удалить их :).
Конечно, @PoeteMaudit :) Обратите внимание, что целью принятия ответа является предоставление полезной ссылки для будущих посетителей, которые могут искать подобное решение. Поэтому всегда рекомендуется принимать оптимальный ответ для данной проблемы (хотя, конечно, всегда приятно иметь тайминги)
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
результатов. Но обратите внимание, что результаты зависят от длины строки и количества символов, которые необходимо заменить.
Проведя несколько тестов, могу сказать, что 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
и т. д.
какое регулярное выражение вы используете? Это определит сложность, т.е. время. например, используйте re.sub('(?i)[^a-z ]+','',doc)
@PoeteMaudit, я добавил входные данные в сообщение.
также с вашей таблицей перевода у вас уже есть значения в ascii. Используйте исходные значения вместо asci для сравнения
@Onyambu, если честно, я еще не использовал его, потому что трудно придумать его без ошибок - это проблема с регулярным выражением; довольно трудно учиться и писать. Вы можете посмотреть здесь: stackoverflow.com/questions/56376461/…
@Onyambu, я сделал это, чтобы минимизировать затраты времени в процессе тестирования. Конечно, это не то, как вы должны использовать в реальном коде.
Хорошо @OlvinRoght, поэтому я предполагаю, что тогда сравнение времени точное.
Это просто означает, что вы даете ему преимущество. Так у него будет большая производительность. Если это так, вы можете использовать ur'\p{P}+'
для регулярного выражения, чтобы удалить все знаки препинания. Я имею в виду, что ваше сравнение несправедливо
@Onyambu, я удалил «headstart», чтобы рассчитать, сколько потребуется конкретному методу для замены. Во всяком случае, я добавил tt = {ord(s): None for s in symbols}
, и это все еще быстрее, чем re ~ 50%.
Хотя имейте в виду, что ваше время сильно отличается от времени @vurmux выше (если я что-то не упустил). (Пожалуйста, не поймите меня неправильно; это выглядит хорошо, но я просто хочу быть уверенным в этом)
@PoeteMaudit, я проверял на Mac mini, у него, наверное, комп помощнее :D
@PoeteMaudit, я добавил код для теста, вы можете проверить все сами;)
@PoeteMaudit, я добавил тесты для строк разной длины, можете проверить.
Кажется, это действительно зависит от таблицы ввода и перевода. Я только что попробовал для нас (входные данные - это несколько тестовых журналов размером до ~ 60 МБ, а перевод избегает символов HTML + удалить ~ 4 «плохих» символа, поэтому всего ~ 10 замен), и translate
на самом деле был в ~ 2 раза медленнее чем replace
.
@Крестный отец, действительно. Ожидаемое применение .translate()
сильно отличается от замены «плохих» символов. Его можно использовать для замены, но, поскольку он требует перебора таблицы перевода, в некоторых случаях он может быть медленнее, чем .replace()
.
@OlvinRoght Я добавил свой ответ с моими выводами. Это было нелогично, потому что я ожидаю, что replace
будет много раз перебирать всю строку, а translate
будет перебирать строку только один раз, вместо этого выполняя итерацию по таблице переводов... в то время как replace
- это O(K*N)
(где K
- длина таблицы перевода, а N
- длина строки), а translate
— это O(N)
(учитывая, что поиск в таблице перевода — это O(1)
, я бы действительно ожидал, что перевод будет быстрее.
Мой код заменяет символы пробелами и НЕ удаляет эти пробелы.
Для коротких строк .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)
).
С другой стороны, .replace()
работает быстро, если замен мало, и медленно, если нужно сделать много замен.
Выглядит интересно и исчерпывающе, спасибо (upvote). Итак, не могли бы вы сказать, какова вычислительная сложность этих методов?
.translate()
является линейным (но с очень маленьким коэффициентом, меньше 1) и зависит от длины строки и (в меньшей степени) от размера таблицы перевода.
.join()
и replace()
определить сложнее, но, вероятно, они тоже линейны (с коэффициентом 1). И на них также сильно влияет то, сколько символов нужно заменить, поэтому это очень изменчиво, а НЕ просто линейная сложность.
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
? Также вы заменяете эти символы пробелами или ничего туда не добавляете?
@Poete Maudit, на самом деле это было сложнее, чем нужно. Мне не нужно было вводить строку символов 3 раза. Достаточно было использовать его только для третьего параметра maketrans(). Я поправил пример.
Да, на самом деле я думаю, что в этом отношении ответ @yatu выше является самым кратким.
Хотя ответ Рохта - лучший ИМО и показывает объективный подход, я хотел бы отметить, что translate
не всегда лучший! Вам действительно нужно проверить это самостоятельно, результат будет зависеть от ваших входных данных.
(отказ от ответственности: я не просматривал исходный код Python, поэтому ниже приведено то, что я ожидал), учитывая, что у нас есть K
символы для замены и N
символы в исходной строке:
str.replace
должен в основном перебирать всю строку, проверяя каждый символ и заменяя его, если он соответствует параметру. Выглядит как чистый O(N)
, поэтому для K
замен будет O(K*N)
.
С другой стороны, translate
должен перебирать всю строку только один раз, проверяя каждый символ на соответствие в таблице перевода. Так как таблица переводов представляет собой хэш-карту, поиск там происходит O(1)
, поэтому весь перевод вообще не зависит от K
, должен быть O(N)
Вопрос - почему тогда replace
быстрее в моем случае??? Я не знаю :(
Я столкнулся с этим, когда рефакторил наш скрипт, анализируя тестовые журналы (довольно большие файлы, думаю, 60 МБ+), и он очищал его от некоторых случайных символов, а также выполнял некоторую HTML-санитизацию, вот словарь замены:
replace_dict = {
"&": "&",
"\"": """,
"<": "<",
">": ">",
"\u0000": "",
"\u0007": "",
"\u0008": "",
"\u001a": "",
"\u001b": "",
}
Когда я увидел в исходном коде всего 9 вызовов str.replace
в строке, это была моя первая мысль — «Что за хрень, а давайте вместо этого будем использовать translate
», это должно быть намного быстрее. Однако в моем случае я обнаружил, что replace
на самом деле самый быстрый метод.
Тестовый сценарий:
replace_dict = {
"&": "&",
"\"": """,
"<": "<",
">": ">",
"\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)
Вы также можете попробовать прочитать файл как двоичный ("rb"
) и использовать bytes.translate()
, что может быть значительно быстрее.
@OlvinRoght Я не уверен, что понимаю, как использовать bytes.translate
в этом случае, поскольку кажется, что он работает только для замены одного символа, а не для замены строки (и, например, "
- это строка из 6 символов). А насчет смены символов \u..
- это вообще имеет какое-то значение? Я чувствую, что \u
-формат выглядит более последовательным и читабельным...
Я нашел вариант с \xFF
более читаемым, хорошо. Но рекомендация из первого комментария по-прежнему важна. Кроме того, измените "".join(...)
на "".join([...])
, это также немного улучшит последний метод.
@OlvinRoght, но это совсем не имеет значения, не так ли? Мы оптимизируем не саму тестовую программу, а фактические методы замены. Это общий код, выполняемый для всех тестовых методов в setup
, поэтому даже если я добавлю sleep(1)
вверху файла, это ничего не изменит.
func_list_comp
использует symbols
, и он продемонстрирует немного лучшую производительность, если symbols
будет инициализирован как set
.
Хорошо, я нашел, что происходит. Вы также узнаете, будете ли вы использовать следующую таблицу перевода {ord("&"): "a", ord('"'): "q", ord("<"): "l", ord(">"): "g", 0: None, 7: None, 8: None, 0x1a: None, 0x1b: None}
. Есть две функции перевода: unicode_fast_translate()
и _PyUnicode_TranslateCharmap()
, которые вызываются первыми при выполнении определенных условий.
По сути, если ваша строка замены не имеет длины 1 символ, str.translate
будет использовать сложный метод, который демонстрирует значительно более низкую производительность. Вы можете покопаться в источниках и сравнить с функции замены строк, чтобы включить некоторые пояснения в свой ответ.
Просьба уточнить. Вы хотите заменить каждый символ пробелом? Или вы хотите убрать каждый символ целиком, ничем его не заменив? Потому что
(Hello World)] *!
не станетHello World
, если вы замените все его специальные символы пробелами. Становится[one space]Hello World[five spaces]
.