Come posso sapere se una stringa si ripete in Python?


352

Sto cercando un modo per verificare se una determinata stringa si ripete o meno per l'intera stringa o meno.

Esempi:

[
    '0045662100456621004566210045662100456621',             # '00456621'
    '0072992700729927007299270072992700729927',             # '00729927'
    '001443001443001443001443001443001443001443',           # '001443'
    '037037037037037037037037037037037037037037037',        # '037'
    '047619047619047619047619047619047619047619',           # '047619'
    '002457002457002457002457002457002457002457',           # '002457'
    '001221001221001221001221001221001221001221',           # '001221'
    '001230012300123001230012300123001230012300123',        # '00123'
    '0013947001394700139470013947001394700139470013947',    # '0013947'
    '001001001001001001001001001001001001001001001001001',  # '001'
    '001406469760900140646976090014064697609',              # '0014064697609'
]

sono stringhe che si ripetono e

[
    '004608294930875576036866359447',
    '00469483568075117370892018779342723',
    '004739336492890995260663507109',
    '001508295625942684766214177978883861236802413273',
    '007518796992481203',
    '0071942446043165467625899280575539568345323741',
    '0434782608695652173913',
    '0344827586206896551724137931',
    '002481389578163771712158808933',
    '002932551319648093841642228739',
    '0035587188612099644128113879',
    '003484320557491289198606271777',
    '00115074798619102416570771',
]

sono esempi di quelli che non lo fanno.

Le sezioni ripetute delle stringhe che mi vengono fornite possono essere piuttosto lunghe, e le stringhe stesse possono essere di 500 o più caratteri, quindi scorrere ogni personaggio nel tentativo di creare un pattern e poi controllare il pattern rispetto al resto della stringa sembra terribilmente lento. Moltiplicalo potenzialmente per centinaia di stringhe e non riesco a vedere alcuna soluzione intuitiva.

Ho esaminato un po 'le regex e sembrano buone per quando sai cosa stai cercando, o almeno per la lunghezza del pattern che stai cercando. Sfortunatamente, non conosco nessuno dei due.

Come posso sapere se una stringa si sta ripetendo e, se lo è, qual è la sottosequenza ripetuta più breve?


15
passare in rassegna ogni personaggio cercando di creare un modello e poi controllarlo rispetto al resto della stringa sembra terribilmente lento - ma è vero?
Tim


2
@AvinashRaj Questa è solo la parte corrispondente di una stringa, non la cosa completa.
John,

11
@AvinashRaj L'OP sta chiedendo tutte le possibili soluzioni. La domanda a cui ti colleghi accetta solo la soluzione regex . Si noti che regex potrebbe essere in grado di risolvere il problema, ma in molto più tempo del necessario. Ad esempio, una soluzione ottimale (tempo lineare) userebbe l'albero dei suffissi del testo. Devi solo trovare la sottostringa ripetuta più lunga e fare alcuni controlli sulle lunghezze.
Bakuriu,

2
@ TigerhawkT3 Il set di dati reale è troppo grande e ingombrante, ma gli esempi nella domanda ne fanno parte e, se lo desideri, eccone altri .
John,

Risposte:


570

Ecco una soluzione concisa che evita le espressioni regolari e i loop lenti in Python:

def principal_period(s):
    i = (s+s).find(s, 1, -1)
    return None if i == -1 else s[:i]

Vedere la risposta Wiki della community avviata da @davidism per risultati di riferimento. In sintesi,

La soluzione di David Zhang è il chiaro vincitore, sovraperformando tutti gli altri di almeno 5 volte per l'ampio set di esempi.

(Le parole di quella risposta, non le mie.)

Questo si basa sull'osservazione che una stringa è periodica se e solo se è uguale a una rotazione non banale di se stessa. Complimenti a @AleksiTorhamo per aver realizzato che possiamo quindi recuperare il periodo principale dall'indice della prima occorrenza di sin (s+s)[1:-1]e per avermi informato dell'opzionale starte degli endargomenti di Python string.find.


19
Puoi estenderlo per trovare la sottosequenza ripetuta più breve usando .find()o .index()invece di in, ad es. (s+s).find(s, 1, -1).
Aleksi Torhamo,

