長い文字列で連続する繰り返される一連の行を識別するアルゴリズムを探しています


7

次のようなビッグスタックトレースの繰り返し部分を特定できるアルゴリズムが必要です。

java.lang.StackOverflowError
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.$anonfun$typed1$38(Typers.scala:4752)
at typechecker.Typers$Typer.silent(Typers.scala:700)
at typechecker.Typers$Typer.normalTypedApply$1(Typers.scala:4754)
at typechecker.Typers$Typer.typedApply$1(Typers.scala:4801)
at typechecker.Typers$Typer.typedInAnyMode$1(Typers.scala:5586)
at typechecker.Typers$Typer.typed1(Typers.scala:5603)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5723)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5731)
at transform.Erasure$Eraser.adaptMember(Erasure.scala:714)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.$anonfun$typed1$38(Typers.scala:4752)
at typechecker.Typers$Typer.silent(Typers.scala:700)
at typechecker.Typers$Typer.normalTypedApply$1(Typers.scala:4754)
at typechecker.Typers$Typer.typedApply$1(Typers.scala:4801)
at typechecker.Typers$Typer.typedInAnyMode$1(Typers.scala:5586)
at typechecker.Typers$Typer.typed1(Typers.scala:5603)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5723)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5731)
at transform.Erasure$Eraser.adaptMember(Erasure.scala:714)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.$anonfun$typed1$38(Typers.scala:4752)
at typechecker.Typers$Typer.silent(Typers.scala:700)
at typechecker.Typers$Typer.normalTypedApply$1(Typers.scala:4754)
at typechecker.Typers$Typer.typedApply$1(Typers.scala:4801)
at typechecker.Typers$Typer.typedInAnyMode$1(Typers.scala:5586)
at typechecker.Typers$Typer.typed1(Typers.scala:5603)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5723)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5731)
at transform.Erasure$Eraser.adaptMember(Erasure.scala:714)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.$anonfun$typed1$38(Typers.scala:4752)

少し調べれば、このセグメントが3回繰り返されていることは明らかです。

at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.$anonfun$typed1$38(Typers.scala:4752)
at typechecker.Typers$Typer.silent(Typers.scala:700)
at typechecker.Typers$Typer.normalTypedApply$1(Typers.scala:4754)
at typechecker.Typers$Typer.typedApply$1(Typers.scala:4801)
at typechecker.Typers$Typer.typedInAnyMode$1(Typers.scala:5586)
at typechecker.Typers$Typer.typed1(Typers.scala:5603)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5723)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5731)
at transform.Erasure$Eraser.adaptMember(Erasure.scala:714)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)

最終目標は、再帰関数のスタックトレースをより適切に出力できるようにすることです。

java.lang.StackOverflowError
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
------------------------- Repeated 3x -------------------------
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.$anonfun$typed1$38(Typers.scala:4752)
at typechecker.Typers$Typer.silent(Typers.scala:700)
at typechecker.Typers$Typer.normalTypedApply$1(Typers.scala:4754)
at typechecker.Typers$Typer.typedApply$1(Typers.scala:4801)
at typechecker.Typers$Typer.typedInAnyMode$1(Typers.scala:5586)
at typechecker.Typers$Typer.typed1(Typers.scala:5603)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5723)
at typechecker.Typers$Typer.typedQualifier(Typers.scala:5731)
at transform.Erasure$Eraser.adaptMember(Erasure.scala:714)
at transform.Erasure$Eraser.typed1(Erasure.scala:789)
-------------------------------------------------------------
at typechecker.Typers$Typer.runTyper$1(Typers.scala:5640)
at typechecker.Typers$Typer.typedInternal(Typers.scala:5672)
at typechecker.Typers$Typer.body$2(Typers.scala:5613)
at typechecker.Typers$Typer.typed(Typers.scala:5618)
at typechecker.Typers$Typer.$anonfun$typed1$38(Typers.scala:4752)

