Более сложная версия "Как узнать, повторяется ли строка в Python?"

Я читал этот пост, и мне интересно, может ли кто-нибудь найти способ уловить повторяющиеся мотивы в более сложную строку.

Например, найдите все повторяющиеся мотивы в

string = 'AAACACGTACGTAATTCCGTGTGTCCCCTATACGTATACGTTT'

Здесь повторяющиеся мотивы: 'AAAC ACGTACGT AATTCC GTGTGT КЦИК TATACGTATACG ТТТ'

Итак, результат должен быть примерно таким:

output = {'ACGT': {'repeat': 2,
                   'region': (5,13)},
          'GT': {'repeat': 3,
                 'region': (19,24)},
          'TATACG': {'repeat': 2,
                     'region': (29,40)}}

Этот пример исходит из типичных биологических явлений, называемых микросателлитом, который присутствует в ДНК.

UPDATE 1: Звездочки были удалены из строковой переменной. Это была ошибка.

ОБНОВЛЕНИЕ 2: Мотив одиночного символа не учитывается. Например: в ACGUG AAA GUC мотив "A" не учитывается.

Ответ 1

вы можете использовать функцию рекурсии следующим образом:

Примечание. Аргумент результата будет рассматриваться как глобальная переменная (поскольку передача изменяемого объекта в функцию влияет на вызывающего)

import re
def finder(st,past_ind=0,result=[]):
   m=re.search(r'(.+)\1+',st)
   if m:
      i,j=m.span()
      sub=st[i:j]
      ind = (sub+sub).find(sub, 1)
      sub=sub[:ind]
      if len(sub)>1:
        result.append([sub,(i+past_ind+1,j+past_ind+1)])
      past_ind+=j
      return finder(st[j:],past_ind)
   else:
      return result



s='AAACACGTACGTAATTCCGTGTGTCCCCTATACGTATACGTTT'
print finder(s)

результат:

[['ACGT', (5, 13)], ['GT', (19, 25)], ['TATACG', (29, 41)]]

ответьте на предыдущий вопрос для следующей строки:

s = 'AAAC**ACGTACGTA**ATTCC**GTGTGT**CCCC**TATACGTATACG**TTT'

Вы можете использовать оба ответа из упомянутого вопроса и некоторые дополнительные рецепты:

Сначала вы можете разделить строку на **, а затем создать новый список, содержащий повторяющиеся строки с r'(.+)\1+' regex:

Таким образом, результат будет:

>>> new=[re.search(r'(.+)\1+',i).group(0) for i in s.split('**')]
>>> new
['AAA', 'ACGTACGT', 'TT', 'GTGTGT', 'CCCC', 'TATACGTATACG', 'TTT']

Примечание о 'ACGTACGT', который пропустил A в конце!

Затем вы можете использовать функцию principal_period для получения повторяющихся подстрок:

def principal_period(s):
    i = (s+s).find(s, 1, -1)
    return None if i == -1 else s[:i]

>>> for i in new:
...    p=principal_period(i)
...    if p is not None and len(p)>1:
...        l.append(p)
...        sub.append(i)
... 

Итак, у вас будут повторяющиеся строки в l и основные строки в sub:

>>> l
['ACGT', 'GT', 'TATACG']
>>> sub
['ACGTACGT', 'GTGTGT', 'TATACGTATACG']

Затем вам понадобится region, который вы можете сделать с помощью метода span:

>>> for t in sub:
...    regons.append(re.search(t,s).span())

>>> regons
[(6, 14), (24, 30), (38, 50)]

И, наконец, вы можете закрепить 3 списка regon, sub, l и использовать понимание dict для создания ожидаемого результата:

>>> z=zip(sub,l,regons)
>>> out={i :{'repeat':i.count(j),'region':reg} for i,j,reg in z}
>>> out
{'TATACGTATACG': {'region': (38, 50), 'repeat': 2}, 'ACGTACGT': {'region': (6, 14), 'repeat': 2}, 'GTGTGT': {'region': (24, 30), 'repeat': 3}}

Основной код:

>>> s = 'AAAC**ACGTACGTA**ATTCC**GTGTGT**CCCC**TATACGTATACG**TTT'
>>> sub=[]
>>> l=[]
>>> regon=[]
>>> new=[re.search(r'(.+)\1+',i).group(0) for i in s.split('**')]
>>> for i in new:
...    p=principal_period(i)
...    if p is not None and len(p)>1:
...        l.append(p)
...        sub.append(i)
... 

>>> for t in sub:
...    regons.append(re.search(t,s).span())
... 
>>> z=zip(sub,l,regons)
>>> out={i :{'repeat':i.count(j),'region':reg} for i,j,reg in z}
>>> out
{'TATACGTATACG': {'region': (38, 50), 'repeat': 2}, 'ACGTACGT': {'region': (6, 14), 'repeat': 2}, 'GTGTGT': {'region': (24, 30), 'repeat': 3}}

