web-dev-qa-db-fra.com

Rechercher la séquence répétitive la plus longue d'une chaîne

J'ai besoin de trouver la séquence la plus longue d'une chaîne avec la mise en garde que la séquence doit être répétée trois fois ou plus. Ainsi, par exemple, si ma chaîne est:

fdwaw4helloworldvcdv1c3xcv3xcz1sda21f2sd1ahelloworldgafgfa4564534321fadghelloworld

alors je voudrais que la valeur "helloworld" soit renvoyée.

Je connais quelques façons d'accomplir cela, mais le problème auquel je suis confronté est que la chaîne réelle est absurdement grande, donc je suis vraiment à la recherche d'une méthode qui peut le faire en temps opportun.

39
Snesticle

Ce problème est une variante du problème de sous-chaîne répétée le plus long et il existe un algorithme à temps O (n) pour le résoudre qui utilise arbres de suffixes . L'idée (comme suggéré par Wikipedia) est de construire un arbre de suffixe (temps O (n)), d'annoter tous les nœuds de l'arbre avec le nombre de descendants (temps O(n) en utilisant un DFS), puis pour trouver le nœud le plus profond de l'arbre avec au moins trois descendants (temps O(n) à l'aide d'un DFS). Cet algorithme global prend le temps O (n).

Cela dit, les arbres de suffixes sont notoirement difficiles à construire, donc vous voudrez probablement trouver une bibliothèque Python qui implémente des arbres de suffixes pour vous avant d'essayer cette implémentation. Une recherche rapide sur Google se présente cette bibliothèque , mais je ne sais pas si c'est une bonne implémentation.

J'espère que cela t'aides!

31
templatetypedef

Utilisez defaultdict pour comptabiliser chaque sous-chaîne en commençant par chaque position dans la chaîne d'entrée. L'OP n'était pas clair si les correspondances superposées devaient ou non être incluses, cette méthode de force brute les inclut.

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

impressions:

('helloworld', 3)
10
PaulMcG

Commençons par la fin, comptons la fréquence et arrêtons dès que l'élément le plus fréquent apparaît 3 fois ou plus.

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)

Résultat:

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

Edit: si vous avez le sentiment que vous avez affaire à une entrée aléatoire et que la sous-chaîne commune doit être de petite longueur, vous feriez mieux de commencer (si vous avez besoin de la vitesse) avec de petites sous-chaînes et d'arrêter quand vous le pouvez n'en trouve pas qui apparaissent au moins 3 fois:

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) 

Le même résultat que ci-dessus.

3
Max Li

La première idée qui m'est venue à l'esprit est la recherche d'expressions régulières de plus en plus 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

Le code s'est exécuté avec succès. La complexité temporelle semble être au moins O (n ^ 2).

1
Matt Coughlin

Si vous inversez la chaîne d'entrée, puis alimentez-la dans une expression régulière comme (.+)(?:.*\1){2}
Il devrait vous donner la plus longue chaîne répétée 3 fois. (Groupe de capture inverse 1 pour la réponse)

Modifier:
Je dois dire annuler de cette façon. Cela dépend du premier match. À moins qu'il ne soit testé contre une longueur de devise contre une longueur maximale jusqu'à présent, dans une boucle itérative, l'expression régulière ne fonctionnera pas pour cela.

0
sln