Tìm kiếm một thuật toán để xác định chuỗi các dòng lặp lại liên tục trong một chuỗi dài


7

Tôi muốn một thuật toán có thể xác định các phần lặp lại của dấu vết ngăn xếp lớn như thế này:

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)

Với một chút kiểm tra, rõ ràng phân khúc này đang được lặp lại ba lần:

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)

Mục tiêu cuối cùng là để tôi có thể in ra các dấu vết của các hàm đệ quy theo cách đẹp hơn

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)

Tôi không chắc điều này khả thi đến mức nào, nhưng tôi rất vui vì mọi giải pháp khả thi ngay cả khi chúng có những hạn chế và hạn chế riêng. Google sơ bộ đã không tìm thấy tôi bất cứ điều gì, nhưng rất có thể tôi chỉ không biết google là gì

Câu trả lời:


4

Trong các trường hợp thông thường, JVM chỉ điền vào 1024 cuộc gọi cuối cùng trong một stacktrace và trong Dotty / Scalac, hầu hết các stackoverflows đều có một đoạn lặp lại có độ dài ≈ 70 hoặc ít hơn. Một stacktrace T của StackOverflowException có thể được phân tách thành ba phần S · R ^ N · P, trong đó R là phần lặp lại của stacktrace, S là một hậu tố của R và P là một số tiền tố của R hoặc một chuỗi không liên quan đến các cuộc gọi. Chúng tôi quan tâm đến một giải pháp sao cho tổng chiều dài C = | S · R ^ N | của phần lặp lại và N đều là cực đại và | S | là tối thiểu

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

Toàn bộ thuật toán có thể được thực hiện theo cách không phân bổ bất kỳ bộ nhớ nào (để xử lý OOM). Nó có độ phức tạp O (F ^ 2 | T |), nhưng các trường hợp ngoại lệ đủ hiếm và đây là một hằng số nhỏ (F << 1024, T = 1024).

Tôi đã triển khai thuật toán chính xác này trong thư viện của mình https://github.com/alexknvl/tracehash ( https://github.com/alexknvl/tracehash/blob/master/src/main/java/tracehash/i INTERNal/SOCoverSolver.java ) cho cùng một mục đích đơn giản hóa các lỗi scalac / dotc;)

EDIT: Đây là một triển khai cùng một thuật toán trong 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: Theo một số ý tưởng từ câu trả lời của mukel, đây là một hàm https://gist.github.com/alexknvl/b041099eb5347d728e2dacd1e8caed8c giải quyết một cái gì đó dọc theo dòng:

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)

Đó là tham lam vì vậy nó không nhất thiết là một giải pháp tối ưu, nhưng nó dường như hoạt động khá tốt trong các trường hợp đơn giản, ví dụ như được đưa ra

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

nó tạo ra một câu trả lời

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

"Toàn bộ thuật toán có thể được thực hiện theo cách không phân bổ bất kỳ bộ nhớ nào." Bạn có nghĩa là thuật toán có thể được thực hiện để nó chỉ sử dụng bộ nhớ ngăn xếp? Tại sao chỉ hữu ích khi sử dụng bộ nhớ stack?
John L.

1
Bạn có thể muốn tránh phân bổ bất cứ thứ gì trong heap trong trường hợp bạn bắt được OutOfMemoryError .
alex.knvl

Tôi hiểu rồi. Vâng, nó xảy ra lỗi trong câu hỏi là StackOverflowError .
John L.


4

Nếu bạn cho rằng, một dòng stacktrace, = một nhân vật, bạn có thể sử dụng:

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

Một cách để giải quyết vấn đề này một cách hiệu quả là xây dựng Suffix Trie: http://en.wikipedia.org/wiki/Suffix_tree

Mỗi khi bạn đi qua một con đường đã được định sẵn, bạn thực sự phát hiện ra sự lặp lại trong chuỗi của bạn.

Bây giờ, chỉ cần liệt kê tất cả các lần lặp lại trong một cấu trúc dữ liệu riêng biệt và trích xuất dữ liệu dài nhất ... hoặc tất cả nếu bạn muốn.


1
Vấn đề chuỗi con lặp lại dài nhất dường như cũng tìm thấy các chuỗi con trùng nhau hoặc được lặp lại với các khoảng trống giữa chúng. Có cách nào để làm cho nó chỉ tìm thấy các chuỗi con được lặp đi lặp lại liên tiếp mà không có khoảng trống hoặc chồng chéo?
Li Haoyi

từ cây hậu tố, bạn có thể dễ dàng liệt kê tất cả các chuỗi con lặp đi lặp lại. Tôi đã tạo Gist sau để thể hiện ý tưởng của mình: gist.github.com/nremond/fb63a27b6291053ca4dd1de81135de84 Tôi hơi xấu hổ vì mã nhanh & bẩn, nhưng nếu bạn thích thuật toán này, chúng tôi có thể làm việc với nó.
n1r3

4

Một thuật toán nhân tố chuỗi hiệu quả có thể giúp đỡ. Cho một chuỗiS, n= =|S| tìm tối đa p như vậy mà S= =Tp ví dụ T nối p lần, chúng tôi gọi Tcác hạt giống và p các giai đoạn . Điều này có thể được thực hiện trongÔi(n)sử dụng hàm tiền tố của thuật toán Morris-Pratt (Knuth-), nhưng đừng sợ cái tên đó là thuật toán rất đơn giản và đẹp, đây là giải pháp của tôi cho vấn đề tương ứng SPOJ PERIOD .

Được trang bị một hệ số chuỗi nhanh, sau đó chúng ta có thể tiến hành phân tách một chuỗi dưới dạng nối các khối được bao thanh toán (lập trình động) bằng cách sử dụng hàm chi phí tùy chỉnh, ví dụ như giảm thiểu tổng chiều dài của hạt, giảm thiểu tổng bình phương độ dài của hạt .. .

S= =T1p1T2p2Tkpk

Tổng độ phức tạp là Ôi(n2).

Nếu Ôi(n2) là quá tốn kém, bạn có thể chuyển sang một chiến lược như tham lam, ví dụ như ngay khi bạn tìm thấy một đoạn có thể hiểu được, hãy siết nó và tiếp tục từ thời điểm đó, và cũng giới hạn kích thước hạt tối đa (ví dụ: |T|200) và cắt tỉa tìm kiếm nếu bạn không tìm thấy một khoảng thời gian 2 trong ví dụ đầu tiên 400 ký tự.


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

Gọi cho contiguous_repeated_string("abcdefgabcdabcdabcd")bạn sẽ nhận được abcdabcdabcd.

Gọi cho contiguous_repeated_string("ATCGATCGA")bạn sẽ nhận được ATCGATCG.

Và sau đó bạn có thể sử dụng KMP Longest Proper Prefix Postfix arrayđể gấp chuỗi kết quả với O(N).

Tổng thời gian phức tạp là O(N^2).


"Tổng độ phức tạp thời gian là Ôi(N2)". Còn về sự phức tạp thời gian tồi tệ nhất?
John L.
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.