Ottenere la corrispondenza della stringa più vicina


397

Ho bisogno di un modo per confrontare più stringhe con una stringa di prova e restituire la stringa che assomiglia molto ad essa:

TEST STRING: THE BROWN FOX JUMPED OVER THE RED COW

CHOICE A   : THE RED COW JUMPED OVER THE GREEN CHICKEN
CHOICE B   : THE RED COW JUMPED OVER THE RED COW
CHOICE C   : THE RED FOX JUMPED OVER THE BROWN COW

(Se l'ho fatto correttamente) La stringa più vicina a "TEST STRING" dovrebbe essere "CHOICE C". Qual è il modo più semplice per farlo?

Ho intenzione di implementarlo in più lingue tra cui VB.net, Lua e JavaScript. A questo punto, lo pseudo codice è accettabile. Se puoi fornire un esempio per una lingua specifica, anche questo è apprezzato!


3
Gli algoritmi che in genere fanno questo tipo di cose funzionano per determinare quante modifiche sono necessarie per trasformare una stringa esaminata nella stringa di destinazione. Quei tipi di algoritmi non funzionano affatto bene in una situazione come questa. Penso che ottenere un computer per farlo sarà molto difficile.
Matt Greer,

3
Levenshtein codice sorgente distanza in molte lingue: Java, Ruby, Python, PHP, ecc en.wikibooks.org/wiki/Algorithm_Implementation/Strings/...
joelparkerhenderson

9
In generale, ciò che conta come "stringa più vicina" dipenderà dalla misura di somiglianza utilizzata e dalle penalità utilizzate per introdurre lacune nell'allineamento. Ad esempio, consideri "mucca" e "pollo" più simili a "mucca" e "rosso" (perché sono concetti correlati), oppure è il contrario (perché "pollo" ha più lettere di "mucca" )? Ma data una misura di somiglianza e penalità di gap, si può dimostrare che l'algoritmo di Levenshtein di seguito è garantito per trovare la stringa più vicina. Lo stesso vale per Needleman-Wunsch e Smith-Waterman (più sotto).
Sten L

Raggruppa i caratteri o raggruppa le parole. Dagli un punteggio.
Casey

Risposte:


952

Mi è stato presentato questo problema circa un anno fa, quando si trattava di cercare le informazioni inserite dall'utente su una piattaforma petrolifera in un database di informazioni varie. L'obiettivo era fare una sorta di ricerca di stringhe fuzzy in grado di identificare la voce del database con gli elementi più comuni.

Parte della ricerca ha riguardato l'implementazione dell'algoritmo a distanza di Levenshtein , che determina quante modifiche devono essere apportate a una stringa o frase per trasformarla in un'altra stringa o frase.

L'implementazione che mi è venuta in mente è stata relativamente semplice e ha comportato un confronto ponderato della lunghezza delle due frasi, il numero di modifiche tra ogni frase e se ogni parola potesse essere trovata nella voce di destinazione.

L'articolo è su un sito privato, quindi farò del mio meglio per aggiungere qui i contenuti pertinenti:


Fuzzy String Matching è il processo per eseguire una stima di tipo umano della somiglianza di due parole o frasi. In molti casi, comporta l'identificazione di parole o frasi che sono più simili tra loro. Questo articolo descrive una soluzione interna al problema della corrispondenza delle stringhe fuzzy e la sua utilità nel risolvere una varietà di problemi che possono permetterci di automatizzare le attività che in precedenza richiedevano un noioso coinvolgimento dell'utente.

introduzione

La necessità di eseguire la corrispondenza fuzzy delle stringhe era originariamente nata durante lo sviluppo dello strumento di convalida del Golfo del Messico. Ciò che esisteva era un database di piattaforme e piattaforme petrolifere note del Golfo del Messico, e le persone che acquistavano un'assicurazione ci avrebbero fornito alcune informazioni mal digitate sui loro beni e dovevamo abbinarli al database delle piattaforme note. Quando venivano fornite pochissime informazioni, il meglio che potevamo fare era affidarci a un sottoscrittore per "riconoscere" quello a cui si riferivano e richiamare le informazioni appropriate. È qui che questa soluzione automatizzata è utile.

Ho trascorso una giornata alla ricerca di metodi di corrispondenza fuzzy delle stringhe e alla fine mi sono imbattuto nell'utilissimo algoritmo di distanza Levenshtein su Wikipedia.

Implementazione

Dopo aver letto la teoria alla base, ho implementato e trovato il modo di ottimizzarlo. Ecco come appare il mio codice in VBA:

'Calculate the Levenshtein Distance between two strings (the number of insertions,
'deletions, and substitutions needed to transform the first string into the second)
Public Function LevenshteinDistance(ByRef S1 As String, ByVal S2 As String) As Long
    Dim L1 As Long, L2 As Long, D() As Long 'Length of input strings and distance matrix
    Dim i As Long, j As Long, cost As Long 'loop counters and cost of substitution for current letter
    Dim cI As Long, cD As Long, cS As Long 'cost of next Insertion, Deletion and Substitution
    L1 = Len(S1): L2 = Len(S2)
    ReDim D(0 To L1, 0 To L2)
    For i = 0 To L1: D(i, 0) = i: Next i
    For j = 0 To L2: D(0, j) = j: Next j

    For j = 1 To L2
        For i = 1 To L1
            cost = Abs(StrComp(Mid$(S1, i, 1), Mid$(S2, j, 1), vbTextCompare))
            cI = D(i - 1, j) + 1
            cD = D(i, j - 1) + 1
            cS = D(i - 1, j - 1) + cost
            If cI <= cD Then 'Insertion or Substitution
                If cI <= cS Then D(i, j) = cI Else D(i, j) = cS
            Else 'Deletion or Substitution
                If cD <= cS Then D(i, j) = cD Else D(i, j) = cS
            End If
        Next i
    Next j
    LevenshteinDistance = D(L1, L2)
End Function

Metrica semplice, veloce e molto utile. Usando questo, ho creato due metriche separate per valutare la somiglianza di due stringhe. Uno che chiamo "valuePhrase" e uno che chiamo "valueWords". valuePhrase è solo la distanza di Levenshtein tra le due frasi e valueWords divide la stringa in singole parole, sulla base di delimitatori come spazi, trattini e qualsiasi altra cosa tu voglia, e confronta ogni parola con un'altra, riassumendo la più breve Distanza di Levenshtein che collega due parole qualsiasi. In sostanza, misura se l'informazione in una "frase" è realmente contenuta in un'altra, proprio come una permutazione in termini di parole. Ho trascorso alcuni giorni come un progetto secondario per trovare il modo più efficiente possibile di dividere una stringa in base ai delimitatori.

valueWords, valuePhrase e funzione Split:

Public Function valuePhrase#(ByRef S1$, ByRef S2$)
    valuePhrase = LevenshteinDistance(S1, S2)
End Function

Public Function valueWords#(ByRef S1$, ByRef S2$)
    Dim wordsS1$(), wordsS2$()
    wordsS1 = SplitMultiDelims(S1, " _-")
    wordsS2 = SplitMultiDelims(S2, " _-")
    Dim word1%, word2%, thisD#, wordbest#
    Dim wordsTotal#
    For word1 = LBound(wordsS1) To UBound(wordsS1)
        wordbest = Len(S2)
        For word2 = LBound(wordsS2) To UBound(wordsS2)
            thisD = LevenshteinDistance(wordsS1(word1), wordsS2(word2))
            If thisD < wordbest Then wordbest = thisD
            If thisD = 0 Then GoTo foundbest
        Next word2
foundbest:
        wordsTotal = wordsTotal + wordbest
    Next word1
    valueWords = wordsTotal
End Function

''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
' SplitMultiDelims
' This function splits Text into an array of substrings, each substring
' delimited by any character in DelimChars. Only a single character
' may be a delimiter between two substrings, but DelimChars may
' contain any number of delimiter characters. It returns a single element
' array containing all of text if DelimChars is empty, or a 1 or greater
' element array if the Text is successfully split into substrings.
' If IgnoreConsecutiveDelimiters is true, empty array elements will not occur.
' If Limit greater than 0, the function will only split Text into 'Limit'
' array elements or less. The last element will contain the rest of Text.
''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
Function SplitMultiDelims(ByRef Text As String, ByRef DelimChars As String, _
        Optional ByVal IgnoreConsecutiveDelimiters As Boolean = False, _
        Optional ByVal Limit As Long = -1) As String()
    Dim ElemStart As Long, N As Long, M As Long, Elements As Long
    Dim lDelims As Long, lText As Long
    Dim Arr() As String

    lText = Len(Text)
    lDelims = Len(DelimChars)
    If lDelims = 0 Or lText = 0 Or Limit = 1 Then
        ReDim Arr(0 To 0)
        Arr(0) = Text
        SplitMultiDelims = Arr
        Exit Function
    End If
    ReDim Arr(0 To IIf(Limit = -1, lText - 1, Limit))

    Elements = 0: ElemStart = 1
    For N = 1 To lText
        If InStr(DelimChars, Mid(Text, N, 1)) Then
            Arr(Elements) = Mid(Text, ElemStart, N - ElemStart)
            If IgnoreConsecutiveDelimiters Then
                If Len(Arr(Elements)) > 0 Then Elements = Elements + 1
            Else
                Elements = Elements + 1
            End If
            ElemStart = N + 1
            If Elements + 1 = Limit Then Exit For
        End If
    Next N
    'Get the last token terminated by the end of the string into the array
    If ElemStart <= lText Then Arr(Elements) = Mid(Text, ElemStart)
    'Since the end of string counts as the terminating delimiter, if the last character
    'was also a delimiter, we treat the two as consecutive, and so ignore the last elemnent
    If IgnoreConsecutiveDelimiters Then If Len(Arr(Elements)) = 0 Then Elements = Elements - 1

    ReDim Preserve Arr(0 To Elements) 'Chop off unused array elements
    SplitMultiDelims = Arr
End Function

Misure di somiglianza

Utilizzando queste due metriche e una terza che calcola semplicemente la distanza tra due stringhe, ho una serie di variabili che posso eseguire un algoritmo di ottimizzazione per ottenere il maggior numero di corrispondenze. La corrispondenza delle stringhe fuzzy è, di per sé, una scienza fuzzy, e quindi creando metriche linearmente indipendenti per misurare la somiglianza delle stringhe e avendo un insieme noto di stringhe che desideriamo abbinare tra loro, possiamo trovare i parametri che, per i nostri stili specifici di stringhe, dai i migliori risultati di corrispondenza fuzzy.

Inizialmente, l'obiettivo della metrica era avere un valore di ricerca basso per una corrispondenza esatta e aumentare i valori di ricerca per misure sempre più permutate. In un caso poco pratico, questo era abbastanza facile da definire usando una serie di permutazioni ben definite e ingegnerizzare la formula finale in modo tale che avessero risultati di valori di ricerca crescenti come desiderato.

Permutazioni di corrispondenza delle stringhe fuzzy

Nello screenshot sopra, ho modificato la mia euristica per inventare qualcosa che mi è sembrato ben ridimensionato alla mia differenza percepita tra il termine di ricerca e il risultato. L'euristica che ho usato Value Phrasenel foglio di calcolo sopra era =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2)). Stavo effettivamente riducendo la penalità della distanza di Levenstein dell'80% della differenza nella lunghezza delle due "frasi". In questo modo, le "frasi" che hanno la stessa lunghezza subiscono la penalità completa, ma le "frasi" che contengono "informazioni aggiuntive" (più lunghe) ma a parte ciò condividono per lo più gli stessi personaggi subiscono una penalità ridotta. Ho usato ilValue Words funzione così com'è, e quindi la mia SearchValeuristica finale è stata definita come=MIN(D2,E2)*0.8+MAX(D2,E2)*0.2- una media ponderata. Qualunque dei due punteggi più bassi è stato pesato l'80% e il 20% del punteggio più alto. Questa era solo un'euristica adatta al mio caso d'uso per ottenere un buon tasso di corrispondenza. Questi pesi sono qualcosa che si potrebbe quindi modificare per ottenere il miglior tasso di corrispondenza con i loro dati di test.

Frase di valore corrispondente alla stringa fuzzy

Parole di valore corrispondente alla stringa fuzzy

Come puoi vedere, le ultime due metriche, che sono metriche di corrispondenza delle stringhe sfocate, hanno già una tendenza naturale a dare punteggi bassi alle stringhe che devono corrispondere (in diagonale). Questo va molto bene.

Applicazione Per consentire l'ottimizzazione della corrispondenza fuzzy, peso ogni metrica. Pertanto, ogni applicazione della corrispondenza della stringa fuzzy può ponderare i parametri in modo diverso. La formula che definisce il punteggio finale è semplicemente una combinazione delle metriche e dei loro pesi:

value = Min(phraseWeight*phraseValue, wordsWeight*wordsValue)*minWeight
      + Max(phraseWeight*phraseValue, wordsWeight*wordsValue)*maxWeight
      + lengthWeight*lengthValue

Utilizzando un algoritmo di ottimizzazione (la rete neurale è la migliore qui perché si tratta di un problema discreto e multidimensionale), l'obiettivo è ora quello di massimizzare il numero di corrispondenze. Ho creato una funzione che rileva il numero di corrispondenze corrette di ciascun set tra loro, come si può vedere in questo screenshot finale. Una colonna o riga ottiene un punto se al punteggio più basso viene assegnata la stringa che doveva essere abbinata, e vengono assegnati punti parziali se esiste un pareggio per il punteggio più basso e la corrispondenza corretta è tra le stringhe abbinate legate. L'ho quindi ottimizzato. Puoi vedere che una cella verde è la colonna che meglio corrisponde alla riga corrente e un quadrato blu attorno alla cella è la riga che meglio corrisponde alla colonna corrente. Il punteggio nell'angolo in basso è approssimativamente il numero di partite riuscite e questo è ciò che diciamo per massimizzare il nostro problema di ottimizzazione.

Metrica ottimizzata corrispondente alla stringa fuzzy

L'algoritmo è stato un successo meraviglioso e i parametri della soluzione dicono molto su questo tipo di problema. Noterai che il punteggio ottimizzato era 44 e il miglior punteggio possibile è 48. Le 5 colonne alla fine sono esche e non hanno alcuna corrispondenza con i valori delle righe. Più esche ci sono, più difficile sarà naturalmente trovare la migliore corrispondenza.

In questo particolare caso di corrispondenza, la lunghezza delle stringhe è irrilevante, poiché prevediamo abbreviazioni che rappresentano parole più lunghe, quindi il peso ottimale per la lunghezza è -0,3, il che significa che non penalizziamo le stringhe che variano di lunghezza. Riduciamo il punteggio in previsione di queste abbreviazioni, dando più spazio alle corrispondenze di parole parziali per sostituire le corrispondenze non di parole che richiedono semplicemente meno sostituzioni perché la stringa è più corta.

Il peso della parola è 1,0 mentre il peso della frase è solo 0,5, il che significa che penalizziamo parole intere mancanti da una stringa e valutiamo di più l'intera frase intatta. Questo è utile perché molte di queste stringhe hanno una parola in comune (il pericolo) dove ciò che conta davvero è se la combinazione (regione e pericolo) viene mantenuta o meno.

Infine, il peso minimo è ottimizzato a 10 e il peso massimo a 1. Ciò significa che se il migliore dei due punteggi (frase valore e parole valore) non è molto buono, la corrispondenza è fortemente penalizzata, ma noi non penalizzare notevolmente il peggio dei due punteggi. In sostanza, questo pone l'accento sul fatto che sia valueWord o valuePhrase per avere un buon punteggio, ma non entrambi. Una sorta di mentalità "prendi ciò che possiamo ottenere".

È davvero affascinante ciò che dice il valore ottimizzato di questi 5 pesi sul tipo di corrispondenza fuzzy delle stringhe in corso. Per casi pratici completamente diversi di corrispondenza delle stringhe fuzzy, questi parametri sono molto diversi. Finora l'ho usato per 3 applicazioni separate.

Sebbene non utilizzato nell'ottimizzazione finale, è stato creato un foglio di benchmark che abbina le colonne a se stessi per tutti i risultati perfetti lungo la diagonale e consente all'utente di modificare i parametri per controllare la frequenza con cui i punteggi divergono da 0 e annotare somiglianze innate tra le frasi di ricerca ( che potrebbe in teoria essere utilizzato per compensare i falsi positivi nei risultati)

Fuzzy String Matching Benchmark

Ulteriori applicazioni

Questa soluzione può essere utilizzata ovunque l'utente desideri che un sistema informatico identifichi una stringa in una serie di stringhe in cui non esiste una corrispondenza perfetta. (Come una corrispondenza approssimativa vlookup per le stringhe).


Quindi ciò che dovresti prendere da questo, è che probabilmente vuoi usare una combinazione di euristica di alto livello (trovare parole da una frase nell'altra frase, lunghezza di entrambe le frasi, ecc.) Insieme all'implementazione dell'algoritmo a distanza di Levenshtein. Perché decidere quale sia la corrispondenza "migliore" è una determinazione euristica (sfocata) - dovrai determinare un insieme di pesi per qualsiasi metrica che ti viene in mente per determinare la somiglianza.

Con il set appropriato di euristica e pesi, il tuo programma di confronto prenderà rapidamente le decisioni che avresti preso.


13
Bonus: se qualcuno vuole includere metriche aggiuntive nella sua euristica ponderata, (dato che ne ho fornite solo 3 che non erano poi così linearmente indipendenti) - ecco un intero elenco su wikipedia: en.wikipedia.org/wiki/String_metric
Alain

1
Se S2 ha molte parole (e la creazione di molti piccoli oggetti non è proibitivamente lenta nella tua lingua preferita) un trie può accelerare le cose. La distanza Levenshtein semplice e veloce con un Trie è un ottimo articolo sui tentativi.
JanX2

1
@Alain Questo è un approccio interessante! Sto solo giocando un po 'con la tua idea (in C ++) ma non capisco un punto, il valore di valuePhrase. Se vedo proprio nel tuo codice, è il valore di ritorno della funzione di distanza Levenshtein. Come mai è un valore double / float nella tabella di ricerca 'abcd efgh'? La distanza di Levenshtein è un valore intero e non riesco a vedere ulteriori calcoli nel tuo codice che lo rendono mobile. Cosa mi manca
Andreas W. Wylach,

1
@ AndreasW.Wylach Ottima osservazione. Il VBA che ho mostrato era solo per calcolare la distanza di Levenshtein, ma l'euristica che ho usato nel mio foglio di calcolo era =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))Quindi stavo riducendo la penalità della distanza di Levenstein dell'80% della differenza nella lunghezza delle due "frasi". In questo modo, le "frasi" che hanno la stessa lunghezza subiscono la penalità completa, ma le "frasi" che contengono "informazioni aggiuntive" (più lunghe) ma a parte ciò condividono ancora per lo più gli stessi personaggi subiscono una penalità ridotta.
Alain,

1
@Alain Grazie per essere tornato alla mia domanda, lo apprezzo. La tua spiegazione rende le cose più chiare ora. Nel frattempo ho implementato un metodo value_phrase che approfondisce un po 'di più l'analisi dei token di una frase, ovvero l'ordine / le posizioni dei token di frase, le sequenze di token non di query e accetta un po' più di sfocatura quando si tratta di qualcosa come "acbd" rispetto a "abcd". La tendenza dei punteggi a frase_valore è uguale alla tua, ma diventa un po 'più bassa qua e là. Ancora una volta, ottimo allenamento e mi ha dato l'ispirazione per l'algoritmo di ricerca fuzzy!
Andreas W. Wylach,

88

Questo problema si presenta continuamente in bioinformatica. La risposta accettata sopra (che era eccezionale tra l'altro) è nota in bioinformatica come algoritmi Needleman-Wunsch (confronta due stringhe) e Smith-Waterman (trova una sottostringa approssimativa in una stringa più lunga). Funzionano benissimo e sono stati cavalli di battaglia per decenni.

Ma cosa succede se hai un milione di stringhe da confrontare? Sono trilioni di confronti a coppie, ognuno dei quali è O (n * m)! I moderni sequencer del DNA generano facilmente un miliardo brevi sequenze di DNA, ciascuna lunga circa 200 "lettere" di DNA. In genere, vogliamo trovare, per ciascuna di tali stringhe, la migliore corrispondenza con il genoma umano (3 miliardi di lettere). Chiaramente, l'algoritmo Needleman-Wunsch e i suoi parenti non lo faranno.

Questo cosiddetto "problema di allineamento" è un campo di ricerca attiva. Gli algoritmi più popolari sono attualmente in grado di trovare corrispondenze inesatte tra 1 miliardo di stringhe brevi e il genoma umano nel giro di poche ore su hardware ragionevole (diciamo, otto core e 32 GB di RAM).

La maggior parte di questi algoritmi funziona trovando rapidamente corrispondenze esatte brevi (seed) e quindi estendendole all'intera stringa utilizzando un algoritmo più lento (ad esempio, Smith-Waterman). Il motivo per cui funziona è che siamo davvero interessati solo ad alcune partite ravvicinate, quindi vale la pena sbarazzarsi del 99,9 ...% delle coppie che non hanno nulla in comune.

In che modo trovare corrispondenze esatte aiuta a trovare corrispondenze inesatte ? Bene, supponiamo di consentire una sola differenza tra la query e la destinazione. È facile vedere che questa differenza deve verificarsi nella metà destra o sinistra della query e quindi l'altra metà deve corrispondere esattamente. Questa idea può essere estesa a più discrepanze ed è la base dell'ELAND dell'algoritmo comunemente usato con i sequenziatori del DNA Illumina.

Esistono molti ottimi algoritmi per eseguire la corrispondenza esatta delle stringhe. Data una stringa di query di lunghezza 200 e una stringa di destinazione di lunghezza 3 miliardi (il genoma umano), vogliamo trovare qualsiasi posto nel bersaglio in cui vi sia una sottostringa di lunghezza k che corrisponda esattamente a una sottostringa della query. Un approccio semplice è iniziare indicizzando il target: prendere tutte le sottostringhe k-long, metterle in un array e ordinarle. Quindi prendere ogni sottostringa lunga k della query e cercare l'indice ordinato. L'ordinamento e la ricerca possono essere eseguiti in O (log n) tempo.

Ma l'archiviazione può essere un problema. Un indice dell'obiettivo di 3 miliardi di lettere dovrebbe contenere 3 miliardi di puntatori e 3 miliardi di parole lunghe. Sembrerebbe difficile adattarlo in meno di alcune decine di gigabyte di RAM. Ma sorprendentemente possiamo comprimere notevolmente l'indice, usando la trasformazione Burrows-Wheeler , e sarà comunque interrogabile in modo efficiente. Un indice del genoma umano può adattarsi a meno di 4 GB di RAM. Questa idea è la base di allineatori di sequenze popolari come Bowtie e BWA .

In alternativa, possiamo usare un array di suffissi , che memorizza solo i puntatori, ma rappresenta un indice simultaneo di tutti i suffissi nella stringa di destinazione (essenzialmente, un indice simultaneo per tutti i possibili valori di k; lo stesso vale per la trasformazione di Burrows-Wheeler ). Un indice di array suffisso del genoma umano richiederà 12 GB di RAM se utilizziamo i puntatori a 32 bit.

I collegamenti sopra contengono una grande quantità di informazioni e collegamenti ai principali documenti di ricerca. Il collegamento ELAND va a un PDF con figure utili che illustrano i concetti coinvolti e mostra come gestire inserimenti ed eliminazioni.

Infine, mentre questi algoritmi hanno sostanzialmente risolto il problema del (ri) sequenziamento di singoli genomi umani (un miliardo di stringhe corte), la tecnologia di sequenziamento del DNA migliora ancora più velocemente della legge di Moore e ci stiamo avvicinando rapidamente a set di dati da trilioni di lettere. Ad esempio, ci sono attualmente progetti in corso per sequenziare i genomi di 10.000 specie di vertebrati , ciascuna lunga circa un miliardo di lettere. Naturalmente, vorremmo fare una corrispondenza stringa inesatta a coppie sui dati ...


3
Davvero malandato. Un paio di correzioni: l'ordinamento degli infissi richiede almeno O (n), non O (log n). E poiché la ricerca O (log n) in realtà è troppo lenta nella pratica, normalmente si creerebbe una tabella aggiuntiva per ottenere la ricerca O (1) (indice q-gram). Inoltre, non sono sicuro del perché lo tratti in modo diverso dall'array di suffissi: è solo un'ottimizzazione di quest'ultimo, no (ordinamento di infissi a lunghezza fissa anziché suffissi poiché in realtà non abbiamo bisogno di più di una lunghezza fissa).
Konrad Rudolph,

1
Inoltre, questi algoritmi sono ancora poco pratici per il sequenziamento de novo . Hanno risolto il sequenziamento dei genomi umani solo nella misura in cui abbiamo una sequenza di riferimento che può essere utilizzata per mappare. Ma per l'assemblaggio de novo sono necessari altri algoritmi (beh, ci sono alcuni allineatori che si basano sulla mappatura ma cucire insieme i contig è un altro problema). Infine, spudorato plug: la mia tesi di laurea contiene una descrizione dettagliata dell'algoritmo ELAND.
Konrad Rudolph,

1
Grazie. Ho eliminato l'errore. Il motivo per cui ho iniziato descrivendo l'array a lunghezza fissa è perché è facile da capire. Le matrici di suffissi e BWT sono un po 'più difficili da comprendere, ma in realtà a volte vogliamo usare un indice con valori diversi di k. Ad esempio, STAR utilizza array di suffissi per trovare in modo efficiente allineamenti giunti . Questo è ovviamente utile per allineare l'RNA al genoma.
Sten L

30

Contesto che la scelta B sia più vicina alla stringa di test, poiché sono solo 4 caratteri (e 2 eliminazioni) dall'essere la stringa originale. Considerando che C è più vicino perché include sia il marrone che il rosso. Tuttavia, avrebbe una maggiore distanza di modifica.

C'è un algoritmo chiamato Levenshtein Distance che misura la distanza di modifica tra due ingressi.

Ecco uno strumento per quell'algoritmo.

  1. Tariffa scelta A come distanza di 15.
  2. Tariffa scelta B come distanza di 6.
  3. Tariffe scelta C come distanza di 9.

EDIT: Mi dispiace, continuo a mescolare le stringhe nello strumento levenshtein. Aggiornato per correggere le risposte.


2
Ok, immagino sia vero. Darò un'occhiata a questo Personalmente non mi importa quanto sia vicino al bersaglio fintanto che è abbastanza vicino. Non c'è bisogno di perfezione;) Punti per te fino a quando non posso verificare i risultati della tua risposta :)
Freesnöw,

18

Implementazione Lua, per i posteri:

function levenshtein_distance(str1, str2)
    local len1, len2 = #str1, #str2
    local char1, char2, distance = {}, {}, {}
    str1:gsub('.', function (c) table.insert(char1, c) end)
    str2:gsub('.', function (c) table.insert(char2, c) end)
    for i = 0, len1 do distance[i] = {} end
    for i = 0, len1 do distance[i][0] = i end
    for i = 0, len2 do distance[0][i] = i end
    for i = 1, len1 do
        for j = 1, len2 do
            distance[i][j] = math.min(
                distance[i-1][j  ] + 1,
                distance[i  ][j-1] + 1,
                distance[i-1][j-1] + (char1[i] == char2[j] and 0 or 1)
                )
        end
    end
    return distance[len1][len2]
end

14

Potresti essere interessato a questo post sul blog.

http://seatgeek.com/blog/dev/fuzzywuzzy-fuzzy-string-matching-in-python

Fuzzywuzzy è una libreria Python che fornisce facili misure di distanza come la distanza di Levenshtein per la corrispondenza delle stringhe. È basato su difflib nella libreria standard e utilizzerà l'implementazione C Python-levenshtein se disponibile.

http://pypi.python.org/pypi/python-Levenshtein/


Per altri che leggono questo, Fuzzywuzzy implementa molte idee nel meraviglioso post di Alain. Se stai effettivamente cercando di utilizzare alcune di queste idee, è un ottimo punto di partenza.
Gregory Arenius,

12

Potresti trovare utile questa libreria! http://code.google.com/p/google-diff-match-patch/

È attualmente disponibile in Java, JavaScript, Dart, C ++, C #, Objective C, Lua e Python

Funziona anche abbastanza bene. Lo uso in un paio dei miei progetti Lua.

E non penso che sarebbe troppo difficile portarlo in altre lingue!


2

Se lo stai facendo nel contesto di un motore di ricerca o frontend su un database, potresti prendere in considerazione l'utilizzo di uno strumento come Apache Solr , con il plug-in ComplexPhraseQueryParser . Questa combinazione consente di cercare un indice di stringhe con i risultati ordinati per pertinenza, come determinato dalla distanza di Levenshtein.

Lo abbiamo usato contro una vasta collezione di artisti e titoli di canzoni quando la query in arrivo potrebbe avere uno o più errori di battitura, e ha funzionato abbastanza bene (e notevolmente veloce considerando che le raccolte sono in milioni di stringhe).

Inoltre, con Solr, puoi cercare l'indice su richiesta tramite JSON, quindi non dovrai reinventare la soluzione tra le diverse lingue che stai visualizzando.


1

Una risorsa molto, molto buona per questo tipo di algoritmi è Simmetrics: http://sourceforge.net/projects/simmetrics/

Sfortunatamente il fantastico sito web che contiene molta documentazione è sparito :( Nel caso in cui ritorni di nuovo, il suo indirizzo precedente era questo: http://www.dcs.shef.ac.uk/~sam/simmetrics.html

Voila (per gentile concessione di "Wayback Machine"): http://web.archive.org/web/20081230184321/http://www.dcs.shef.ac.uk/~sam/simmetrics.html

Puoi studiare l'origine del codice, ci sono dozzine di algoritmi per questo tipo di confronti, ognuno con un diverso compromesso. Le implementazioni sono in Java.


1

Per eseguire query in modo efficiente su un ampio set di testo, è possibile utilizzare il concetto di Modifica distanza / Prefisso Modifica distanza.

Modifica distanza ED (x, y): numero minimo di transfrom per passare dal termine x al termine y

Ma il calcolo dell'ED tra ogni termine e il testo della query richiede tempo e risorse. Pertanto, invece di calcolare prima ED per ogni termine, possiamo estrarre possibili termini corrispondenti usando una tecnica chiamata Qgram Index. e quindi applicare il calcolo ED su quei termini selezionati.

Un vantaggio della tecnica dell'indice Qgram è che supporta Fuzzy Search.

Un possibile approccio per adattare l'indice QGram è costruire un indice invertito usando Qgrams. Qui memorizziamo tutte le parole che consistono in un particolare Qgram, sotto quel Qgram (invece di memorizzare una stringa completa puoi usare un ID univoco per ogni stringa). A tale scopo è possibile utilizzare la struttura dei dati di Tree Map in Java. Di seguito è riportato un piccolo esempio sulla memorizzazione dei termini

col: col mbia, col ombo, gan col a, ta col ama

Quindi, durante l'interrogazione, calcoliamo il numero di Qgram comuni tra il testo dell'interrogazione e i termini disponibili.

Example: x = HILLARY, y = HILARI(query term)
Qgrams
$$HILLARY$$ -> $$H, $HI, HIL, ILL, LLA, LAR, ARY, RY$, Y$$
$$HILARI$$ -> $$H, $HI, HIL, ILA, LAR, ARI, RI$, I$$
number of q-grams in common = 4

numero di q-grammi in comune = 4.

Per i termini con un numero elevato di Qgram comuni, calcoliamo l'ED / PED rispetto al termine della query e quindi suggeriamo il termine all'utente finale.

puoi trovare un'implementazione di questa teoria nel seguente progetto (Vedi "QGramIndex.java"). Sentiti libero di porre qualsiasi domanda. https://github.com/Bhashitha-Gamage/City_Search

Per ulteriori informazioni su Modifica distanza, Prefisso Modifica indice distanza Qgram, guarda il seguente video del Prof. Dr Hannah Bast https://www.youtube.com/embed/6pUg2wmGJRo (La lezione inizia alle 20:06)


1

Il problema è difficile da implementare se i dati di input sono troppo grandi (diciamo milioni di stringhe). Ho usato la ricerca elastica per risolvere questo.

Avvio rapido: https://www.elastic.co/guide/en/elasticsearch/client/net-api/6.x/elasticsearch-net.html

Basta inserire tutti i dati di input nel DB e puoi cercare rapidamente qualsiasi stringa in base a qualsiasi distanza di modifica. Ecco uno snippet C # che ti fornirà un elenco di risultati ordinati per distanza di modifica (dal più piccolo al più alto)

var res = client.Search<ClassName>(s => s
    .Query(q => q
    .Match(m => m
        .Field(f => f.VariableName)
        .Query("SAMPLE QUERY")
        .Fuzziness(Fuzziness.EditDistance(5))
    )
));

Quale libreria stai usando? Sono necessarie alcune informazioni aggiuntive affinché ciò sia utile.
scommesse l'

0

Qui puoi avere un POC golang per calcolare le distanze tra le parole fornite. È possibile ottimizzare il minDistancee differenceper altri ambiti.

Parco giochi: https://play.golang.org/p/NtrBzLdC3rE

package main

import (
    "errors"
    "fmt"
    "log"
    "math"
    "strings"
)

var data string = `THE RED COW JUMPED OVER THE GREEN CHICKEN-THE RED COW JUMPED OVER THE RED COW-THE RED FOX JUMPED OVER THE BROWN COW`

const minDistance float64 = 2
const difference float64 = 1

type word struct {
    data    string
    letters map[rune]int
}

type words struct {
    words []word
}

// Print prettify the data present in word
func (w word) Print() {
    var (
        lenght int
        c      int
        i      int
        key    rune
    )
    fmt.Printf("Data: %s\n", w.data)
    lenght = len(w.letters) - 1
    c = 0
    for key, i = range w.letters {
        fmt.Printf("%s:%d", string(key), i)
        if c != lenght {
            fmt.Printf(" | ")
        }
        c++
    }
    fmt.Printf("\n")
}

func (ws words) fuzzySearch(data string) ([]word, error) {
    var (
        w      word
        err    error
        founds []word
    )
    w, err = initWord(data)
    if err != nil {
        log.Printf("Errors: %s\n", err.Error())
        return nil, err
    }
    // Iterating all the words
    for i := range ws.words {
        letters := ws.words[i].letters
        //
        var similar float64 = 0
        // Iterating the letters of the input data
        for key := range w.letters {
            if val, ok := letters[key]; ok {
                if math.Abs(float64(val-w.letters[key])) <= minDistance {
                    similar += float64(val)
                }
            }
        }

        lenSimilarity := math.Abs(similar - float64(len(data)-strings.Count(data, " ")))
        log.Printf("Comparing %s with %s i've found %f similar letter, with weight %f", data, ws.words[i].data, similar, lenSimilarity)
        if lenSimilarity <= difference {
            founds = append(founds, ws.words[i])
        }
    }

    if len(founds) == 0 {
        return nil, errors.New("no similar found for data: " + data)
    }

    return founds, nil
}

func initWords(data []string) []word {
    var (
        err   error
        words []word
        word  word
    )
    for i := range data {
        word, err = initWord(data[i])
        if err != nil {
            log.Printf("Error in index [%d] for data: %s", i, data[i])
        } else {
            words = append(words, word)
        }
    }
    return words

}

func initWord(data string) (word, error) {
    var word word

    word.data = data
    word.letters = make(map[rune]int)
    for _, r := range data {
        if r != 32 { // avoid to save the whitespace
            word.letters[r]++
        }

    }
    return word, nil
}
func main() {
    var ws words
    words := initWords(strings.Split(data, "-"))
    for i := range words {
        words[i].Print()
    }
    ws.words = words

    solution, _ := ws.fuzzySearch("THE BROWN FOX JUMPED OVER THE RED COW")
    fmt.Println("Possible solutions: ", solution)

}
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.