Python re.sub() не заменяет каждое совпадение

Я использую Python 3, и у меня есть две строки: abbcabb а также abca, Я хочу удалить каждое двойное вхождение одного символа. Например:

abbcabb должен дать c а также abca должен дать bc,

Я попробовал следующее регулярное выражение ( здесь):

(.)(.*?)\1

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

(.)(.*?)*?\1

Но этот снова дает неправильный вывод. Что здесь не так?


Код Python является оператором print:

print(re.sub(r'(.)(.*?)\1', '\g<2>', s)) # s is the string

5 ответов

Решение

Сайт объясняет это хорошо, наводите и используйте раздел объяснений.

(.)(.*?)\1 Не удаляет и не соответствует каждому двойному вхождению. Он соответствует 1 символу, за которым следует что-либо посередине, пока этот же символ не встретится снова.

Таким образом, для abbcabb "зажатая" часть должна быть bbc между двумя a

РЕДАКТИРОВАТЬ: Вы можете попробовать что-то вроде этого без регулярных выражений:

string = "abbcabb"
result = []
for i in string:
    if i not in result:
        result.append(i)
    else:
        result.remove(i)
print(''.join(result))

Обратите внимание, что это создает "последнее" нечетное вхождение строки, а не первое.

Для "первого" известного случая вы должны использовать счетчик, как предложено в этом ответе. Просто измените условие, чтобы проверить наличие нечетных чисел. pseudo code(count[letter] %2 == 1)

Это может быть решено без регулярного выражения, как показано ниже

>>>''.join([i for i in s1 if s1.count(i) == 1])
'bc'
>>>''.join([i for i in s if s.count(i) == 1])
'c'

Регулярные выражения не кажутся идеальным решением

  • они не обрабатывают перекрытие, поэтому ему необходим цикл (как в этом ответе), и он создает строки снова и снова (производительность снижается)
  • они здесь излишни, нам просто нужно посчитать персонажей

Мне нравится этот ответ, но с помощью count неоднократно в понимании списка каждый раз зацикливается на всех элементах.

Это можно решить без регулярного выражения и без O(n**2) только сложность O(n) с помощью collections.Counter

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

как это:

import collections

s = "abbcabb"

cnt = collections.Counter(s)

s = "".join([c for c in s if cnt[c]==1])

(в качестве бонуса вы можете изменить счетчик, чтобы сохранить персонажей, которые имеют 2, 3, в любом случае)

re.sub() не выполняет перекрывающихся замен. После того, как он заменяет первый матч, он начинает присматривать за концом матча. Поэтому, когда вы выполняете замену на

abbcabb

это сначала заменяет abbca с bbc, Тогда это заменяет bb с пустой строкой. Он не возвращается и не ищет другой матч в bbc,

Если вы хотите этого, вам нужно написать свой собственный цикл.

while True:
    newS = re.sub(r'(.)(.*?)\1', r'\g<2>', s)
    if newS == s:
        break
    s = newS
print(newS)

DEMO

РЕДАКТИРОВАТЬ: на основе обмена комментариями - если вы просто заинтересованы в четности подсчета букв, то вам не нужно регулярное выражение, а вместо этого нужен подход, подобный рекомендации @jon. (Если вам не важен порядок, то более производительный подход с очень длинными строками может использовать что-то вроде collections.Counter вместо.)


Мое лучшее предположение относительно того, что вы пытаетесь сопоставить: "один или несколько символов - вызовите этот подшаблон A - за которым следует другой набор из одного или нескольких символов - вызовите этот подшаблон B - с последующим повторением подшаблона A".

Ты можешь использовать + в качестве ярлыка для "одного или нескольких" (вместо того, чтобы указать его один раз, а затем с помощью * для остальных матчей), но в любом случае вам нужно правильно подобрать подшаблоны. Давай попробуем:

>>> import re
>>> pattern = re.compile(r'(.+?)(.+?)\1')
>>> pattern.sub('\g<2>', 'abbcabbabca')
'bbcbaca'

Хм. Это не сработало. Зачем? Поскольку первый шаблон не является жадным, наш "подшаблон A" может просто соответствовать первому a в строке - это появляется позже, в конце концов. Поэтому, если мы используем жадное совпадение, Python будет возвращаться назад до тех пор, пока не найдет такой длинный шаблон для подшаблона A, который все еще допускает появление шаблона ABA:

>>> pattern = re.compile(r'(.+)(.+?)\1')
>>> pattern.sub('\g<2>', 'abbcabbabca')
'cbc'

Выглядит хорошо для меня.

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