Suche nach einem Algorithmus zur Identifizierung zusammenhängender wiederholter Reihen von Zeilen in einer langen Zeichenfolge

7

Ich möchte einen Algorithmus, der wiederholte Teile von Big-Stack-Traces wie folgt identifizieren kann:

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)

Mit ein wenig Inspektion ist klar, dass dieses Segment dreimal wiederholt wird:

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)

Das Endziel ist, dass ich Stapelspuren rekursiver Funktionen auf schönere Weise ausdrucken kann

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)

Ich bin mir nicht sicher, wie machbar dies ist, aber ich würde mich über alle möglichen Lösungen freuen, selbst wenn sie ihre eigenen Einschränkungen und Einschränkungen haben. Vorläufiges Googeln hat mich nicht gefunden, aber wahrscheinlich weiß ich einfach nicht, was ich googeln soll

Li Haoyi
quelle

Antworten:

4

Unter normalen Umständen füllt JVM nur die letzten 1024 Aufrufe in einem Stacktrace, und in Dotty / Scalac haben die meisten Stackoverflows ein sich wiederholendes Fragment mit einer Länge von ≈ 70 oder weniger. Ein Stacktrace T einer StackOverflowException kann in drei Teile S · R ^ N · P zerlegt werden, wobei R der sich wiederholende Teil des Stacktraces ist, S ein Suffix von R ist und P entweder ein Präfix von R oder eine nicht verwandte Folge von Anrufe. Wir sind an einer Lösung interessiert, bei der die Gesamtlänge C = | S · R ^ N | ist des sich wiederholenden Teils und N sind beide maximal und | S | ist minimal.

// 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|)
}

Der gesamte Algorithmus kann so implementiert werden, dass kein Speicher zugewiesen wird (um OOM zu behandeln). Es hat die Komplexität O (F ^ 2 | T |), aber Ausnahmen sind selten genug und dies ist eine kleine Konstante (F << 1024, T = 1024).

Ich habe genau diesen Algorithmus in meiner Bibliothek https://github.com/alexknvl/tracehash ( https://github.com/alexknvl/tracehash/blob/master/src/main/java/tracehash/internal/SOCoverSolver.java) implementiert ) zum gleichen Zweck der Vereinfachung von Scalac / Dotc-Fehlern;)

EDIT: Hier ist eine Implementierung des gleichen Algorithmus in 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: Nach einigen Ideen aus Mukels Antwort gibt es hier eine Funktion https://gist.github.com/alexknvl/b041099eb5347d728e2dacd1e8caed8c , die etwas in der folgenden Richtung löst:

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)

Es ist gierig, daher ist es nicht unbedingt eine optimale Lösung, aber es scheint in einfachen Fällen, z. B. gegeben, ziemlich gut zu funktionieren

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]))

es erzeugt eine Antwort

[([3], 4), ([5, 4], 3), ([5], 1), ([2, 1], 4), ([2, 4, 3], 1)] 
alex.knvl
quelle
"Der gesamte Algorithmus kann so implementiert werden, dass kein Speicher zugewiesen wird." Meinen Sie damit, dass der Algorithmus so implementiert werden kann, dass nur Stapelspeicher verwendet wird? Warum ist es sinnvoll, nur Stapelspeicher zu verwenden?
John L.
1
Möglicherweise möchten Sie vermeiden, dem Heap etwas zuzuweisen, falls Sie einen OutOfMemoryError abfangen .
alex.knvl
Aha. Nun, es kommt vor, dass der Fehler in der Frage StackOverflowError ist .
John L.
5

Erstellen Sie einen Suffixbaum mit dem Ukkonen-Algorithmus . Auf diese Weise finden Sie in alle Teilzeichenfolgen im bereitgestellten Text mit Indizes.Ö(n)

Im Falle einer ungefähren Übereinstimmung gibt es auch eine erweiterte Version des Ukkonen-Algorithmus .

Böse
quelle
4

Wenn Sie der Meinung sind, dass "eine Zeile der Stapelverfolgung" = "ein Zeichen" ist, können Sie Folgendes verwenden:

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

Eine Möglichkeit, dieses Problem effizient zu lösen, besteht darin, einen Suffix Trie zu erstellen: http://en.wikipedia.org/wiki/Suffix_tree

Jedes Mal, wenn Sie einen bereits festgelegten Pfad durchlaufen, entdecken Sie tatsächlich eine Wiederholung in Ihrer Zeichenfolge.

Listen Sie nun einfach alle Wiederholungen in einer separaten Datenstruktur auf und extrahieren Sie die längste ... oder alle, wenn Sie möchten.

n1r3
quelle
1
Das am längsten wiederholte Teilzeichenfolgenproblem scheint auch Teilzeichenfolgen zu finden, die sich überlappen oder mit Lücken zwischen ihnen wiederholt werden. Gibt es eine Möglichkeit, nur Teilzeichenfolgen zu finden, die nacheinander ohne Lücken oder Überlappungen wiederholt werden?
Li Haoyi
Über den Suffixbaum können Sie einfach alle wiederholten Teilzeichenfolgen auflisten. Ich habe das folgende Gist erstellt, um meine Idee zu zeigen: gist.github.com/nremond/fb63a27b6291053ca4dd1de81135de84 Ich schäme mich ein bisschen für den schnellen und schmutzigen Code, aber wenn Ihnen das Algo gefällt, können wir daran arbeiten.
n1r3
4

Ein effizienter String-Faktorisierungsalgorithmus kann helfen. Gegeben eine ZeichenfolgeS., n=|S.| Maximum finden p so dass S.=T.p z.B T. verkettet p mal rufen wir an T.der Samen und p die Periode . Dies kann in erfolgenÖ(n)Verwenden Sie die Präfixfunktion des (Knuth-) Morris-Pratt-Algorithmus, aber keine Angst vor dem Namen. Es ist ein sehr einfacher und schöner Algorithmus. Hier ist meine Lösung für das entsprechende Problem SPOJ PERIOD .

Mit einer schnellen String-Faktorisierung können wir dann fortfahren, einen String als Verkettung von faktorisierten Chunks (dynamische Programmierung) unter Verwendung einer benutzerdefinierten Kostenfunktion zu zerlegen, z. B. die Gesamtlänge der Samen minimieren, die Summe der Quadrate der Samenlängen minimieren. .

S.=T.1p1T.2p2T.kpk

Gesamtkomplexität ist Ö(n2).

Wenn Ö(n2) ist viel zu kostspielig. Sie können zu einer gierigen Strategie übergehen, z. B. sobald Sie einen faktorisierbaren Teil finden, ihn zusammendrücken und von diesem Punkt aus fortfahren und auch die maximale Samengröße begrenzen (z |T.|200) und beschneiden Sie die Suche, wenn Sie keinen Punkt finden 2 in den ersten zB 400 Zeichen.

Mukel
quelle
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;
  }

Rufen contiguous_repeated_string("abcdefgabcdabcdabcd")Sie an, Sie werden bekommen abcdabcdabcd.

Rufen contiguous_repeated_string("ATCGATCGA")Sie an, Sie werden bekommen ATCGATCG.

Und dann können Sie KMPs verwenden Longest Proper Prefix Postfix array, um die resultierende Zeichenfolge mit zu falten O(N).

Die Gesamtzeitkomplexität beträgt O(N^2).

Wu Fuheng
quelle
"Gesamtzeitkomplexität ist Ö(N.2)". Was ist mit der schlimmsten Zeitkomplexität?
John L.