web-dev-qa-db-fra.com

Vérification de la sous-chaîne floue / approximative existant dans une chaîne plus longue, en Python?

En utilisant des algorithmes comme leveinstein (leveinstein ou difflib), il est facile de trouver des correspondances approximatives.

>>> import difflib
>>> difflib.SequenceMatcher(None,"amazing","amaging").ratio()
0.8571428571428571

Les correspondances floues peuvent être détectées en décidant d'un seuil selon les besoins.

Exigence actuelle: trouver une sous-chaîne floue basée sur un seuil dans une chaîne plus grande.

par exemple.

large_string = "thelargemanhatanproject is a great project in themanhattincity"
query_string = "manhattan"
#result = "manhatan","manhattin" and their indexes in large_string

Une solution de force brute consiste à générer toutes les sous-chaînes de longueur N-1 à N + 1 (ou toute autre longueur correspondante), où N est la longueur de query_string, et d'utiliser levenstein sur elles une par une et de voir le seuil.

Y a-t-il une meilleure solution disponible dans python, de préférence un module inclus dans python 2.7, ou un module disponible en externe).

UPDATE : Python regex fonctionne plutôt bien, bien qu'il soit un peu plus lent que le module re intégré) pour les cas de sous-chaînes floues, ce qui est un résultat évident en raison d'opérations supplémentaires. La sortie souhaitée est bonne et le contrôle de l'ampleur du flou peut être facilement défini.

>>> import regex
>>> input = "Monalisa was painted by Leonrdo da Vinchi"
>>> regex.search(r'\b(leonardo){e<3}\s+(da)\s+(vinci){e<2}\b',input,flags=regex.IGNORECASE)
<regex.Match object; span=(23, 41), match=' Leonrdo da Vinchi', fuzzy_counts=(0, 2, 1)>
42
DhruvPathak

La nouvelle bibliothèque regex qui devrait bientôt remplacer re inclut une correspondance floue.

https://pypi.python.org/pypi/regex/

La syntaxe de correspondance floue semble assez expressive, mais cela vous donnerait une correspondance avec une ou plusieurs insertions/ajouts/suppressions.

import regex
regex.match('(amazing){e<=1}', 'amaging')
14
mgbelisle

Que diriez-vous d'utiliser difflib.SequenceMatcher.get_matching_blocks?

>>> import difflib
>>> large_string = "thelargemanhatanproject"
>>> query_string = "manhattan"
>>> s = difflib.SequenceMatcher(None, large_string, query_string)
>>> sum(n for i,j,n in s.get_matching_blocks()) / float(len(query_string))
0.8888888888888888

>>> query_string = "banana"
>>> s = difflib.SequenceMatcher(None, large_string, query_string)
>>> sum(n for i,j,n in s.get_matching_blocks()) / float(len(query_string))
0.6666666666666666

MISE À JOUR

import difflib

def matches(large_string, query_string, threshold):
    words = large_string.split()
    for Word in words:
        s = difflib.SequenceMatcher(None, Word, query_string)
        match = ''.join(Word[i:i+n] for i, j, n in s.get_matching_blocks() if n)
        if len(match) / float(len(query_string)) >= threshold:
            yield match

large_string = "thelargemanhatanproject is a great project in themanhattincity"
query_string = "manhattan"
print list(matches(large_string, query_string, 0.8))

Impression du code ci-dessus: ['manhatan', 'manhattn']

19
falsetru

J'utilise fuzzywuzzy pour une correspondance floue basée sur le seuil et fuzzysearch pour extraire des mots flous de la correspondance.

process.extractBests prend une requête, une liste de mots et un score de coupure et renvoie une liste de tuples de correspondance et de score au-dessus du score de coupure.

find_near_matches prend le résultat de process.extractBests et renvoie les indices de début et de fin des mots. J'utilise les indices pour construire les mots et j'utilise le mot construit pour trouver l'index dans la grande chaîne. max_l_dist de find_near_matches est la "distance de Levenshtein" qui doit être ajustée en fonction des besoins.

from fuzzysearch import find_near_matches
from fuzzywuzzy import process

large_string = "thelargemanhatanproject is a great project in themanhattincity"
query_string = "manhattan"

def fuzzy_extract(qs, ls, threshold):
    '''fuzzy matches 'qs' in 'ls' and returns list of 
    tuples of (Word,index)
    '''
    for Word, _ in process.extractBests(qs, (ls,), score_cutoff=threshold):
        print('Word {}'.format(Word))
        for match in find_near_matches(qs, Word, max_l_dist=1):
            match = Word[match.start:match.end]
            print('match {}'.format(match))
            index = ls.find(match)
            yield (match, index)

Tester;

