【问题标题】:Algorithm to find the most repetitive (not the most common) sequence in a string (aka tandem repeats)查找字符串中最重复(不是最常见)的序列(又名串联重复)的算法
【发布时间】:2018-07-29 22:18:18
【问题描述】:

我正在寻找一种能够在字符串中找到最重复序列的算法(可能在 Python 中实现)。对于 REPETITIVE,我的意思是任何字符的组合,一遍又一遍地重复而不中断(串联重复)。

我正在寻找的算法“找到最常用的单词”算法相同。实际上,重复块不需要是字符串中最常见的词(子串)。

例如:

s = 'asdfewfUBAUBAUBAUBAUBAasdkBAjnfBAenBAcs'
> f(s)
'UBAUBAUBAUBAUBA' #the "most common word" algo would return 'BA'

不幸的是,我不知道如何解决这个问题。非常欢迎任何帮助。


更新

一个额外的例子来说明我希望返回重复次数最多的序列,无论其基本构建块是什么。

g = 'some noisy spacer'
s = g + 'AB'*5 + g + '_ABCDEF'*2 + g + 'AB'*3
> f(s)
'ABABABABAB' #the one with the most repetitions, not the max len

来自@rici 的示例:

s = 'aaabcabc'
> f(s)
'abcabc'

s = 'ababcababc'
> f(s)
'ababcababc' #'abab' would also be a solution here
             # since it is repeated 2 times in a row as 'ababcababc'.
             # The proper algorithm would return both solutions.

【问题讨论】:

标签: python regex python-3.x string algorithm


【解决方案1】:

结合re.findall()(使用特定的regex模式)和max()函数:

import re

#  extended sample string
s = 'asdfewfUBAUBAUBAUBAUBAasdkjnfencsADADADAD sometext'

def find_longest_rep(s):
    result = max(re.findall(r'((\w+?)\2+)', s), key=lambda t: len(t[0]))
    return result[0]

print(find_longest_rep(s))

输出:

UBAUBAUBAUBAUBA

关键模式:

  • ((\w+?)\2+):
    • (....) - 最外层捕获组,即第一个捕获组
    • (\w+?) - 包含在第二个捕获组中的任何非空白字符序列; +? - 量词,匹配一次到无限次,尽可能少,根据需要扩展
    • \2+ - 匹配第二个捕获组最近匹配的相同文本

【讨论】:

  • 能否分享一下程序的预期时间复杂度?
  • 绝妙的解决方案!谢谢。在接受答案之前,我将通过一些示例对其进行测试。
  • 下面是两个失败的字符串示例:longest_rep('aaabcabc') => 'aaa'(应该是'abcabc'); longest_rep('ababcababc') => 'abab'(应该是'ababcababc'
  • @rici 你是对的,我在我的帖子中添加了一个更有意义的例子。但是,这个解决方案仍然很有帮助re.findall(r'((\w+?)\2+)', s) 找到所有重复的块,找到重复次数更多的块就是小菜一碟。
  • 如果今天没有更好的消息,我觉得这个答案值得被接受,因为它给了我一个非常有用的建议,告诉我如何继续执行我正在寻找的算法。
【解决方案2】:

这是基于 ((\w+?)\2+) 正则表达式但有额外改进的解决方案:

import re
from itertools import chain


def repetitive(sequence, rep_min_len=1):
    """Find the most repetitive sequence in a string.

    :param str sequence: string for search
    :param int rep_min_len: minimal length of repetitive substring
    :return the most repetitive substring or None
    """
    greedy, non_greedy = re.compile(r'((\w+)\2+)'), re.compile(r'((\w+?)\2+)')

    all_rep_seach = lambda regex: \
        (regex.search(sequence[shift:]) for shift in range(len(sequence)))

    searched = list(
        res.groups()
        for res in chain(all_rep_seach(greedy), all_rep_seach(non_greedy))
        if res)

    if not sequence:
        return None

    cmp_key = lambda res: res[0].count(res[1]) if len(res[1]) >= rep_min_len else 0
    return max(searched, key=cmp_key)[0]

你可以这样测试:

def check(seq, expected, rep_min_len=1):
    result = repetitive(seq, rep_min_len)
    print('%s => %s' % (seq, result))
    assert result == expected, expected


check('asdfewfUBAUBAUBAUBAUBAasdkBAjnfBAenBAcs', 'UBAUBAUBAUBAUBA')
check('some noisy spacerABABABABABsome noisy spacer_ABCDEF_ABCDEFsome noisy spacerABABAB', 'ABABABABAB')
check('aaabcabc', 'aaa')
check('aaabcabc', 'abcabc', rep_min_len=2)
check('ababcababc', 'ababcababc')
check('ababcababcababc', 'ababcababcababc')

主要特点:

  1. 使用贪婪的((\w+)\2+) 和非贪婪的((\w+)\2+?) 正则表达式;
  2. 在所有子串中搜索重复的子串,并从头开始移位(例如'string' => ['string', 'tring', 'ring', 'ing', 'ng', 'g']);
  3. 选择基于重复次数而不是子序列的长度(例如,对于“ABABABAB_ABCDEF_ABCDEF”,结果将是“ABABABAB”,而不是“_ABCDEF_ABCDEF”);
  4. 重复序列的最小长度很重要(请参阅“aaabcabc”检查)。

【讨论】:

  • 嗨,我正在尝试您的解决方案,但是,对于任何输入,result 始终为无。
【解决方案3】:

您正在搜索的是一种算法,可以在字符串中找到“最大”的原始串联重复。这是一篇论文,描述了一种线性时间算法,用于查找字符串中的所有串联重复,并扩展所有原始串联重复。 Gusfield. Linear Time Algorithms for Finding and Representing all Tandem Repeats in a String

【讨论】:

  • 你知道我在哪里可以找到它的实现吗?
【解决方案4】:

这是我写的蛮力算法。也许会有用:

def find_most_repetitive_substring(string):
max_counter = 1
position, substring_length, times = 0, 0, 0
for i in range(len(string)):
    for j in range(len(string) - i):
        counter = 1
        if j == 0:
            continue
        while True:
            if string[i + counter * j: i + (counter + 1) * j] != string[i: i + j] or i + (counter + 1) * j > len(string):
                if counter > max_counter:
                    max_counter = counter
                    position, substring_length, times = i, j, counter
                break
            else:
                counter += 1
return string[position: position + substring_length * times]

【讨论】:

    猜你喜欢
    • 2012-06-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-29
    相关资源
    最近更新 更多