11
Inoltre, penso che (s+s).find(s, 1, -1)sarà (leggermente) più veloce di (s+s)[1:-1].find(s), almeno per stringhe più grandi, poiché lo slicing significa che devi creare un'altra copia (quasi) dell'intera stringa.
Aleksi Torhamo,

13
È come se prendi un'onda sin o cos da una funzione periodica e la sposti a destra. Dal momento che è completamente periodico, le onde alla fine si abbineranno perfettamente ... I calcoli matematici paralleli a questa soluzione sono semplicemente fenomenali. :) Vorrei poterti dare + ∞ voti.
Shashank,

6
Il recente aggiornamento di Guido a PEP 8 è pertinente qui: "Siate coerenti nelle dichiarazioni di ritorno. O tutte le dichiarazioni di ritorno in una funzione dovrebbero restituire un'espressione, o nessuna di esse dovrebbe. Se una dichiarazione di ritorno restituisce un'espressione, qualsiasi dichiarazione di ritorno dove nessun valore è return deve dichiarare esplicitamente questo come return None, e una dichiarazione esplicita di ritorno dovrebbe essere presente alla fine della funzione (se raggiungibile). "
Zero Pireo,

8
@WayneConrad Prendi una stringa, diciamo, fai saltare "abcd"il carattere a destra e incollalo nuovamente a sinistra per ottenere "dabc". Questa procedura è chiamata rotazione di una stringa a destra di 1 carattere . Ripetere i ntempi per ruotare una stringa a destra di ncaratteri. Ora osserva che se abbiamo una stringa di kcaratteri, ruotando a destra di qualsiasi multiplo di klascia invariata la stringa. Una rotazione non banale di una stringa è una il cui numero di caratteri non è un multiplo della lunghezza della stringa.
David Zhang,

181

Ecco una soluzione che utilizza espressioni regolari.

import re

REPEATER = re.compile(r"(.+?)\1+$")

def repeated(s):
    match = REPEATER.match(s)
    return match.group(1) if match else None

Scorrendo gli esempi nella domanda:

examples = [
    '0045662100456621004566210045662100456621',
    '0072992700729927007299270072992700729927',
    '001443001443001443001443001443001443001443',
    '037037037037037037037037037037037037037037037',
    '047619047619047619047619047619047619047619',
    '002457002457002457002457002457002457002457',
    '001221001221001221001221001221001221001221',
    '001230012300123001230012300123001230012300123',
    '0013947001394700139470013947001394700139470013947',
    '001001001001001001001001001001001001001001001001001',
    '001406469760900140646976090014064697609',
    '004608294930875576036866359447',
    '00469483568075117370892018779342723',
    '004739336492890995260663507109',
    '001508295625942684766214177978883861236802413273',
    '007518796992481203',
    '0071942446043165467625899280575539568345323741',
    '0434782608695652173913',
    '0344827586206896551724137931',
    '002481389578163771712158808933',
    '002932551319648093841642228739',
    '0035587188612099644128113879',
    '003484320557491289198606271777',
    '00115074798619102416570771',
]

for e in examples:
    sub = repeated(e)
    if sub:
        print("%r: %r" % (e, sub))
    else:
        print("%r does not repeat." % e)

... produce questo output:

'0045662100456621004566210045662100456621': '00456621'
'0072992700729927007299270072992700729927': '00729927'
'001443001443001443001443001443001443001443': '001443'
'037037037037037037037037037037037037037037037': '037'
'047619047619047619047619047619047619047619': '047619'
'002457002457002457002457002457002457002457': '002457'
'001221001221001221001221001221001221001221': '001221'
'001230012300123001230012300123001230012300123': '00123'
'0013947001394700139470013947001394700139470013947': '0013947'
'001001001001001001001001001001001001001001001001001': '001'
'001406469760900140646976090014064697609': '0014064697609'
'004608294930875576036866359447' does not repeat.
'00469483568075117370892018779342723' does not repeat.
'004739336492890995260663507109' does not repeat.
'001508295625942684766214177978883861236802413273' does not repeat.
'007518796992481203' does not repeat.
'0071942446043165467625899280575539568345323741' does not repeat.
'0434782608695652173913' does not repeat.
'0344827586206896551724137931' does not repeat.
'002481389578163771712158808933' does not repeat.
'002932551319648093841642228739' does not repeat.
'0035587188612099644128113879' does not repeat.
'003484320557491289198606271777' does not repeat.
'00115074798619102416570771' does not repeat.

