Соответствует тому же числу повторений символа, что и повторы захваченной группы
Я хотел бы очистить некоторые входные данные, которые были зарегистрированы с моей клавиатуры с помощью 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