2012-06-18 24 views
36

Necesito encontrar la secuencia más larga en una cadena con la advertencia de que la secuencia debe repetirse tres o más veces. Así, por ejemplo, si mi cadena es:Encontrar la secuencia repetitiva más larga en una cadena

fdwaw4helloworldvcdv1c3xcv3xcz1sda21f2sd1ahelloworldgafgfa4564534321fadghelloworld

entonces me gustaría el valor "holamundo" para ser devueltos.

Yo sé de un par de maneras de lograr esto, pero el problema que estoy enfrentando es que la cadena real es absurdamente grande, así que estoy realmente en busca de un método que puede hacerlo en el momento oportuno.

+7

No sé si hay una solución de expresiones regulares para este problema. Esta _puede ser una expresión regular, pero python podría tener una extensión no regular que hace algo como esto. En el caso general, este es el problema de LCS, que se puede resolver usando la programación dinámica: http://en.wikipedia.org/wiki/Longest_common_subsequence_problem –

Respuesta

30

Este problema es una variante del longest repeated substring problem y hay un algoritmo O (n) -time para resolverlo que usa suffix trees. La idea (como sugiere Wikipedia) es construir un árbol de sufijos (tiempo O (n)), anotar todos los nodos en el árbol con el número de descendientes (tiempo O (n) usando un DFS), y luego encontrar el árbol de sufijos (tiempo O (n)). nodo más profundo en el árbol con al menos tres descendientes (tiempo O (n) usando un DFS). Este algoritmo general toma tiempo O (n).

Dicho esto, los árboles de sufijo son muy difíciles de construir, por lo que es probable que desee encontrar una biblioteca de Python que implemente los árboles de sufijos antes de intentar esta implementación. Una búsqueda rápida en Google aparece this library, aunque no estoy seguro de si esta es una buena implementación.

Espero que esto ayude!

+1

"notoriamente difícil de construir", ¿qué? –

+8

@ KonradRudolph- No conozco ningún algoritmo "simple" para construir árboles de sufijos en tiempo lineal. Los dos algoritmos que conozco (el algoritmo de Ukkonen y el algoritmo DC3) son extremadamente complicados y no tienen pruebas obvias de corrección. Dicho esto, si me equivoco al respecto, ¡me encantaría que me corrijan! – templatetypedef

+0

Estoy de acuerdo con que las pruebas no son triviales. Pero existen pseudocódigos para el algoritmo de Ukkonen que son fáciles de adaptar. Además, aunque los algoritmos de tiempo lineal son difíciles de encontrar, existen algoritmos de construcción no lineales derivados trivialmente que, sin embargo, funcionan bastante bien en la práctica. –

0

La primera idea que viene a la mente es la búsqueda con expresiones regulares cada vez más grandes:

import re 

text = 'fdwaw4helloworldvcdv1c3xcv3xcz1sda21f2sd1ahelloworldgafgfa4564534321fadghelloworld' 
largest = '' 
i = 1 

while 1: 
    m = re.search("(" + ("\w" * i) + ").*\\1.*\\1", text) 
    if not m: 
     break 
    largest = m.group(1) 
    i += 1 

print largest # helloworld 

El código se ejecutó correctamente. La complejidad del tiempo parece ser al menos O (n^2).

+0

no funciona con 'banana'. la respuesta debería ser 'ana 2 veces' –

3

Comencemos desde el final, cuente la frecuencia y deténgase tan pronto como aparezca el elemento más frecuente 3 o más veces.

from collections import Counter 
a='fdwaw4helloworldvcdv1c3xcv3xcz1sda21f2sd1ahelloworldgafgfa4564534321fadghelloworld' 
times=3 
for n in range(1,len(a)/times+1)[::-1]: 
    substrings=[a[i:i+n] for i in range(len(a)-n+1)] 
    freqs=Counter(substrings) 
    if freqs.most_common(1)[0][1]>=3: 
     seq=freqs.most_common(1)[0][0] 
     break 
print "sequence '%s' of length %s occurs %s or more times"%(seq,n,times) 

Resultado:

>>> sequence 'helloworld' of length 10 occurs 3 or more times 

Editar: si usted tiene la sensación de que está tratando con la entrada al azar y la subcadena común debe ser de pequeña longitud, es mejor empezar (si es necesario la velocidad) con pequeñas subseries y parar cuando no se puede encontrar ninguna que aparecen al menos 3 veces:

from collections import Counter 
a='fdwaw4helloworldvcdv1c3xcv3xcz1sda21f2sd1ahelloworldgafgfa4564534321fadghelloworld' 
times=3 
for n in range(1,len(a)/times+1): 
    substrings=[a[i:i+n] for i in range(len(a)-n+1)] 
    freqs=Counter(substrings) 
    if freqs.most_common(1)[0][1]<3: 
     n-=1 
     break 
    else: 
     seq=freqs.most_common(1)[0][0] 
print "sequence '%s' of length %s occurs %s or more times"%(seq,n,times) 

el mismo resultado anterior.

+0

Lo que quiere decir, construya cada conjunto de subcadenas M elementos de largo, comenzando con' M = len (a)/N' (donde N es el número mínimo de repeticiones), y cuente los representantes de cada. Si ninguna subcadena tiene número de ocurrencias> = N, resta 1 de M e inténtalo de nuevo. ¿Sí? – PaulMcG

+0

@PaulMcGuire exactamente –

+0

Estoy intentando algo similar en la dirección opuesta (pequeña a grande). ¿Alguna idea de la complejidad del tiempo para su enfoque? –

9

Use defaultdict para contar cada subcadena comenzando con cada posición en la cadena de entrada. El OP no estaba claro si las coincidencias superpuestas deberían o no deberían incluirse, este método de fuerza bruta las incluye.

from collections import defaultdict 

def getsubs(loc, s): 
    substr = s[loc:] 
    i = -1 
    while(substr): 
     yield substr 
     substr = s[loc:i] 
     i -= 1 

def longestRepetitiveSubstring(r, minocc=3): 
    occ = defaultdict(int) 
    # tally all occurrences of all substrings 
    for i in range(len(r)): 
     for sub in getsubs(i,r): 
      occ[sub] += 1 

    # filter out all substrings with fewer than minocc occurrences 
    occ_minocc = [k for k,v in occ.items() if v >= minocc] 

    if occ_minocc: 
     maxkey = max(occ_minocc, key=len) 
     return maxkey, occ[maxkey] 
    else: 
     raise ValueError("no repetitions of any substring of '%s' with %d or more occurrences" % (r,minocc)) 

impresiones:

('helloworld', 3) 
+3

Realmente me encanta esta solución, pero desafortunadamente mis hilos son demasiado grandes. Apuesto, sin embargo, a que su respuesta será muy útil para varias personas que desembarquen aquí a través de Google, ya que resuelve el ejemplo original que di bastante bien. – Snesticle

0

Si se invierte la cadena de entrada, luego alimentar a una expresión regular como (.+)(?:.*\1){2}
hay que darle la cadena más larga repitieron 3 veces.(Grupo de captura inversa 1 para la respuesta)

Editar:
Tengo que decir cancelar esta manera. Depende del primer partido. A menos que se pruebe contra una longitud de curr contra longitud máxima hasta el momento, en un ciclo iterativo, regex no funcionará para esto.

Cuestiones relacionadas