L'espressione regolare (.+?)\1+$è divisa in tre parti:

  1. (.+?)è un gruppo corrispondente contenente almeno uno (ma il minor numero possibile) di qualsiasi personaggio (perché +?non è avido ).

  2. \1+ controlla almeno una ripetizione del gruppo corrispondente nella prima parte.

  3. $controlla la fine della stringa, per assicurarsi che non vi siano contenuti extra non ripetitivi dopo le sottostringhe ripetute (e l'utilizzo re.match()garantisce che non ci sia testo non ripetuto prima delle sottostringhe ripetute).

In Python 3.4 e versioni successive, è possibile rilasciare $e usare re.fullmatch()invece, oppure (in qualsiasi Python almeno fino alla 2.3) andare nell'altra direzione e utilizzare re.search()con il regex ^(.+?)\1+$, che sono tutti più legati al gusto personale di ogni altra cosa.


6
Non ho idea del perché questa concisa fodera non sia la risposta più votata. Le altre risposte non sono male, ma questa è di gran lunga migliore. (Può usare l' espressione regolare frequentemente denigrata , ma posso ispezionarlo molto più facilmente delle altre risposte molto più lunghe, che sono piene di blocchi nidificati, potenziali errori di battitura, errori off-by-one, ecc.) Ah bene, peggio è meglio Credo.
Paul Draper,

9
Penso che ci siano due ragioni principali per questo: 1) ad alcuni programmatori piace la matematica più di quanto non piacciano le regex, e 2) poiché variando la lunghezza e la natura delle stringhe di input fa avanzare risposte diverse sulle prestazioni, le stringhe super-long case (che potrebbe anche non apparire nei dati reali) rendono questa soluzione non ottimale.
TigerhawkT3,

a volte incontri grandi pregiudizi nei confronti delle espressioni regolari. Ive aveva 2 manager che proibivano l'uso delle espressioni regolari perché avevano sentito che le espressioni regolari erano lo strumento sbagliato per il lavoro. Tranne il rapporto sessuale, hanno proseguito chiedendomi di implementare un motore regexp
joojaa

1
@PaulDraper: indovina cosa sta facendo regex dietro la scena? sta analizzando la stringa e memorizza ogni elemento fino a quando si verifica una corrispondenza di reapeatition possibile. È lo stesso che statet dell'OP è troppo lento. quindi solo perché è un liner 2 non c'è alcuna vittoria nelle prestazioni.
Dhein,

2
@Zaibis, mi piacerebbe normalmente d'accordo, ma questo è sia la soluzione più breve e veloce ( stackoverflow.com/a/29482936/1212596)....Except per David, che è stato pubblicato dopo che ho fatto quel commento. In realtà mi piace di più l'approccio di David (intelligente!).
Paul Draper,

90

Puoi fare l'osservazione che per considerare una stringa da ripetere, la sua lunghezza deve essere divisibile per la lunghezza della sua sequenza ripetuta. Dato che, ecco una soluzione che genera divisori della lunghezza da 1a n / 2compreso, divide la stringa originale in sottostringhe con la lunghezza dei divisori, e verifica l'uguaglianza del set di risultati:

from math import sqrt, floor

def divquot(n):
    if n > 1:
        yield 1, n
    swapped = []
    for d in range(2, int(floor(sqrt(n))) + 1):
        q, r = divmod(n, d)
        if r == 0:
            yield d, q
            swapped.append((q, d))
    while swapped:
        yield swapped.pop()

def repeats(s):
    n = len(s)
    for d, q in divquot(n):
        sl = s[0:d]
        if sl * q == s:
            return sl
    return None

