Python 中的重叠正则表达式替换,但取决于捕获组的值

Overlapping regular expression substitution in Python, but contingent on values of capture groups

我目前正在用 Python 编写一个程序,该程序应该将一种语言中的所有字符从一种正字法音译成另一种正字法。这里手头有两件事,一是已经解决了,二是问题所在。第一步,将源正字法中的字符转换为目标正字法,例如

š -> sh

ł -> lh

m̓ -> m’

l̓ -> l’

(ffr:类似撇号的字符是单引号。)

接近问题:在某些情况下,有一些字素以标准方式书写(m', n', l', y', w') 是根据它们周围的内容而写成不同的。具体来说,如果字素紧跟在元音之后和之前,并且字素之前的元音在更高的 'level' 中,则 ' 字符可能会移动到辅音字符之前等级制度。这是一个解释起来有点复杂的规则,但这里有一些例子,其中我包括音译的第一阶段:

əm̓ə -> um’u -> um’u (no change)
əm̓i -> um’i -> um’i (no change)
im̓ə -> im’u -> i’mu (’ character moves to precede; i > u)
em̓i -> e’mi -> em’i (’ character moves to precede; e > i)

等级是'字符应该向最高等级的元音移动,如下所示: e > i > a > u

这是我拥有的代码,它可以很好地处理第二步。它非常干净并且简洁地解决了问题:

import re

def glottalized_resonant_mover(linestring):
    
    '''
    moves glottal character over according to glottalized resonant 
    hierarchy:

    case description: VR’W for some vowels V, W; some glottalized 
    resonant R’

    hierarchy: e > i > a > u
               3 > 2 > 1 > 0

    if h(V) > h(W), then string is V’RW
    
    '''

    hi_scores = {'e' : 3,
                'i' : 2,
                'a' : 1,
                'u' : 0}

    def hierarchy_sub(matchobj):
        '''moves glottalized resonant if a vowel pulls it one way
        or the other
        '''

        if hi_scores[matchobj.group(1)] > hi_scores[matchobj.group(4)]:

            swap_string = ''.join(
                [
                matchobj.group(1),
                matchobj.group(3),
                matchobj.group(2),
                matchobj.group(4)
                ]
            )
            return swap_string

        else:
            return matchobj.group(0)


    glot_res_re = re.compile('(a|e|i|u)(l|m|n|w|y)(’)(a|e|i|u)')
    swapstring = glot_res_re.sub(hierarchy_sub, linestring)
    
    return swapstring

sample = ['’im’ush', 'ttham’uqwus', 'xwtsekwul’im’us']

for i in sample:
    print(glottalized_resonant_mover(i))

因此,当此代码被赋予音译词 im'ushttham'uqwus xwtsekwul'im'us,它适用于前两个词,但不适用于第三个词。总结清楚:

’im’ush'         -> ’i’mush √
ttham’uqwus      -> ttha’muqwus √
xwtsekwul’im’us  -> xwtsekwul’im’us X should be: xwtsekwul’i’mus

问题是第三个词有两个捕获组:先是ul'i然后是im'u 两者共享 i.

现在,这个程序正在输入文本行,第一阶段的音译发生,然后第二步应该发生。有些文档有几千行那么长,而且有很多这样的文档。还有其他一些我要实现的东西(检查单词列表等)会占用大量计算能力,所以我想在仍然易于理解的情况下尽可能快地完成它。

此外,我确实可以为每个字符编写一个序列,然后再替换一大串字符序列,但这样我就失去了一些可移植性以及以后轻松进行编辑的能力。

那么,如果有一个问题:解决这个问题的最佳方法是什么,它仍然保留了我原始解决方案的方法和一些品质?

我不太清楚你来这里是为了什么。特别是,你的代码显然并不总是做你想做的事(“它对前两个词非常有效,但对第三个词不是”),但你没有要求解决这个问题,也没有给我们足够的信息知道为什么第三个词“是错误的”。

所以我会编造一些东西 ;-) 因为 re.sub() 不知道重叠,所以我会按“优先”顺序匹配多次,只查找 对于形式为

