(この質問は音楽に関するものではありませんが、私は音楽を使用例として使用しています。)
音楽では、フレーズを構成する一般的な方法は、中央部分が1回以上繰り返される一連の音符としてです。したがって、このフレーズは、イントロダクション、ループパート、およびエンディングで構成されています。以下はその一例です。
[ 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 ] ]
ここで、最初の項目はイントロであり、2回目の繰り返し部分の繰り返し回数と3回目の部分は終わりです。
このような分割を実行するアルゴリズムが必要です。
ただし、リストを分割する方法が複数ある場合があるという1つの注意点があります。たとえば、上記のリストは次のように分割できます。
[ [ 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]
それが正しいかどうかはわかりませんが、これまでに説明した簡単なテストに合格しています。それの問題は、それが減速する方法であることです。サフィックスツリーを確認しましたが、使用している部分文字列は重複しておらず、隣接している必要があるため、それらは私のユースケースに適合していないようです。
あなたがやろうとしていることは、ほぼ LZ77 圧縮アルゴリズムのようです。リンク先のWikipediaの記事で、参照実装に対してコードをチェックできます。