EDIT: in Python 3, l' /operatore è stato modificato per eseguire la divisione float per impostazione predefinita. Per ottenere la intdivisione da Python 2, è possibile utilizzare //invece l' operatore. Grazie a @ TigerhawkT3 per avermi portato alla mia attenzione.

L' //operatore esegue la divisione di interi in Python 2 e Python 3, quindi ho aggiornato la risposta per supportare entrambe le versioni. La parte in cui testiamo per vedere se tutte le sottostringhe sono uguali è ora un'operazione di corto circuito che utilizzaall e un'espressione del generatore.

AGGIORNAMENTO: in risposta a una modifica alla domanda originale, il codice è stato ora aggiornato per restituire la sottostringa ripetuta più piccola se esiste e in Nonecaso contrario. @godlygeek ha suggerito di utilizzare divmodper ridurre il numero di iterazioni sul divisorsgeneratore e il codice è stato aggiornato per corrispondere anche a quello. Ora restituisce tutti i divisori positivi di nin ordine crescente, esclusivo di nse stesso.

Ulteriore aggiornamento per prestazioni elevate: dopo più test, sono giunto alla conclusione che il semplice test per l'uguaglianza delle stringhe offre le migliori prestazioni rispetto a qualsiasi soluzione di slicing o iteratore in Python. Quindi, ho preso una foglia dal libro di @ TigerhawkT3 e ho aggiornato la mia soluzione. Ora è oltre 6 volte più veloce di prima, notevolmente più veloce della soluzione di Tigerhawk ma più lento di quella di David.


3
Questa soluzione è sorprendente. È possibile modificare il metodo divisori per seguire il modello di produzione-consumo. In modo che produca i risultati così come sono stati trovati. Sarà un peccato se questa non è la risposta più alta. Tutto il resto è forza bruta.
Justin Danielani,

3
@JustinDanielson Restituisce un oggetto generatore creato da un'espressione generatore, che è un produttore implicito :) Valuterà in modo pigro i divisori.
Shashank,

1
Ohh. Non lo sapevo. Bene, anche meglio allora. : DI capire perché vuoi evitare sqrt, ma potresti usare n / 2 come limite superiore per l'intervallo del divisore.
JustinDanielson,

1
@JustinDanielson Grazie per il suggerimento, il limite superiore dell'intervallo è ora (n/2)inclusivo.
Shashank,

1
Dovrebbe n / 2in divisors()essere n // 2?
TigerhawkT3,

87

Ecco alcuni parametri di riferimento per le varie risposte a questa domanda. Ci sono stati alcuni risultati sorprendenti, tra cui prestazioni molto diverse a seconda della stringa testata.

