代码之家  ›  专栏  ›  技术社区  ›  rv.kvetch

正则表达式替换Python中由下划线分隔的单词

  •  2
  • rv.kvetch  · 技术社区  · 8 年前

    我试图用Python替换特定的单词(用下划线的特定单词边界分隔)。我知道我可以做一个字符串拆分并循环遍历列表项来替换关键短语,然后重新将其连接到字符串中。。但这并不是最优雅或最好的方式去做。

    def replace_words(string, rep_dict, separator):
        regex = r'({0}|\b)({1})({2}|\b)'.format(re.escape(separator), '|'.join(rep_dict.keys()), re.escape(separator))
        return re.sub(regex, lambda x: '{0}{1}{2}'.format(x.group(1), rep_dict[x.group(2)], x.group(3)), string)
    

    假设我使用任何其他分隔符,例如星号(*),则它按预期工作:

     rep_odds = {'first': '1st', 'third': '3rd', 'fifth': '5th'}
     rep_evens = {'second': '2nd', 'fourth': '4th'}
     orders = ['first', 'second', 'third', 'fourth', 'fifth']
     before = '*'.join(orders)
     after = replace_words(before, rep_odds, separator='*')
     # returns: 1st*second*3rd*fourth*5th
     after = replace_words(before, {**rep_odds, **rep_evens}, separator='*')
     # returns: 1st*2nd*3rd*4th*5th
    

    但是,如果我将其更改为使用分隔符作为下划线(u),则会出现这种意外的(错误的)行为:

    before = '_'.join(orders)
    after = replace_words(before, rep_odds, separator='_')
    # returns: 1st_second_3rd_fourth_5th <-- Good
    after = replace_words(before, {**rep_odds, **rep_evens}, separator='_')
    # returns: 1st_second_3rd_fourth_5th <-- What went wrong ?!
    

    如果有人能帮助我理解这种行为,我对学习regex以及它在Python中的工作方式还是有点陌生。。。谢谢。

    4 回复  |  直到 8 年前
        1
  •  2
  •   BallpointBen    8 年前

    很好的问题。问题是:

    re.sub 不允许一个字符位于多个匹配组中;一旦某个字符属于某个匹配项,则会使用该字符,除非指定该匹配项为非匹配项。当使用星号进行匹配时,一个关键事实是单词边界位于星号和单词字符之间。下面是使用星号(the {0} {1} ,和 {2} lambda

    ('', 'first', '*')
    ('', 'second', '*')
    ('', 'third', '*')
    ('', 'fourth', '*')
    ('', 'fifth', '')
    

    当regex匹配器到达第一个匹配的末尾时,其光标位于第一个星号和单词之间 second ,这是一个词的边界。因此 second* third* 等等。

    但是,当使用下划线时,以下是相应的匹配项:

    ('', 'first', '_')
    ('_', 'third', '_')
    ('_', 'fifth', '')
    

    当regex匹配器到达第一个匹配的末尾时,其光标位于第一个下划线和单词之间 ,这是 一个词的界限。因为它已经传递了第一个下划线并且不在单词边界处,所以它不能匹配 (_|\b)second 第二 ,并且您可以看到,该匹配包括 third

    简而言之,第一个例子是“幸运”的,因为在传递分隔符字符之后,您将在单词边界中着陆,而第二个例子不是这样。

    要解决这个问题,可以使用前瞻断言,它不会使用匹配的字符。

    def replace_words(string, rep_dict, separator):
        regex = r'({0}|\b)({1})((?={2}|\b).*?)'.format(
            re.escape(separator), '|'.join(rep_dict.keys()), re.escape(separator)
        )
    
        return re.sub(
            regex, lambda x: '{0}{1}{2}'.
            format(x.group(1), rep_dict[x.group(2)], x.group(3)), string
        )
    

    ('', 'first', '')
    ('*', 'second', '')
    ('*', 'third', '')
    ('*', 'fourth', '')
    ('*', 'fifth', '')
    

    忽略下面的横线文本,这些文本会在单词前缀上匹配,例如。 *firstperson* 会变成 *1stperson*

    P、 你最好的选择是分裂和重新加入美国。无论如何,这很可能是re.sub在幕后所做的,因为字符串是不可变的。

    要解决此问题,只能在关键字前面的分隔符上进行匹配 字符串的结尾)。

    def replace_words(string, rep_dict, separator):
        regex = r'(^|{0})({1})'.format(
            re.escape(separator), '|'.join(rep_dict.keys())
        )
    
        return re.sub(
            regex, lambda x: print(x.groups()) or '{0}{1}'.
            format(x.group(1), rep_dict[x.group(2)]), string
        )
    

        2
  •  0
  •   taras Hardik Kamdar    8 年前

    它不会直接回答你的问题,但这不是:

    def replace_words(s, rep_dict, sep):
        return sep.join(rep_dict.get(word, word) for word in s.split(sep))
    

    比regex解决方案更优雅(反正它使用 join )?

        3
  •  0
  •   Austin    8 年前

    regex 如果我能理解你。

    rep_odds = {'first': '1st', 'third': '3rd', 'fifth': '5th'}
    rep_evens = {'second': '2nd', 'fourth': '4th'}
    orders = ['first', 'second', 'third', 'fourth', 'fifth']
    
    print('_'.join([rep_odds.get(x, rep_evens.get(x, 0)) for x in orders]))
    # 1st_2nd_3rd_4th_5th
    

    您可以将其泛化为与任何分隔符或 orders

    def fun(sep, orders):
        print(sep.join([rep_odds.get(x, rep_evens.get(x, 0)) for x in orders]))
    
    fun('*', orders)
    # 1st*2nd*3rd*4th*5th
    
        4
  •  0
  •   1pluszara    8 年前

    这是另一种方式:

    z=dict(rep_odds.items() + rep_evens.items())
    for i,item in enumerate(orders):
        orders[i]=z.get(item)
    

    输出:

    print(orders)
    ['1st', '2nd', '3rd', '4th', '5th']