Соответствует тому же числу повторений символа, что и повторы захваченной группы

Я хотел бы очистить некоторые входные данные, которые были зарегистрированы с моей клавиатуры с помощью Python и регулярных выражений. Особенно, когда backspace использовался, чтобы исправить ошибку.

Пример 1:

[in]:  'Helloo<BckSp> world'
[out]: 'Hello world'

Это может быть сделано с

re.sub(r'.<BckSp>', '', 'Helloo<BckSp> world')

Пример 2:
Однако, когда у меня есть несколько возвратов, я не знаю, как удалить точно такое же количество символов:

[in]:  'Helllo<BckSp><BckSp>o world'
[out]: 'Hello world'

(Здесь я хочу удалить "l" и "o" перед двумя пробелами).

Я мог бы просто использовать re.sub(r'[^>]<BckSp>', '', line) несколько раз, пока нет <BckSp> осталось, но я хотел бы найти более элегантное / более быстрое решение.

Кто-нибудь знает как это сделать?

5 ответов

Похоже, что Python не поддерживает рекурсивное регулярное выражение. Если вы можете использовать другой язык, вы можете попробовать это:

.(?R)?<BckSp>

Смотрите: https://regex101.com/r/OirPNn/1

Это не очень эффективно, но вы можете сделать это с помощью модуля re:

(?:[^<](?=[^<]*((?=(\1?))\2<BckSp>)))+\1

демонстрация

Таким образом, вам не нужно считать, шаблон использует только повторение.

(?: 
    [^<] # a character to remove
    (?=  # lookahead to reach the corresponding <BckSp>
        [^<]* # skip characters until the first <BckSp>
        (  # capture group 1: contains the <BckSp>s
            (?=(\1?))\2 # emulate an atomic group in place of \1?+
                        # The idea is to add the <BcKSp>s already matched in the
                        # previous repetitions if any to be sure that the following
                        # <BckSp> isn't already associated with a character
            <BckSp> # corresponding <BckSp>
        )
    )
)+ # each time the group is repeated, the capture group 1 is growing with a new <BckSp>

\1 # matches all the consecutive <BckSp> and ensures that there's no more character
   # between the last character to remove and the first <BckSp>

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

(?:[^<](?=[^<]*(\1?+<BckSp>)))+\1

демонстрация

Но с модулем regex вы также можете использовать рекурсию (как заметил @Fallenhero):

[^<](?R)?<BckSp>

демонстрация

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

s = "Helllo\b\bo world"
res = []

for c in s:
    if c == '\b':
        if res:
            del res[-1]
    else:
        res.append(c)

print(''.join(res)) # Hello world

Если маркер буквально '<BckSp>' или другую строку длиной более 1, которую вы можете использовать replace заменить его '\b' и используйте решение выше. Это работает только если вы знаете, что '\b' не происходит на входе. Если вы не можете назначить замещающий символ, вы можете использовать split и обработать результаты:

s = 'Helllo<BckSp><BckSp>o world'
res = []

for part in s.split('<BckSp>'):
    if res:
        del res[-1]
    res.extend(part)

print(''.join(res)) # Hello world

Немного многословно, но вы можете использовать эту лямбда-функцию для подсчета числа <BckSp> вхождение и использование подпрограмм для получения окончательного результата.

>>> bk = '<BckSp>'

>>> s = 'Helllo<BckSp><BckSp>o world'
>>> print re.sub(r'(.*?)((?:' + bk + ')+)', lambda x: x.group(1)[0:len(x.group(1)) - len(x.group(2))/len(bk)], s)
Hello world

>>> s = 'Helloo<BckSp> world'
>>> print re.sub(r'(.*?)((?:' + bk + ')+)', lambda x: x.group(1)[0:len(x.group(1)) - len(x.group(2))/len(bk)], s)
Hello world

>>> s = 'Helloo<BckSp> worl<BckSp>d'
>>> print re.sub(r'(.*?)((?:' + bk + ')+)', lambda x: x.group(1)[0:len(x.group(1)) - len(x.group(2))/len(bk)], s)
Hello word

>>> s = 'Helllo<BckSp><BckSp>o world<BckSp><BckSp>k'
>>> print re.sub(r'(.*?)((?:' + bk + ')+)', lambda x: x.group(1)[0:len(x.group(1)) - len(x.group(2))/len(bk)], s)
Hello work

Поскольку нет поддержки рекурсивных / подпрограммных вызовов, в Python нет атомарных групп / притяжательных квантификаторов. re, вы можете удалить эти символы с последующими символами возврата в цикле:

import re
s = "Helllo\b\bo world"
r = re.compile("^\b+|[^\b]\b")
while r.search(s): 
    s = r.sub("", s)
print(s)

Посмотреть демо Python

"^\b+|[^\b]\b" шаблон найдет 1+ символов возврата на начало строки (с ^\b+) а также [^\b]\b найдет все неперекрывающиеся вхождения любого символа, кроме backspace с последующим backspace.

Тот же самый подход в случае, когда возврат на одну позицию выражается в виде некоторой единицы / тэга, подобного литералу <BckSp>:

import re
s = "Helllo<BckSp><BckSp>o world"
r = re.compile("^(?:<BckSp>)+|.<BckSp>", flags=re.S)
while r.search(s): 
    s = r.sub("", s)
print(s)

Посмотреть еще одну демонстрацию Python

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