【问题标题】:Python re.sub() is not replacing every matchPython re.sub() 不会替换每个匹配项
【发布时间】:2019-05-16 08:56:13
【问题描述】:

我使用的是 Python 3,我有两个字符串:abbcabbabca。我想删除单个字符的每一次出现。例如:

abbcabb 应该给cabca 应该给bc

我尝试了以下正则表达式 (here):

(.)(.*?)\1

但是,它为第一个字符串提供了错误的输出。另外,当我尝试另一个时(here):

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

但是,这个又给出了错误的输出。这里出了什么问题?


python代码是打印语句:

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

【问题讨论】:

  • 解释你想要的结果背后的逻辑。你是说如果字符出现偶数次,那么你根本不想要它,如果有一个奇数,你想要在输出中正好一个?您真的关心输出顺序,还是只想知道哪些字符出现奇数次?
  • “每两次出现”是什么意思? “字符串中多次出现的所有字符”? “具有相同值的邻居的所有字符”?
  • @KarlKnechtel 你是对的。如果重复很奇怪,我只想要一个。而且,顺序是可选的。
  • 所以我们很清楚:将两个输入放在一起,abbcabbabca,应该给b(因为两个cs 取消),而不是cbc
  • @KarlKnechtel 是的,你是对的。

标签: python regex string replace capturing-group


【解决方案1】:

网站解释得很好,悬停并使用解释部分。

(.)(.*?)\1 不会删除或匹配每个重复出现的情况。它匹配 1 个字符,然后是中间的任何内容,直到再次遇到相同的字符。

因此,对于abbcabb,“夹层”部分应位于两个a 之间的bbc

编辑: 您可以尝试这样的事情,而不使用正则表达式:

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

请注意,这会产生字符串的“最后一次”奇数出现,而不是第一次出现。

对于“第一次”已知事件,您应该使用answer 中建议的计数器。只需更改条件以检查奇数。 pseudo code(count[letter] %2 == 1)

【讨论】:

  • 谢谢!因此,当正则表达式删除 a's 时,它实际上会跳过再次查看 bbc
  • 宾果游戏。或者更准确地说,re.sub 应该被认为是两个步骤。正则表达式首先匹配整个字符串上的所有内容,这意味着abbcabb,然后才会发生替换步骤。 @rv7
  • 我正在使用正则表达式,因为字符串的长度保证在 50 以下。所以,我认为循环不是处理它们的正确方法。
  • 正则表达式无论如何都必须有效地扫描或循环遍历您的文本。如果您的目标只是删除出现对,则可以在此设置中获得更可靠的输出以避免正则表达式。 @rv7
【解决方案2】:

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

【讨论】:

  • 经过反思,我 认为 OP 希望它如何工作是整个 abbcabb 与正则表达式匹配:一个开头 abb、一个字符和然后是与开头模式匹配的结尾abb
  • @KarlKnechtel 我不同意,OPs 组只有一个字符长。那不能匹配abb
【解决方案3】:

编辑:基于评论交流 - 如果您只关心字母计数的奇偶性,那么您不想要正则表达式,而是想要像@jon 推荐的方法。 (如果您不关心顺序,那么对于非常长的字符串来说,性能更高的方法可能会使用 collections.Counter 之类的东西。)


对于您要匹配的内容,我的最佳猜测是:“一个或多个字符 - 称此子模式 A - 后跟一组不同的一个或多个字符 - 称此子模式 B - 后跟子模式 A” .

您可以使用+ 作为“一个或多个”的快捷方式(而不是指定一次,然后在其余匹配中使用*),但无论哪种方式,您都需要正确设置子模式。让我们试试吧:

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

嗯。那没有用。为什么?因为第一个模式不是贪婪的,我们的“子模式 A”可以匹配字符串中的第一个 a - 毕竟它确实出现在后面。因此,如果我们使用贪婪匹配,Python 将回溯,直到找到与子模式 A 一样长的模式仍然允许 A-B-A 模式出现:

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

我觉得不错。

【讨论】:

    【解决方案4】:

    不用正则表达式也可以解决,如下所示

    >>>''.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 检查的条件(例如s.count(i) % 2 == 1)。
    • 确定@KarlKnechtel,我会尽快修改感谢您的通知:)
    • 有效,但在列表理解中重复使用 count 每次都会遍历所有元素:o(n**2)。给了我回答自己的想法
    【解决方案5】:

    正则表达式似乎不是理想的解决方案

    • 它们不处理重叠,因此它需要一个循环(如在 this answer 中)并且它会一遍又一遍地创建字符串(性能会受到影响)
    • 他们在这里太过分了,我们只需要计算字符数

    我喜欢this answer,但在列表理解中重复使用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 次出现的字符)

    【讨论】:

    • 是的,从长远来看,这种通用方法是最好的 - 因此我的回答中暗示了这一点。 (不?)幸运的是,在现代机器上,这需要相当长的字符串才能变得明显:)
    猜你喜欢
    • 2022-11-29
    • 2019-06-08
    • 2011-02-15
    • 2015-12-18
    • 2012-08-15
    相关资源
    最近更新 更多