Подтвердить что ты не робот

Самый быстрый метод Python для поиска и замены на большой строке

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

findall() чувствует себя более простым и элегантным, но для этого требуется поразительное количество времени.

finditer() прорисовывается через большой файл, но я не уверен, что это правильный способ сделать это.

Вот пример кода. Обратите внимание, что фактический текст, который мне интересен, представляет собой одну строку размером около 10 МБ, и там есть огромная разница в этих двух методах.

import re

def findall_replace(text, reg, rep):
    for match in reg.findall(text):
        output = text.replace(match, rep)
    return output

def finditer_replace(text, reg, rep):
    cursor_pos = 0
    output = ''
    for match in reg.finditer(text):
        output += "".join([text[cursor_pos:match.start(1)], rep])
        cursor_pos = match.end(1)
    output += "".join([text[cursor_pos:]])
    return output

reg = re.compile(r'(dog)')
rep = 'cat'
text = 'dog cat dog cat dog cat'

finditer_replace(text, reg, rep)

findall_replace(text, reg, rep)

UPDATE Добавлен метод re.sub для тестов:

def sub_replace(reg, rep, text):
    output = re.sub(reg, rep, text)
    return output

Результаты

re.sub() - 0: 00: 00.031000
finditer() - 0: 00: 00.109000
findall() - 0: 01:17.260000

4b9b3361

Ответ 1

Стандартный метод - использовать встроенный

re.sub(reg, rep, text)

Кстати, причина разницы в производительности между вашими версиями заключается в том, что каждая замена в вашей первой версии приводит к повторной обработке всей строки. Копии бывают быстрыми, но когда вы копируете 10 МБ на ходу, количество копий будет медленным.

Ответ 2

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

re.sub(pattern, repl, string[, count, flags])

Причина, по которой ваша функция findall_replace() длинна, заключается в том, что при каждом совпадении создается новый строковый объект, как вы увидите, выполнив следующий код:

ch = '''qskfg qmohb561687ipuygvnjoihi2576871987uuiazpoieiohoihnoipoioh
opuihbavarfgvipauhbi277auhpuitchpanbiuhbvtaoi541987ujptoihbepoihvpoezi 
abtvar473727tta aat tvatbvatzeouithvbop772iezubiuvpzhbepuv454524522ueh'''

import re

def findall_replace(text, reg, rep):
    for match in reg.findall(text):
        text = text.replace(match, rep)
        print id(text)
    return text

pat = re.compile('\d+')
rep = 'AAAAAAA'

print id(ch)
print
print findall_replace(ch, pat, rep)

Обратите внимание, что в этом коде я заменил output = text.replace(match, rep) на text = text.replace(match, rep), иначе заменяется только последнее вхождение.

finditer_replace() длинный по той же причине, что и для findall_replace(): повторное создание строкового объекта. Но первый использует итератор re.finditer(), а последний строит объект списка, поэтому он длиннее. Это разница между итератором и нетератором.

Ответ 3

Кстати, ваш код с findall_replace() небезопасен, он может возвращать невосстановленные результаты:

ch = 'sea sun ABC-ABC-DEF bling ranch micABC-DEF fish'

import re

def findall_replace(text, reg, rep):
    for gr in reg.findall(text):
        text = text.replace(gr, rep)
        print 'group==',gr
        print 'text==',text
    return '\nresult is : '+text

pat = re.compile('ABC-DE')
rep = 'DEFINITION'

print 'ch==',ch
print
print findall_replace(ch, pat, rep)

дисплей

ch== sea sun ABC-ABC-DEF bling ranch micABC-DEF fish

group== ABC-DE
text== sea sun ABC-DEFINITIONF bling ranch micDEFINITIONF fish
group== ABC-DE
text== sea sun DEFINITIONFINITIONF bling ranch micDEFINITIONF fish

result is : sea sun DEFINITIONFINITIONF bling ranch micDEFINITIONF fish