Alcune funzioni sono state modificate per funzionare con Python 3 (principalmente sostituendo /con //per garantire la divisione dei numeri interi). Se vedi qualcosa di sbagliato, vuoi aggiungere la tua funzione o vuoi aggiungere un'altra stringa di test, esegui il ping di @ZeroPiraeus nella chat room di Python .

In sintesi: esiste una differenza di circa 50 volte tra le soluzioni migliori e quelle con le prestazioni peggiori per l'ampia serie di dati di esempio forniti da OP qui (tramite questo commento). La soluzione di David Zhang è il chiaro vincitore, superando tutti gli altri di circa 5 volte per il grande set di esempi.

Un paio di risposte sono molto lente in casi "nessuna corrispondenza" estremamente grandi. Altrimenti, le funzioni sembrano essere ugualmente vincenti o vincitori chiari a seconda del test.

Ecco i risultati, inclusi i grafici realizzati con matplotlib e Seaborn per mostrare le diverse distribuzioni:


Corpus 1 (esempi forniti - set piccolo)

mean performance:
 0.0003  david_zhang
 0.0009  zero
 0.0013  antti
 0.0013  tigerhawk_2
 0.0015  carpetpython
 0.0029  tigerhawk_1
 0.0031  davidism
 0.0035  saksham
 0.0046  shashank
 0.0052  riad
 0.0056  piotr

median performance:
 0.0003  david_zhang
 0.0008  zero
 0.0013  antti
 0.0013  tigerhawk_2
 0.0014  carpetpython
 0.0027  tigerhawk_1
 0.0031  davidism
 0.0038  saksham
 0.0044  shashank
 0.0054  riad
 0.0058  piotr

Corpus 1 grafico


Corpus 2 (esempi forniti - set grande)

mean performance:
 0.0006  david_zhang
 0.0036  tigerhawk_2
 0.0036  antti
 0.0037  zero
 0.0039  carpetpython
 0.0052  shashank
 0.0056  piotr
 0.0066  davidism
 0.0120  tigerhawk_1
 0.0177  riad
 0.0283  saksham

median performance:
 0.0004  david_zhang
 0.0018  zero
 0.0022  tigerhawk_2
 0.0022  antti
 0.0024  carpetpython
 0.0043  davidism
 0.0049  shashank
 0.0055  piotr
 0.0061  tigerhawk_1
 0.0077  riad
 0.0109  saksham

Corpus 1 grafico


Corpus 3 (casi limite)

mean performance:
 0.0123  shashank
 0.0375  david_zhang
 0.0376  piotr
 0.0394  carpetpython
 0.0479  antti
 0.0488  tigerhawk_2
 0.2269  tigerhawk_1
 0.2336  davidism
 0.7239  saksham
 3.6265  zero
 6.0111  riad

median performance:
 0.0107  tigerhawk_2
 0.0108  antti
 0.0109  carpetpython
 0.0135  david_zhang
 0.0137  tigerhawk_1
 0.0150  shashank
 0.0229  saksham
 0.0255  piotr
 0.0721  davidism
 0.1080  zero
 1.8539  riad

Grafico Corpus 3


I test e i risultati grezzi sono disponibili qui .


37

Soluzione non regex:

def repeat(string):
    for i in range(1, len(string)//2+1):
        if not len(string)%len(string[0:i]) and string[0:i]*(len(string)//len(string[0:i])) == string:
            return string[0:i]

Soluzione non regex più veloce, grazie a @ThatWeirdo (vedi commenti):

def repeat(string):
    l = len(string)
    for i in range(1, len(string)//2+1):
        if l%i: continue
        s = string[0:i]
        if s*(l//i) == string:
            return s

La soluzione di cui sopra è molto raramente più lenta di quella originale di qualche percento, ma di solito è un po 'più veloce - a volte molto più veloce. Non è ancora più veloce di David per stringhe più lunghe e la soluzione regex di zero è superiore per stringhe corte. Arriva al più veloce (secondo il test di davidism su github - vedi la sua risposta) con stringhe di circa 1000-1500 caratteri. Indipendentemente da ciò, è affidabile il secondo più veloce (o migliore) in tutti i casi che ho testato. Grazie ThatWeirdo.

Test:

print(repeat('009009009'))
print(repeat('254725472547'))
print(repeat('abcdeabcdeabcdeabcde'))
print(repeat('abcdefg'))
print(repeat('09099099909999'))
print(repeat('02589675192'))

risultati:

009
2547
abcde
None
None
None

Non è una soluzione di forza bruta?
JustinDanielson,

7
@JustinDanielson Sì. Ma una soluzione comunque.
Sinkingpoint,

3
Visualizzo circa 1e-5 a 3e-5 secondi per stringhe brevi, da 3e-5 a 4e-5 secondi per stringhe lunghe (1000 caratteri) di successo, e un po 'meno di un millisecondo per stringhe lunghe non riuscite (caso peggiore) . Quindi un migliaio di stringhe di 1000 caratteri richiederebbe circa un secondo. Rispetto alla risposta matematica, questo trova corrispondenze 10 volte più veloci, ma impiega 3 volte più a fallire.
TigerhawkT3,

repeat('aa')ritornaNone
Tom Cornebize il

2
len(string[0:i])è sempre uguale a i(almeno in questo caso). Sostituendoli, e anche il salvataggio len(string)e le string[0:i]variabili potrebbero accelerare le cose. Anche IMO questa è un'ottima soluzione, eccezionale;)
ThatWeirdo,

24

Innanzitutto, dimezza la stringa purché sia ​​un duplicato di "2 parti". Ciò riduce lo spazio di ricerca in presenza di un numero pari di ripetizioni. Quindi, lavorando in avanti per trovare la stringa ripetuta più piccola, controlla se dividere la stringa intera con sottostringa sempre più grande produce solo valori vuoti. Solo le sottostringhe length // 2devono essere testate poiché qualsiasi cosa che non abbia ripetizioni.

def shortest_repeat(orig_value):
    if not orig_value:
        return None

    value = orig_value

    while True:
        len_half = len(value) // 2
        first_half = value[:len_half]

        if first_half != value[len_half:]:
            break

        value = first_half

    len_value = len(value)
    split = value.split

    for i in (i for i in range(1, len_value // 2) if len_value % i == 0):
        if not any(split(value[:i])):
            return value[:i]

    return value if value != orig_value else None

Ciò restituisce la corrispondenza più breve o nessuna se non vi è alcuna corrispondenza.


16

Il problema può anche essere risolto O(n)nel peggiore dei casi con la funzione prefisso.

Nota, potrebbe essere più lento in generale (UPD: ed è molto più lento) rispetto ad altre soluzioni che dipendono dal numero di divisori di n, ma di solito trovano prima i fallimenti, penso che sarà uno dei casi cattivi per loro aaa....aab, dove ci sonon - 1 = 2 * 3 * 5 * 7 ... *p_n - 1 a 's

Prima di tutto devi calcolare la funzione del prefisso

def prefix_function(s):
    n = len(s)
    pi = [0] * n
    for i in xrange(1, n):
        j = pi[i - 1]
        while(j > 0 and s[i] != s[j]):
            j = pi[j - 1]
        if (s[i] == s[j]):
            j += 1
        pi[i] = j;
    return pi

allora o non c'è risposta o il periodo più breve è

k = len(s) - prefix_function(s[-1])

e devi solo verificare se k != n and n % k == 0(se k != n and n % k == 0allora la risposta ès[:k] , altrimenti non c'è risposta

Puoi controllare la prova qui (in russo, ma probabilmente il traduttore online farà il trucco)

def riad(s):
    n = len(s)
    pi = [0] * n
    for i in xrange(1, n):
        j = pi[i - 1]
        while(j > 0 and s[i] != s[j]):
            j = pi[j - 1]
        if (s[i] == s[j]):
            j += 1
        pi[i] = j;
    k = n - pi[-1]
    return s[:k] if (n != k and n % k == 0) else None

Il tuo prefix_function()non è Python valido: hai due punti mancanti sul tuo whilee ifdichiarazioni, e &&invece di and. Dopo aver risolto quelli, fallisce a UnboundLocalError: local variable 'i' referenced before assignmentcausa della linea for i in range(i, n):.
Zero Pireo

Grazie :-) Se riesci a mettere insieme una funzione che usa il tuo prefix_function()per restituire risultati simili alle altre risposte - o la sottostringa più corta o None- la includerò in un benchmark rivisto che sto mettendo insieme.
Zero Pireo

@ZeroPiraeus, In realtà funziona benissimo, l'ho appena chiamato in modo sbagliato
RiaD

16

Questa versione prova solo quelle lunghezze di sequenza candidate che sono fattori della lunghezza della stringa; e utilizza l' *operatore per creare una stringa a lunghezza intera dalla sequenza candidata:

def get_shortest_repeat(string):
    length = len(string)
    for i in range(1, length // 2 + 1):
        if length % i:  # skip non-factors early
            continue

        candidate = string[:i]
        if string == candidate * (length // i):
            return candidate

    return None

Grazie a TigerhawkT3 per aver notato che length // 2senza + 1non riuscirebbe ad abbinare il ababcaso.


Questa soluzione è praticamente identica alla mia ottimizzata. Vedo che hai un rangelimite di length//2, proprio come ho fatto io, devi cambiarlo in length//2+1se vuoi catturare stringhe che sono esattamente raddoppiate (ad es 'aabaab'.).
TigerhawkT3

E ora sono identici! \ / / Devo prestare maggiore attenzione all'ottimizzazione in futuro, ma sono contento che l'algoritmo stesso fosse solido.
TigerhawkT3,

15

Ecco una soluzione semplice, senza regex.

Per sottostringhe a spartire dall'indice di Zeroth, di lunghezze da 1 a 1 len(s), controllare se quella sottostringa substrè il motivo ripetuto. Questo controllo può essere eseguito concatenando substrcon se stesso i ratiotempi, in modo tale che la lunghezza della stringa così formata sia uguale alla lunghezza di s. Quindiratio=len(s)/len(substr) .

Ritorna quando viene trovata per la prima volta tale sottostringa. Ciò fornirebbe la sottostringa più piccola possibile, se ne esiste una.

def check_repeat(s):
    for i in range(1, len(s)):
        substr = s[:i]
        ratio = len(s)/len(substr)
        if substr * ratio == s:
            print 'Repeating on "%s"' % substr
            return
    print 'Non repeating'

>>> check_repeat('254725472547')
Repeating on "2547"
>>> check_repeat('abcdeabcdeabcdeabcde')
Repeating on "abcde"

Ora che lo guardo attentamente, sembra quasi identico alla mia soluzione originariamente pubblicata (prima di qualsiasi modifica), con le uniche differenze che riguardano il salvataggio della lunghezza e della sottostringa. Immagino di avere un algoritmo abbastanza buono. : P
TigerhawkT3

@ TigerhawkT3 Sì davvero! :)
Saksham Varma,

9

Ho iniziato con più di otto soluzioni a questo problema. Alcuni erano basati su regex (match, findall, split), alcuni su slicing e testing delle stringhe e altri con metodi string (find, count, split). Ognuno di essi aveva vantaggi in termini di chiarezza del codice, dimensioni del codice, velocità e consumo di memoria. Avevo intenzione di pubblicare la mia risposta qui quando ho notato che la velocità di esecuzione era classificata come importante, quindi ho fatto ulteriori test e miglioramenti per arrivare a questo:

def repeating(s):
    size = len(s)
    incr = size % 2 + 1
    for n in xrange(1, size//2+1, incr):
        if size % n == 0:
            if s[:n] * (size//n) == s:
                return s[:n]

Questa risposta sembra simile ad alcune altre risposte qui, ma ha alcune ottimizzazioni di velocità che altri non hanno usato:

  • xrange è un po 'più veloce in questa applicazione,
  • se una stringa di input ha una lunghezza dispari, non controllare alcuna sottostringa di lunghezza pari,
  • utilizzando s[:n]direttamente, evitiamo di creare una variabile in ogni ciclo.

Sarei interessato a vedere come si comporta nei test standard con hardware comune. Credo che sarà molto al di sotto dell'eccellente algoritmo di David Zhang nella maggior parte dei test, ma altrimenti dovrebbe essere abbastanza veloce.

Ho trovato questo problema molto controintuitivo. Le soluzioni che pensavo fossero veloci erano lente. Le soluzioni che sembravano lente erano veloci! Sembra che la creazione della stringa di Python con l'operatore moltiplica e il confronto delle stringhe siano altamente ottimizzati.


Non male affatto :-) Il benchmark funziona su Python 3.4 (in parte perché OP non specifica una versione ed è quello che tutti dovrebbero usare, e in parte perché usa il nuovo statisticsmodulo), quindi ho dovuto cambiare i tuoi /s in //s e sostituisci xrange()con range()(che si comporta come 2.x's xrange()in 3.x).
Zero Pireo,

Ecco le revisioni al benchmark, quindi puoi rivedere le mie modifiche, comunque.
Zero Pireo,

Grazie Zero. È stato veloce. I risultati sono leggermente diminuiti rispetto alle mie previsioni. Sospetto che le tecniche che ho usato per la velocità in Python 2.7 non siano molto efficaci in Python 3.4. Oh, bene - un esercizio divertente ed educativo.
Logic Knight,

//in 3.x è divisione intera (proprio come il comportamento 2.x di /), mentre 3.x /è divisione float (che sono sicuro che sarebbe molto più lenta anche se non rompesse la soluzione causando un tentativo di utilizzo un galleggiante come indice). Come accennato, 3.x range()è la stessa cosa di 2.x xrange(); non esiste un equivalente di 2.x range()in 3.x. Quindi non penso che sia la causa di eventuali discrepanze tra il benchmark e i tempi che hai effettuato. Probabilmente è solo che 3.x è complessivamente più lento di 2.x (o forse la tua macchina è più veloce della mia).
Zero Pireo,

Quando avrò del tempo, darò un'occhiata da vicino alle differenze di runtime tra Python 2 e Python 3.
Logic Knight

2

Questa funzione viene eseguita molto rapidamente (testata ed è oltre 3 volte più veloce della soluzione più veloce qui su stringhe con oltre 100k caratteri e la differenza aumenta più è lungo il modello ripetuto). Cerca di ridurre al minimo il numero di confronti necessari per ottenere la risposta:

def repeats(string):
    n = len(string)
    tried = set([])
    best = None
    nums = [i for i in  xrange(2, int(n**0.5) + 1) if n % i == 0]
    nums = [n/i for i in nums if n/i!=i] + list(reversed(nums)) + [1]
    for s in nums:
        if all(t%s for t in tried):
            print 'Trying repeating string of length:', s
            if string[:s]*(n/s)==string:
                best = s
            else:
                tried.add(s)
    if best:
        return string[:best]

Si noti che, ad esempio, per una stringa di lunghezza 8, controlla solo un frammento di dimensione 4 e non è necessario eseguire ulteriori test poiché un motivo di lunghezza 1 o 2 comporterebbe un motivo ripetuto di lunghezza 4:

>>> repeats('12345678')
Trying repeating string of length: 4
None

# for this one we need only 2 checks 
>>> repeats('1234567812345678')
Trying repeating string of length: 8
Trying repeating string of length: 4
'12345678'

Grazie :) Non l'ho ottimizzato molto però. Volevo solo presentare un approccio diverso perché altre risposte si stanno concentrando sulla ricerca del modello e il mio approccio si concentra sulla dimostrazione che non esiste un modello :) Pertanto, per stringhe casuali il mio algoritmo dovrebbe funzionare molto più velocemente.
Piotr Dabkowski il

0

Nella risposta di David Zhang se abbiamo una sorta di buffer circolare questo non funzionerà: a principal_period('6210045662100456621004566210045662100456621')causa dell'avvio 621, dove mi sarebbe piaciuto sputare:00456621 .

Estendendo la sua soluzione possiamo usare quanto segue:

def principal_period(s):
    for j in range(int(len(s)/2)):
        idx = (s[j:]+s[j:]).find(s[j:], 1, -1)
        if idx != -1:
            # Make sure that the first substring is part of pattern
            if s[:j] == s[j:][:idx][-j:]:
                break

    return None if idx == -1 else s[j:][:idx]

principal_period('6210045662100456621004566210045662100456621')
>>> '00456621'

-1

Ecco il codice in Python che verifica la ripetizione della sottostringa nella stringa principale fornita dall'utente .

print "Enter a string...."
#mainstring = String given by user
mainstring=raw_input(">")
if(mainstring==''):
    print "Invalid string"
    exit()
#charlist = Character list of mainstring
charlist=list(mainstring)
strarr=''
print "Length of your string :",len(mainstring)
for i in range(0,len(mainstring)):
    strarr=strarr+charlist[i]
    splitlist=mainstring.split(strarr)
    count = 0
    for j in splitlist:
        if j =='':
            count+=1
    if count == len(splitlist):
        break
if count == len(splitlist):
    if count == 2:
        print "No repeating Sub-String found in string %r"%(mainstring)

    else:
        print "Sub-String %r repeats in string %r"%(strarr,mainstring)
else :
    print "No repeating Sub-String found in string %r"%(mainstring)

Input :

0045662100456621004566210045662100456621

Uscita :

Lunghezza della corda: 40

La sotto-stringa '00456621' si ripete nella stringa '0045662100456621004566210045662100456621'

Input :

004608294930875576036866359447

Uscita :

Lunghezza della corda: 30

Nessuna stringa secondaria ripetuta trovata nella stringa '004608294930875576036866359447'

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.