これがどの程度実現可能かはわかりませんが、独自の制限や制約がある場合でも、考えられるすべての解決策を喜んで提供します。予備のグーグル検索では何も見つかりませんでしたが、Googleで何をするかわからない可能性があります

回答:


4

通常の状況では、JVMはスタックトレースの最後の1024呼び出しのみを満たし、Dotty / Scalacではほとんどのスタックオーバーフローの長さが≈70以下の繰り返しフラグメントがあります。StackOverflowExceptionのスタックトレースTは、3つの部分S・R ^ N・Pに分解できます。ここで、Rはスタックトレースの繰り返し部分、SはRの一部のサフィックス、PはRの一部のプレフィックスまたは無関係のシーケンスのいずれかです。呼び出します。全長C = | S・R ^ N |のような解に興味があります。繰り返し部分とNの両方が最大で、| S | 最小限です。

// Scala Pseudocode (beware of for comprehensions)
//
// Stack is assumed to be in reverse order, 
// most recent stack frame is last.
val stack: Array[StackTraceElement]
val F: Int // Maximum size of R.

val candidates = for {
  // Enumerate all possible suffixes S.
  S <- ∀ prefix of stack
  if |S| < F

  // Remove the suffix from the stack,
  R <- ∀ non-empty prefix of stack.drop(|S|)
  // Find a fragment that ends with S.
  if R.endsWith(S)

  // Find out how many fragments fit into the stack.
  // (how many times we can remove R from the stack)
  N = coverSize(R, stack.drop(|S|))
  if N >= 2 // Or higher.
} yield (S, R, N)

// Best cover has maximum coverage, 
// minimum fragment length, 
// and minimum suffix length.
val bestCandidate = candidates.maxBy { (S, R, N) =>
  val C = |S| + |R| * N
  return (C, -|R|, -|S|)
}

アルゴリズム全体は、メモリを割り当てない方法で実装できます(OOMを処理するため)。複雑さはO(F ^ 2 | T |)ですが、例外はまれであり、これは小さな定数です(F << 1024、T = 1024)。

私は自分のライブラリにこの正確なアルゴリズムを実装しているhttps://github.com/alexknvl/tracehashhttps://github.com/alexknvl/tracehash/blob/master/src/main/java/tracehash/internal/SOCoverSolver.java)scalac / dotcエラーを簡略化する同じ目的で;)

編集:Pythonでの同じアルゴリズムの実装は次のとおりです。

stack = list(reversed([3, 4, 2, 1, 2, 1, 2, 1, 2, 1, 2]))
F = 6

results = []
for slen in range(0, F + 1):
    suffix, stack1 = stack[:slen], stack[slen:]

    for flen in range(1, F + 1):
        fragment = stack1[:flen]

        if fragment[flen - slen:] != suffix:
            continue

        stack2 = stack1[:]
        n = 0
        while stack2[:flen] == fragment:
            stack2 = stack2[flen:]
            n += 1

        if n >= 2: # A heuristic, might want to set it a bit higher.
            results.append((slen, flen, n))

def cost(t):
    s, r, n = t
    c = s + r * n
    return (c, -r, -s)

S, R, N = max(results, key=cost)
print('%s · %s^%d · %s' % (stack[:S], stack[S:S+R], N, stack[S+R*N:]))
# Prints [] · [2, 1]^4 · [2, 4, 3]

EDIT2:ムケルの答えからのアイデアのいくつかに続いて、ここに関数https://gist.github.com/alexknvl/b041099eb5347d728e2dacd1e8caed8cがあります:

stack = a[1]^k[1] · a[2]^k[2] · ...
argmax (sum |a[i]| * k[i] where k[i] >= 2, 
        -sum |a[i]| where k[i] >= 2, 
        -sum |a[i]| where k[i] == 1)

それは貪欲なので、必ずしも最適な解決策ではありませんが、与えられた単純なケースではかなりうまくいくようです

