Захватите целое число в строке и используйте его как часть регулярного выражения

У меня есть строка:

s = ".,-2gg,,,-2gg,-2gg,,,-2gg,,,,,,,,t,-2gg,,,,,,-2gg,t,,-1gtt,,,,,,,,,-1gt,-3ggg"

и регулярное выражение, которое я использую

import re
delre = re.compile('-[0-9]+[ACGTNacgtn]+') #this is almost correct
print (delre.findall(s))

Это возвращает:

['-2gg', '-2gg', '-2gg', '-2gg', '-2gg', '-2gg', '-1gtt', '-1gt', '-3ggg']

Но -1gtt и -1gt не являются желаемыми совпадениями. Целое число в этом случае определяет, сколько последующих символов должно соответствовать, поэтому желаемый результат для этих двух совпадений будет -1g и -1g соответственно.

Есть ли способ получить целое число после тире и динамически определить регулярное выражение так, чтобы оно соответствовало такому количеству и только такому количеству последующих символов?

🤔 А знаете ли вы, что...
Python поддерживает динамическую типизацию, что облегчает разработку.


3
51
2

Ответы:

Решено

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

import re

# surround [0-9]+ and [ACGTNacgtn]+ in parentheses to create two capture groups
delre = re.compile('-([0-9]+)([ACGTNacgtn]+)')  

s = ".,-2gg,,,-2gg,-2gg,,,-2gg,,,,,,,,t,-2gg,,,,,,-2gg,t,,-1gtt,,,,,,,,,-1gt,-3ggg"

# each match should be a tuple of (number, letter(s)), e.g. ('1', 'gtt') or ('2', 'gg')
for number, bases in delre.findall(s):
    # print the number, then use slicing to truncate the string portion
    print(f'-{number}{bases[:int(number)]}')

Это печатает

-2gg
-2gg
-2gg
-2gg
-2gg
-2gg
-1g
-1g
-3ggg

Скорее всего, вы захотите сделать что-то кроме print, но вы можете форматировать совпадающие строки так, как вам нужно!

ПРИМЕЧАНИЕ. Это не работает в тех случаях, когда за целым числом следует меньше совпадающих символов, чем указано, например. -10agcta по-прежнему соответствует, хотя содержит всего 5 символов.


Еще одно альтернативное решение с использованием re.sub, которое делает это без цикла:

import re

# surround [0-9]+ and [ACGTNacgtn]+ in parentheses to create two capture groups
delre = re.compile('[^-]*-([0-9]+)([ACGTNacgtn]+)[^-]*')  

s = ".,-2gg,,,-2gg,-2gg,,,-2gg,,,,,,,,t,-2gg,,,,,,-2gg,t,,-1gtt,,,,,,,,,-1gt,-3ggg"

print (re.sub(delre, lambda m: f"-{m.group(1)}{m.group(2)[:int(m.group(1))]}\n", s))

Выход:

-2gg
-2gg
-2gg
-2gg
-2gg
-2gg
-1g
-1g
-3ggg

или же, если вы хотите вывести результат в массиве, используйте:

arr = re.sub(delre, lambda m: f"-{m.group(1)}{m.group(2)[:int(m.group(1))]} ", s).strip().split()
print (arr)

['-2gg', '-2gg', '-2gg', '-2gg', '-2gg', '-2gg', '-1g', '-1g', '-3ggg']