Арифметические операции в регулярном выражении

Я использую плагин gedit regex (регулярное выражение стиля Python). Я хотел бы сделать некоторую арифметическую операцию для обратной ссылки на группу.

Например:

PART 1 DATA MODELS Chapter  
2 Entity-Relationship Model 27

Я хотел бы изменить его как

PART 1 DATA MODELS Chapter  25
2 Entity-Relationship Model 27

Мое регулярное выражение ^(PART.*)\n(.*\s(\d+))\n, и я хотел бы заменить его чем-то вроде \1 (\3-2)\n\2\n, где \3-2 означает обратную ссылку \3 минус 2. Но замена регулярного выражения неверна. Интересно, как это сделать? Спасибо!

Ответ 1

Вы можете перейти к функции re.sub lambda, которая принимает объект re.MatchObject для каждого совпадающего совпадения шаблонов и возвращает строку замены. Например:

import re    
print re.sub("(\d+)\+(\d+)",
             lambda m: str(int(m.group(1))+int(m.group(2))),
             "If 2+2 is 4 then 1+2+3+4 is 10")

печатает

Если 4 равно 4, то 3 + 7 составляет 10

Вы можете легко применить его к своей проблеме.

Ответ 2

Следующий код делает то, что вы хотите от строки, которую вы дали в качестве примера. Один момент заключается в том, что он очень специфичен для формата этой строки. Он не может управлять любой изменчивостью в строке. Это действительно ограничено только этим типом строкового формата.

import re

ss = '''PART 1 DATA MODELS Chapter
2 Entity-Relationship Model 27

The sun is shining

PART 1 DATA MODELS Chapter
13 Entity-Relationship Model 45
'''

regx = re.compile('^(PART.*)(\n(\d*).*\s(\d+)\n)',re.MULTILINE)

def repl(mat):
    return ''.join((mat.group(1),' ',
                    str(int(mat.group(4))-int(mat.group(3))),
                    mat.group(2)))

for mat in regx.finditer(ss):
    print mat.groups()

print

print regx.sub(repl,ss)

результат

('PART 1 DATA MODELS Chapter', '\n2 Entity-Relationship Model 27\n', '2', '27')
('PART 1 DATA MODELS Chapter', '\n13 Entity-Relationship Model 45\n', '13', '45')

PART 1 DATA MODELS Chapter 25
2 Entity-Relationship Model 27

The sun is shining

PART 1 DATA MODELS Chapter 32
13 Entity-Relationship Model 45

Отредактировано: я забыл флаг re.MULTILINE

Ответ 3

Я не знаю, что вы можете делать арифметические или другие вычисления в регулярных выражениях. Если там есть механизм регулярных выражений, который поддерживает это, он будет действительно изящным! Но я понимаю, что это было бы непрактично, без чрезмерного замедления двигателя регулярных выражений.

Думаю, лучше всего будет использовать функцию/метод регулярного выражения sub:

re.sub(pattern, repl, string[, count, flags])

Вернуть строку полученных путем замены самых левых неперекрывающихся вхождений шаблон в строке с заменой repl. Если шаблон не найден, строка возвращается без изменений. repl может быть строкой или функцией; если это строка, любые обратные слэши в ней обрабатываются. То есть, \n преобразуется в один символ новой строки, \r преобразуется в linefeed и т.д. Неизвестные escape-последовательности, такие как \j, остаются в силе. Backreferences, такие как \6, заменяются подстрокой, соответствующей группа 6 в шаблоне. Например:

>>> re.sub(r'def\s+([a-zA-Z_][a-zA-Z_0-9]*)\s*\(\s*\):',
...        r'static PyObject*\npy_\1(void)\n{',
...        'def myfunc():')
'static PyObject*\npy_myfunc(void)\n{'

Если repl - это функция, это для каждого неперекрывающегося появления шаблона. Функция принимает один аргумент объекта сопоставления и возвращает замену строка. Например:

>>> def dashrepl(matchobj):
...     if matchobj.group(0) == '-': return ' '
...     else: return '-'
>>> re.sub('-{1,2}', dashrepl, 'pro----gram-files')
'pro--gram files'
>>> re.sub(r'\sAND\s', ' & ', 'Baked Beans And Spam', flags=re.IGNORECASE)
'Baked Beans & Spam'

Шаблон может быть строкой или объектом RE.

Необязательный аргумент count - максимальное количество шаблонов замещения, подлежащие замене; count должно быть неотрицательным целым числом. Если опущено или равно нулю, все вхождения будут заменены. Пустые совпадения для шаблон заменяется только тогда, когда он не смежен с предыдущим совпадением, поэтому sub ('x *', '-', 'abc') возвращает '-a-b-c -'.

В дополнение к символьным экранам и обратным ссылкам, как описано выше, \g будет использовать подстроку, соответствующую группе с именем name, как определено синтаксисом (? P...).\g использует номер соответствующей группы; \g < 2 > , следовательно, эквивалентно \2, но не является неоднозначным в замене, например \g < 2 > 0.\20 будет интерпретируется как ссылка на группу 20, а не на ссылку на группу 2 за которым следует буквальный символ "0". Обратная ссылка \g < 0 > заменяет всю подстроку, соответствующую RE.

Вы можете передать repl как функцию, которая вычисляет значения для замены обратно в исходную строку.

Ответ 4

Если gedit не является надмножеством Python, он не позволит выполнять операции внутри замены-regex, как вы пытаетесь сделать с (\3-2). В любом случае, \3 является строкой, и вам нужно сначала преобразовать с помощью int(). Поэтому вам придется разбить его на отдельный re.search(...), вычислить вставленное pageno, а затем вставить.

Вторая проблема заключается в том, что вы не соответствовали длине страницы '2', вы жестко закодировали ее в = - вы хотите, чтобы ваше регулярное выражение соответствовало ей с начала второй строки?

(Также в любом случае ваше многострочное совпадение будет соответствовать только одной строке после PART, если это то, что вы намеревались.)

Здесь он реализован в обычном регулярном выражении Python:

for (chap,sect,page) in re.finditer(r'^(PART.*)\n(.*\s+(\d+))\n', input, re.M):
    print chap, int(page)-2
    print sect

(я попытался обернуть это как repl fn paginate_chapter(matchobj), не может получить re.sub, чтобы называть это надежно еще...)