stack = list(reversed([
  3, 4, 2, 
  1, 2, 1, 2, 1, 2, 1, 2, 
  5, 
  4, 5, 4, 5, 4, 5, 
  3, 3, 3, 3]))

答えを出す

[([3], 4), ([5, 4], 3), ([5], 1), ([2, 1], 4), ([2, 4, 3], 1)] 

「アルゴリズム全体は、メモリを割り当てない方法で実装できます。」アルゴリズムはスタックメモリのみを使用するように実装できるということですか?スタックメモリのみを使用すると便利なのはなぜですか。
ジョン・L.

1
OutOfMemoryErrorをキャッチした場合に備えて、ヒープに何も割り当てないようにすることができます。
alex.knvl

そうですか。さて、問題のエラーはStackOverflowErrorです。
ジョンL.


4

「スタックトレースの行」=「文字」と考える場合は、次を使用できます。

http://en.wikipedia.org/wiki/Longest_repeated_substring_problem

この問題を効率的に解決する1つの方法は、サフィックストライを作成することです:http : //en.wikipedia.org/wiki/Suffix_tree

すでに確立されたパスを通過するたびに、実際には文字列の繰り返しを発見しています。

ここで、すべての繰り返しを別のデータ構造にリストし、最も長い繰り返しを抽出します...必要に応じてすべてを繰り返します。


1
最長の繰り返し部分文字列の問題は、重複する部分文字列、またはそれらの間にギャップがある繰り返し部分文字列も見つけるようです。ギャップやオーバーラップなしに連続して繰り返される部分文字列のみを検索する方法はありますか?
Li Haoyi

接尾辞ツリーから、繰り返されるすべての部分文字列を簡単にリストできます。私は私のアイデアを表示するには、以下の要旨を作成:gist.github.com/nremond/fb63a27b6291053ca4dd1de81135de84私は少し速い&汚いコードで恥ずかしいが、アルゴ好きならば、我々はそれに取り組むことができます。
n1r3

4

効率的な文字列因数分解アルゴリズムが役立ちます。文字列を与えるS=|S| 最大を見つける p そのような S=Tp 例えば T 連結 p 回、私たちは電話します Tシードとp 期間。これはO(Knuth-)Morris-Prattアルゴリズムの接頭辞関数を使用しますが、その名前は非常にシンプルで美しいアルゴリズムです。これは、対応する問題SPOJ PERIODに対する私の解決策です。

高速文字列分解を利用して、カスタムコスト関数を使用して、分解されたチャンクの連結(動的プログラミング)として文字列を分解することができます。たとえば、シードの全長を最小化し、シードの長さの2乗の合計を最小化します。 。

S=T1p1T2p2Tkpk

全体の複雑さは O2

もし O2 コストが高すぎるため、たとえば、因数分解可能なチャンクを見つけ、それを絞り込んでそのポイントから続行し、最大シードサイズ(たとえば、 |T|200)、ピリオドが見つからない場合は検索をプルーニングします 2 最初の例では400文字。


0
  string contiguous_repeated_string(string A){
    string r(1,A[0]);
    for(int i=0;i<A.size();i++){
      for(int l=2;l<=A.size();l++){
        string s=A.substr(i,l);
        if ((s+s).substr(1,2*s.size()-1).find(s)!=s.size()-1 and s.size()>r.size()){
          r=s;
        }
      }
    }
    return r;
  }

お電話contiguous_repeated_string("abcdefgabcdabcdabcd")くださいabcdabcdabcd

お電話contiguous_repeated_string("ATCGATCGA")くださいATCGATCG

そして、KMPを使用してLongest Proper Prefix Postfix array、結果の文字列をで折りたたむことができますO(N)

合計時間は複雑ですO(N^2)


「総時間の複雑さは ON2"。最悪の時間の複雑さはどうですか?
ジョンL.
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.