查找最长的相邻重复非重叠子字符串

时间:2020-05-12 17:58:30

标签: python algorithm language-agnostic substring string-algorithm

(此问题与音乐无关,但我以音乐为例 用例。)

在音乐中,构造短语的一种常见方法是将音符序列 中间部分重复一次或多次。因此,这句话 由引言,循环部分和结尾组成。这是一个 例如:

[ E E E F G A F F G A F F G A F C D ]

我们可以“看到”简介是[E E E]重复部分是[F G A F]和结尾是[C D]。因此,拆分列表的方法将是

[ [ E E E ] 3 [ F G A F ] [ C D ] ]

其中第一项是简介,第二项是 重复部分重复,第三部分重复。

我需要一种算法来执行这种拆分。

但是有一个警告,那就是可能有多种方式 拆分列表。例如,上面的列表可以分为:

[ [ E E E F G A ] 2 [ F F G A ] [ F C D ] ]

但这是更糟糕的划分,因为前奏和尾奏的时间更长。所以 该算法的标准是找到使 环部分的长度,并最大程度地减少了 简介和结尾。这意味着正确的分割

[ A C C C C C C C C C A ]

[ [ A ] 9 [ C ] [ A ] ]

因为前奏和尾奏的总长度为2,而长度 循环部分的值为9。

此外,虽然前奏和后奏可以为空,但只有“ true”重复 允许的。因此,不允许进行以下拆分:

[ [ ] 1 [ E E E F G A F F G A F F G A F C D ] [ ] ]

将其视为找到最佳的“压缩” 顺序。请注意,在某些序列中可能没有任何重复:

[ A B C D ]

对于这些退化的情况,允许任何明智的结果。

这是我对算法的实现:

def find_longest_repeating_non_overlapping_subseq(seq):
    candidates = []
    for i in range(len(seq)):
        candidate_max = len(seq[i + 1:]) // 2
        for j in range(1, candidate_max + 1):
            candidate, remaining = seq[i:i + j], seq[i + j:]
            n_reps = 1
            len_candidate = len(candidate)
            while remaining[:len_candidate] == candidate:
                n_reps += 1
                remaining = remaining[len_candidate:]
            if n_reps > 1:
                candidates.append((seq[:i], n_reps,
                                   candidate, remaining))
    if not candidates:
        return (type(seq)(), 1, seq, type(seq)())

    def score_candidate(candidate):
        intro, reps, loop, outro = candidate
        return reps - len(intro) - len(outro)
    return sorted(candidates, key = score_candidate)[-1]

我不确定它是否正确,但是它通过了我的简单测试 描述。它的问题在于它是减慢速度的方法。我看了 后缀树,但它们似乎不适合我的用例,因为 我需要的子字符串应该不重叠且相邻。

2 个答案:

答案 0 :(得分:1)

这是我在执行的内容。它与您的非常相似,但是会跳过已检查为先前子字符串重复的子字符串。

from collections import namedtuple
SubSequence = namedtuple('SubSequence', ['start', 'length', 'reps'])

def longest_repeating_subseq(original: str):
    winner = SubSequence(start=0, length=0, reps=0)
    checked = set()
    subsequences = (  # Evaluates lazily during iteration
        SubSequence(start=start, length=length, reps=1)
        for start in range(len(original))
        for length in range(1, len(original) - start)
        if (start, length) not in checked)

    for s in subsequences:
        subseq = original[s.start : s.start + s.length]
        for reps, next_start in enumerate(
                range(s.start + s.length, len(original), s.length),
                start=1):
            if subseq != original[next_start : next_start + s.length]:
                break
            else:
                checked.add((next_start, s.length))

        s = s._replace(reps=reps)
        if s.reps > 1 and (
                (s.length * s.reps > winner.length * winner.reps)
                or (  # When total lengths are equal, prefer the shorter substring
                    s.length * s.reps == winner.length * winner.reps
                    and s.reps > winner.reps)):
            winner = s

    # Check for default case with no repetitions
    if winner.reps == 0:
        winner = SubSequence(start=0, length=len(original), reps=1)

    return (
        original[ : winner.start],
        winner.reps,
        original[winner.start : winner.start + winner.length],
        original[winner.start + winner.length * winner.reps : ])

def test(seq, *, expect):
    print(f'Testing longest_repeating_subseq for {seq}')
    result = longest_repeating_subseq(seq)
    print(f'Expected {expect}, got {result}')
    print(f'Test {"passed" if result == expect else "failed"}')
    print()

if __name__ == '__main__':
    test('EEEFGAFFGAFFGAFCD', expect=('EEE', 3, 'FGAF', 'CD'))
    test('ACCCCCCCCCA', expect=('A', 9, 'C', 'A'))
    test('ABCD', expect=('', 1, 'ABCD', ''))

为我传递您的所有三个示例。看起来这种事情可能会有很多奇怪的情况,但是考虑到这是一种优化的蛮力,可能更多的是更新规范,而不是修复代码本身中的错误。

答案 1 :(得分:0)

您似乎想要做的几乎是LZ77压缩算法。您可以在我链接到的Wikipedia文章中对照参考实现检查代码。

相关问题