(此问题与音乐无关,但我以音乐为例 用例。)
在音乐中,构造短语的一种常见方法是将音符序列 中间部分重复一次或多次。因此,这句话 由引言,循环部分和结尾组成。这是一个 例如:
[ 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]
我不确定它是否正确,但是它通过了我的简单测试 描述。它的问题在于它是减慢速度的方法。我看了 后缀树,但它们似乎不适合我的用例,因为 我需要的子字符串应该不重叠且相邻。
答案 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文章中对照参考实现检查代码。