print('query: {}\nstring: {}'.format(query_string, large_string))
for match,index in fuzzy_extract(query_string, large_string, 70):
    print('match: {}\nindex: {}'.format(match, index))

query_string = "citi"
print('query: {}\nstring: {}'.format(query_string, large_string))
for match,index in fuzzy_extract(query_string, large_string, 30):
    print('match: {}\nindex: {}'.format(match, index))

query_string = "greet"
print('query: {}\nstring: {}'.format(query_string, large_string))
for match,index in fuzzy_extract(query_string, large_string, 30):
    print('match: {}\nindex: {}'.format(match, index))

Production;
requête: manhattan
string: theargarghathatanproject is a great project in themanhattincity
match: manhatan
index: 8
match: manhattin
index: 49

requête: citi
string: theargarghathatanproject is a great project in themanhattincity
correspondance: ville
index: 58

requête: saluer
string: theargarghathatanproject is a great project in themanhattincity
match: super
index: 29

15
Nizam Mohamed

Récemment, j'ai écrit une bibliothèque d'alignement pour Python: https://github.com/eseraygun/python-alignment

En l'utilisant, vous pouvez effectuer des alignements globaux et locaux avec des stratégies de notation arbitraires sur n'importe quelle paire de séquences. En fait, dans votre cas, vous avez besoin d'alignements semi-locaux car vous ne vous souciez pas des sous-chaînes de query_string. J'ai simulé un algorithme semi-local en utilisant l'alignement local et quelques heuristiques dans le code suivant, mais il est facile d'étendre la bibliothèque pour une implémentation correcte.

Voici l'exemple de code dans le fichier README modifié pour votre cas.

from alignment.sequence import Sequence, GAP_ELEMENT
from alignment.vocabulary import Vocabulary
from alignment.sequencealigner import SimpleScoring, LocalSequenceAligner

large_string = "thelargemanhatanproject is a great project in themanhattincity"
query_string = "manhattan"

# Create sequences to be aligned.
a = Sequence(large_string)
b = Sequence(query_string)

# Create a vocabulary and encode the sequences.
v = Vocabulary()
aEncoded = v.encodeSequence(a)
bEncoded = v.encodeSequence(b)

# Create a scoring and align the sequences using local aligner.
scoring = SimpleScoring(1, -1)
aligner = LocalSequenceAligner(scoring, -1, minScore=5)
score, encodeds = aligner.align(aEncoded, bEncoded, backtrace=True)

# Iterate over optimal alignments and print them.
for encoded in encodeds:
    alignment = v.decodeSequenceAlignment(encoded)

    # Simulate a semi-local alignment.
    if len(filter(lambda e: e != GAP_ELEMENT, alignment.second)) != len(b):
        continue
    if alignment.first[0] == GAP_ELEMENT or alignment.first[-1] == GAP_ELEMENT:
        continue
    if alignment.second[0] == GAP_ELEMENT or alignment.second[-1] == GAP_ELEMENT:
        continue

    print alignment
    print 'Alignment score:', alignment.score
    print 'Percent identity:', alignment.percentIdentity()
    print

La sortie pour minScore=5 Est la suivante.

m a n h a - t a n
m a n h a t t a n
Alignment score: 7
Percent identity: 88.8888888889

m a n h a t t - i
m a n h a t t a n
Alignment score: 5
Percent identity: 77.7777777778

m a n h a t t i n
m a n h a t t a n
Alignment score: 7
Percent identity: 88.8888888889

Si vous supprimez l'argument minScore, vous n'obtiendrez que les meilleures correspondances.

m a n h a - t a n
m a n h a t t a n
Alignment score: 7
Percent identity: 88.8888888889

m a n h a t t i n
m a n h a t t a n
Alignment score: 7
Percent identity: 88.8888888889

Notez que tous les algorithmes de la bibliothèque ont O(n * m) complexité temporelle, n et m étant la longueur des séquences.

12
Eser Aygün

Les approches ci-dessus sont bonnes, mais j'avais besoin de trouver une petite aiguille dans beaucoup de foin, et j'ai fini par l'approcher comme ceci:

from difflib import SequenceMatcher as SM
from nltk.util import ngrams
import codecs

needle = "this is the string we want to find"
hay    = "text text lots of text and more and more this string is the one we wanted to find and here is some more and even more still"

needle_length  = len(needle.split())
max_sim_val    = 0
max_sim_string = u""

for ngram in ngrams(hay.split(), needle_length + int(.2*needle_length)):
    hay_ngram = u" ".join(ngram)
    similarity = SM(None, hay_ngram, needle).ratio() 
    if similarity > max_sim_val:
        max_sim_val = similarity
        max_sim_string = hay_ngram

print max_sim_val, max_sim_string

Rendements:

0.72972972973 this string is the one we wanted to find
9
duhaime