的事物
(e)([lmnwy])(’)([iau])

首先。 3 个相似模式的序列似乎捕获了您给我们的所有规则,并且它们仅在某些东西确实需要交换时才匹配。备注:

  • 您不必“手写”这些内容。下面的代码构造了它们。

  • 不要强调速度。现在还为时过早,“数千行”实际上在现代盒子上处理应该是微不足道的。这比您猜测的要快,因为除非需要进行替换,否则永远不会产生调用替换函数的开销。

编辑:因为替换是无条件的,所以我将其更改为使用固定的替换字符串模板,而不是调用带有匹配对象参数的替换函数。

R = "lmnwy"
V_in_order = "eiau"
pats = []
for i, vowel in enumerate(V_in_order[:-1]):
    pat = f"({vowel})([{R}])(’)([{V_in_order[i+1:]}])"
    print("pattern", repr(pat))
    pats.append(re.compile(pat))

# Not needed!
# def sub(m):
#    return m.group(1) + m.group(3) + m.group(2) + m.group(4)

def glottalized_resonant_mover(s):
    for p in pats:
        s = p.sub(r'', s)
    return s

sample = ['’im’ush', 'ttham’uqwus', 'xwtsekwul’im’us']
for i in sample:
    print(i, "->", glottalized_resonant_mover(i))

输出在所有情况下都符合您的要求:

pattern '(e)([lmnwy])(’)([iau])'
pattern '(i)([lmnwy])(’)([au])'
pattern '(a)([lmnwy])(’)([u])'
’im’ush -> ’i’mush
ttham’uqwus -> ttha’muqwus
xwtsekwul’im’us -> xwtsekwul’i’mus```

是的,只需要很小的改动。

  VR'W -> V'RW

其实只需要操作前3个字符,以'W'为必要条件,所以我们要解决的问题就变成了:

  VR'(W) -> V'R

使用前瞻断言:(? =...) 可以匹配 VR'(W)

上一个:VR'W

(a|e|i|u)(l|m|n|w|y)(')(a|e|i|u)

后面的只匹配三个字母但向前看一个WVR'(W)

(a|e|i|u)(l|m|n|w|y)(')(?=(a|e|i|u))

所以'W'是条件,不在操作范围内,可以重新匹配

import re

def glottalized_resonant_mover(linestring):
    
    '''
    moves glottal character over according to glottalized resonant 
    hierarchy:

    case description: VR’W for some vowels V, W; some glottalized 
    resonant R’

    hierarchy: e > i > a > u
               3 > 2 > 1 > 0

    if h(V) > h(W), then string is V’RW
    
    '''

    hi_scores = {'e' : 3,
                'i' : 2,
                'a' : 1,
                'u' : 0}

    def hierarchy_sub(matchobj):
        '''moves glottalized resonant if a vowel pulls it one way
        or the other
        '''
        if hi_scores[matchobj.group(1)] > hi_scores[matchobj.group(4)]:

            swap_string = ''.join(
                [
                matchobj.group(1),
                matchobj.group(3),
                matchobj.group(2),
                #matchobj.group(4) <- Don't need the last one because 'lookahead'
                ]
            )
            return swap_string

        else:
            return matchobj.group(0)
       
    glot_res_re = re.compile('(a|e|i|u)(l|m|n|w|y)(’)(?=(a|e|i|u))')
    # glot_res_re = re.compile('(a|e|i|u)(l|m|n|w|y)(’)(a|e|i|u)')
    swapstring = glot_res_re.sub( hierarchy_sub, linestring)
    
    return swapstring

sample = ['’im’ush', 'ttham’uqwus', 'xwtsekwul’im’us']
answer =['’i’mush', 'ttha’muqwus', 'xwtsekwul’i’mus']
it1 = iter(sample)
it2 = iter(answer)
for i in sample:
    print(next(it1),'->',glottalized_resonant_mover(i), "==", next(it2))

输出:

’im’ush -> ’i’mush == ’i’mush
ttham’uqwus -> ttha’muqwus == ttha’muqwus
xwtsekwul’im’us -> xwtsekwul’i’mus == xwtsekwul’i’mus