Estoy buscando una manera de probar si una cadena dada se repite o no para toda la cadena o no.
Ejemplos:
[
'0045662100456621004566210045662100456621', # '00456621'
'0072992700729927007299270072992700729927', # '00729927'
'001443001443001443001443001443001443001443', # '001443'
'037037037037037037037037037037037037037037037', # '037'
'047619047619047619047619047619047619047619', # '047619'
'002457002457002457002457002457002457002457', # '002457'
'001221001221001221001221001221001221001221', # '001221'
'001230012300123001230012300123001230012300123', # '00123'
'0013947001394700139470013947001394700139470013947', # '0013947'
'001001001001001001001001001001001001001001001001001', # '001'
'001406469760900140646976090014064697609', # '0014064697609'
]
son cadenas que se repiten, y
[
'004608294930875576036866359447',
'00469483568075117370892018779342723',
'004739336492890995260663507109',
'001508295625942684766214177978883861236802413273',
'007518796992481203',
'0071942446043165467625899280575539568345323741',
'0434782608695652173913',
'0344827586206896551724137931',
'002481389578163771712158808933',
'002932551319648093841642228739',
'0035587188612099644128113879',
'003484320557491289198606271777',
'00115074798619102416570771',
]
son ejemplos de los que no.
Las secciones repetidas de las cadenas que me dan pueden ser bastante largas, y las cadenas en sí pueden tener 500 o más caracteres, por lo que recorrer cada personaje tratando de construir un patrón y luego verificar el patrón frente al resto de la cadena parece muy lento. Multiplique eso por potencialmente cientos de cadenas y no puedo ver ninguna solución intuitiva.
He examinado un poco las expresiones regulares y parecen buenas para cuando sabes lo que estás buscando, o al menos la longitud del patrón que estás buscando. Lamentablemente, no sé tampoco.
¿Cómo puedo saber si una cadena se repite y, si es así, cuál es la subsecuencia de repetición más corta?
Respuestas:
Aquí hay una solución concisa que evita expresiones regulares y bucles lentos en Python:
Consulte la respuesta Wiki de la comunidad iniciada por @davidism para obtener resultados de referencia. En resumen,
(Las palabras de esa respuesta, no las mías).
Esto se basa en la observación de que una cadena es periódica si y solo si es igual a una rotación no trivial de sí misma. Felicitaciones a @AleksiTorhamo por darnos cuenta de que luego podemos recuperar el período principal del índice de la primera aparición de
s
in(s+s)[1:-1]
, y por informarme de los argumentos opcionalesstart
yend
de Pythonstring.find
.fuente
.find()
o en.index()
lugar dein
, por ejemplo.(s+s).find(s, 1, -1)
.(s+s).find(s, 1, -1)
que será (muy ligeramente) más rápido que(s+s)[1:-1].find(s)
, al menos para cadenas más grandes, ya que el corte significa que debe crear otra copia de (casi) toda la cadena."abcd"
saca el carácter de la derecha y pégalo de nuevo a la izquierda para obtenerlo"dabc"
. Este procedimiento se llama rotar una cadena a la derecha por 1 carácter . Repita losn
tiempos para rotar una cadena a la derecha porn
caracteres. Ahora observe que si tenemos una cadena dek
caracteres, la rotación a la derecha por cualquier múltiplo dek
hojas deja la cadena sin cambios. Una rotación no trivial de una cadena es aquella cuyo número de caracteres no es múltiplo de la longitud de la cadena.Aquí hay una solución usando expresiones regulares.
Iterando sobre los ejemplos en la pregunta:
... produce esta salida:
La expresión regular
(.+?)\1+$
se divide en tres partes:(.+?)
es un grupo coincidente que contiene al menos uno (pero lo menos posible) de cualquier personaje (porque+?
no es codicioso ).\1+
busca al menos una repetición del grupo correspondiente en la primera parte.$
comprueba el final de la cadena, para asegurarse de que no haya contenido adicional que no se repita después de las subcadenas repetidas (y el usore.match()
asegura que no haya texto que no se repita antes de las subcadenas repetidas).En Python 3.4 y versiones posteriores, puede soltar
$
y usarre.fullmatch()
en su lugar, o (en cualquier Python al menos hasta 2.3) ir a otro lado y usarre.search()
con la expresión regular^(.+?)\1+$
, todo lo cual se debe más al gusto personal que a cualquier otra cosa.fuente
Puede hacer la observación de que para que una cadena se considere repetitiva, su longitud debe ser divisible por la longitud de su secuencia repetida. Dado que, aquí es una solución que genera divisores de la longitud desde
1
an / 2
inclusive, divide la cadena original en subcadenas con la longitud de los divisores, y prueba la igualdad del conjunto de resultados:EDITAR: en Python 3, el
/
operador ha cambiado para hacer la división flotante por defecto. Para obtener laint
división de Python 2, puede usar el//
operador en su lugar. Gracias a @ TigerhawkT3 por llamar mi atención sobre esto.El
//
operador realiza la división de enteros tanto en Python 2 como en Python 3, por lo que he actualizado la respuesta para admitir ambas versiones. La parte donde probamos para ver si todas las subcadenas son iguales ahora es una operación de cortocircuito que usaall
una expresión de generador.ACTUALIZACIÓN: en respuesta a un cambio en la pregunta original, el código ahora se ha actualizado para devolver la subcadena de repetición más pequeña si existe y
None
si no. @godlygeek ha sugerido usardivmod
para reducir el número de iteraciones en eldivisors
generador, y el código también se ha actualizado para que coincida con eso. Ahora devuelve todos los divisores positivos den
en orden ascendente, exclusivo den
sí mismo.Actualización adicional para un alto rendimiento: después de varias pruebas, he llegado a la conclusión de que simplemente probar la igualdad de cadena tiene el mejor rendimiento de cualquier solución de corte o iteración en Python. Por lo tanto, tomé una hoja del libro de @ TigerhawkT3 y actualicé mi solución. Ahora es más de 6 veces más rápido que antes, notablemente más rápido que la solución de Tigerhawk, pero más lento que el de David.
fuente
(n/2)
inclusivo.n / 2
dedivisors()
sern // 2
?Aquí hay algunos puntos de referencia para las diversas respuestas a esta pregunta. Hubo algunos resultados sorprendentes, incluido un rendimiento muy diferente dependiendo de la cadena que se está probando.
Algunas funciones se modificaron para funcionar con Python 3 (principalmente al reemplazar
/
con//
para asegurar la división de enteros). Si ve algo mal, desea agregar su función o desea agregar otra cadena de prueba, haga ping a @ZeroPiraeus en la sala de chat de Python .En resumen: hay aproximadamente una diferencia de 50x entre las soluciones de mejor y peor desempeño para el amplio conjunto de datos de ejemplo suministrados por OP aquí (a través de este comentario). La solución de David Zhang es el claro ganador, superando a todos los demás en alrededor de 5 veces para el gran conjunto de ejemplos.
Un par de respuestas son muy lentas en casos extremadamente grandes de "no coincidencia". De lo contrario, las funciones parecen ser iguales o claros ganadores dependiendo de la prueba.
Estos son los resultados, incluidas las parcelas realizadas con matplotlib y seaborn para mostrar las diferentes distribuciones:
Corpus 1 (ejemplos suministrados - conjunto pequeño)
Corpus 2 (ejemplos suministrados - conjunto grande)
Corpus 3 (casos de borde)
Las pruebas y los resultados en bruto están disponibles aquí .
fuente
Solución no regex:
Solución no regex más rápida, gracias a @ThatWeirdo (ver comentarios):
La solución anterior rara vez es más lenta que la original en un pequeño porcentaje, pero generalmente es un poco más rápido, a veces mucho más rápido. Todavía no es más rápido que el davidismo para cadenas más largas, y la solución regex de cero es superior para cadenas cortas. Sale al más rápido (según la prueba de davidismo en github, vea su respuesta) con cadenas de aproximadamente 1000-1500 caracteres. De todos modos, es confiablemente el segundo más rápido (o mejor) en todos los casos que probé. Gracias ThatWeirdo.
Prueba:
Resultados:
fuente
repeat('aa')
vuelveNone
len(string[0:i])
siempre es igual ai
(al menos en este caso). Reemplazarlos, y también guardarlen(string)
ystring[0:i]
en variables puede acelerar las cosas. También OMI, esta es una gran solución, increíble;)Primero, reduzca a la mitad la cadena siempre que sea un duplicado de "2 partes". Esto reduce el espacio de búsqueda si hay un número par de repeticiones. Luego, trabajando hacia adelante para encontrar la cadena repetitiva más pequeña, verifique si dividir la cadena completa por una subcadena cada vez más grande solo da como resultado valores vacíos. Solo las subcadenas
length // 2
deben ser probadas ya que cualquier cosa que no tenga repeticiones.Esto devuelve la coincidencia más corta o Ninguna si no hay coincidencia.
fuente
El problema también puede resolverse
O(n)
en el peor de los casos con la función de prefijo.Tenga en cuenta que puede ser más lento en el caso general (UPD: y es mucho más lento) que otras soluciones que dependen del número de divisores
n
, pero generalmente encuentran fallas antes, creo que uno de los casos negativos para ellos seráaaa....aab
, donde hayn - 1 = 2 * 3 * 5 * 7 ... *p_n - 1
a
'sPrimero que nada necesitas calcular la función de prefijo
entonces o no hay respuesta o el período más corto es
y solo tienes que verificar si
k != n and n % k == 0
(si lak != n and n % k == 0
respuesta ess[:k]
, de lo contrario no hay respuestaPuede verificar la prueba aquí (en ruso, pero el traductor en línea probablemente hará el truco)
fuente
prefix_function()
no es válida en Python: usted tiene dos puntos que faltan en suswhile
yif
declaraciones, y&&
en lugar deand
. Después de arreglarlos, fallaUnboundLocalError: local variable 'i' referenced before assignment
debido a la líneafor i in range(i, n):
.prefix_function()
para devolver resultados similares a las otras respuestas, ya sea la subcadena más corta o la incluiréNone
en un punto de referencia revisado que estoy armando.Esta versión intenta solo aquellas longitudes de secuencia candidatas que son factores de la longitud de la cadena; y usa el
*
operador para construir una cadena de longitud completa a partir de la secuencia candidata:Gracias a TigerhawkT3 por notar que
length // 2
sin+ 1
dejaría de coincidir con elabab
caso.fuente
range
límite delength//2
, al igual que yo, tiene que cambiar esolength//2+1
si desea atrapar cadenas que se duplican exactamente (por ejemplo'aabaab'
).Aquí hay una solución directa, sin expresiones regulares.
Para las subcadenas que
s
comienzan desde el índice cero, de longitudes de 1 alen(s)
, verifique si esa subcadenasubstr
es el patrón repetido. Esta verificación se puede realizar concatenandosubstr
consigo mismaratio
veces, de modo que la longitud de la cadena así formada sea igual a la longitud des
. Por lo tantoratio=len(s)/len(substr)
.Regrese cuando se encuentre dicha subcadena. Esto proporcionaría la subcadena más pequeña posible, si existe.
fuente
Comencé con más de ocho soluciones a este problema. Algunos eran bases en expresiones regulares (match, findall, split), algunos de corte y prueba de string, y algunos con métodos de string (find, count, split). Cada uno tenía beneficios en la claridad del código, el tamaño del código, la velocidad y el consumo de memoria. Iba a publicar mi respuesta aquí cuando noté que la velocidad de ejecución se clasificó como importante, así que hice más pruebas y mejoras para llegar a esto:
Esta respuesta parece similar a algunas otras respuestas aquí, pero tiene algunas optimizaciones de velocidad que otros no han usado:
xrange
es un poco más rápido en esta aplicación,s[:n]
directamente, evitamos crear una variable en cada bucle.Me interesaría ver cómo funciona esto en las pruebas estándar con hardware común. Creo que será muy inferior al excelente algoritmo de David Zhang en la mayoría de las pruebas, pero de lo contrario debería ser bastante rápido.
Este problema me pareció muy intuitivo. Las soluciones que pensé que serían rápidas eran lentas. ¡Las soluciones que parecían lentas fueron rápidas! Parece que la creación de cadenas de Python con el operador de multiplicación y las comparaciones de cadenas están altamente optimizadas.
fuente
statistics
módulo), así que tuve que cambiar su/
s a//
s y reemplazarxrange()
conrange()
(que se comporta como 2.xxrange()
en 3.x).//
en 3.x es la división entera (al igual que el comportamiento 2.x de/
), mientras que 3.x/
es la división flotante (que estoy seguro sería mucho más lenta incluso si no se rompe la solución al intentar usar un flotador como índice). Como se mencionó, 3.xrange()
es lo mismo que 2.x'sxrange()
; no existe equivalente de 2.xrange()
en 3.x. Por lo tanto, no creo que esa sea la causa de ninguna discrepancia entre el punto de referencia y los tiempos que haya realizado. Probablemente sea solo que 3.x es más lento en general que 2.x (o tal vez su máquina es más rápida que la mía).Esta función se ejecuta muy rápidamente (probada y es más de 3 veces más rápida que la solución más rápida aquí en cadenas con más de 100k caracteres y la diferencia aumenta a medida que se repite el patrón). Intenta minimizar la cantidad de comparaciones necesarias para obtener la respuesta:
Tenga en cuenta que, por ejemplo, para la cadena de longitud 8 verifica solo un fragmento de tamaño 4 y no tiene que probar más porque el patrón de longitud 1 o 2 daría como resultado la repetición del patrón de longitud 4:
fuente
En la respuesta de David Zhang, si tenemos algún tipo de búfer circular, esto no funcionará:
principal_period('6210045662100456621004566210045662100456621')
debido al comienzo621
, donde me hubiera gustado que escupiera:00456621
.Extendiendo su solución podemos usar lo siguiente:
fuente
Aquí está el código en python que verifica la repetición de subcadena en la cadena principal dada por el usuario .
Entrada :
Salida :
Entrada :
Salida :
fuente