Python re.sub () не заменяет каждое совпадение - PullRequest
0 голосов
/ 15 декабря 2018

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

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

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

(.)(.*?)\1

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

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

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

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

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

Ответы [ 5 ]

0 голосов
/ 15 декабря 2018

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

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

Мне нравится этот ответ , но с использованием 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, в любом случае)

0 голосов
/ 15 декабря 2018

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

>>>''.join([i for i in s1 if s1.count(i) == 1])
'bc'
>>>''.join([i for i in s if s.count(i) == 1])
'c'
0 голосов
/ 15 декабря 2018

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

0 голосов
/ 15 декабря 2018

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


Мой лучший выбор относительно того, что вы пытаетесь сопоставитьis: «один или несколько символов - вызовите этот подшаблон 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'

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

0 голосов
/ 15 декабря 2018

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

(.)(.*?)\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)

...