Ответ 2

Если вы можете связать свой запрос, вы можете использовать один проход строки. Число сравнений будет length of string * (max_length - min_length), поэтому масштабируется линейно.

s = 'AAACACGTACGTAATTCCGTGTGTCCCCTATACGTATACGTTT'

def find_repeats(s, max_length, min_length=2):

    for i in xrange(len(s)):
        for j in xrange(min_length, max_length+1):
            count = 1
            while s[i:i+j] == s[i+j*count:i+j*count+j]: count += 1
            if count > 1:
                yield s[i:i+j], i, count

for pattern, position, count in find_repeats(s, 6, 2):
    print "%6s at region (%d, %d), %d repeats" % (pattern, position, position + count*len(pattern), count)

Вывод:

    AC at region (2, 6), 2 repeats
  ACGT at region (4, 12), 2 repeats
  CGTA at region (5, 13), 2 repeats
    GT at region (18, 24), 3 repeats
    TG at region (19, 23), 2 repeats
    GT at region (20, 24), 2 repeats
    CC at region (24, 28), 2 repeats
    TA at region (28, 32), 2 repeats
TATACG at region (28, 40), 2 repeats
ATACGT at region (29, 41), 2 repeats
    TA at region (34, 38), 2 repeats

Обратите внимание, что это наследует несколько более наложенных шаблонов, чем ответы регулярного выражения, но, не зная больше о том, что вы считаете совпадением хорошего, его трудно уменьшить, например, почему это значение TATACG лучше, чем ATACGT?

Дополнительно: использование диктата для возврата матчей - плохая идея, поскольку шаблоны не будут уникальными.

Ответ 3

Этот простой цикл while обнаруживает все повторяющиеся шаблоны:

def expand():
    global hi
    hi += 1

def shrink():
    global lo
    lo += 1

s = 'AAACACGTACGTAATTCCGTGTGTCCCCTATACGTATACGTTT'

motifs = set()

lo = 0
hi = 0

f = expand

while hi <= len(s):
    sub = s[lo : hi+1]
    if s.count(sub) > 1:
        motifs.add(sub)
        if lo==hi: f = expand
        f()
    else:
        f = shrink if lo<=hi else expand
        f()

В этот момент motifs содержит все повторяющиеся шаблоны... Пусть фильтрует их с некоторыми критериями:

minlen = 3
for m in filter(lambda m: len(m)>=minlen and s.count(2*m)>=1, motifs):
    print(m)

'''
ATACGT
ACGT
TATACG
CGTA
'''

Ответ 4

Вы можете использовать тот факт, что в regex, lookaheads не продвигают первичный итератор. Таким образом, вы можете вложить группу захвата в lookahead, чтобы найти (потенциально перекрывающиеся) шаблоны, которые повторяются и имеют заданную минимальную длину:

>>> import re
>>> s = 'AAACACGTACGTAATTCCGTGTGTCCCCTATACGTATACGTTT'
>>> re.findall(r'(?=(.{2,})\1+)', s)
['AC', 'ACGT', 'CGTA', 'GT', 'TG', 'GT', 'CC', 'TATACG', 'ATACGT', 'TA']
>>> re.findall(r'(?=(.{2,}?)\1+)', s)
['AC', 'ACGT', 'CGTA', 'GT', 'TG', 'GT', 'CC', 'TA', 'ATACGT', 'TA']

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

Теперь, если мы возьмем регулярное выражение (?=(.{2,})\1+) и изменим его, мы также можем захватить всю подстроку, содержащую повторяющиеся мотивы. Посредством этого мы можем использовать span совпадений для вычисления числа повторений:

(?=((.{2,})\2+))

В приведенном выше регулярном выражении у нас есть группа захвата внутри группы захвата внутри lookahead. Теперь у нас есть все необходимое для решения проблемы:

def repeated_motifs(s):
    import re
    from collections import defaultdict
    rmdict = defaultdict(list)
    for match in re.finditer(r'(?=((.{2,})\2+))', s):
        motif = match.group(2)
        span1, span2 = match.span(1), match.span(2)
        startindex = span1[0]
        repetitions = (span1[1] - startindex) // (span2[1] - startindex)
        others = rmdict[motif]
        if not others or startindex > others[-1]['region'][1]:
            others.append({'repeat': repetitions, 'region': span1})
    return rmdict


s = 'AAACACGTACGTAATTCCGTGTGTCCCCTATACGTATACGTTT'
d = repeated_motifs(s)
print(d)
# list of the repeating motifs we have found sorted by first region
print(sorted(list(d.keys()), key=lambda k: d[k][0]['region']))

Поскольку желаемое поведение в ситуации, когда мотив повторяется в нескольких "областях" строки, не указывается, я сделал предположение, что OP хотел бы использовать словарь string->list, где каждый список содержит свой собственный набор словарей.