En yakın dize eşleşmesini alma


397

Bir test dizesine birden çok dizeleri karşılaştırmak ve yakından benzeyen dize döndürmek için bir yol gerekir:

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

(Bunu doğru yaptıysam) "TEST STRING" e en yakın dize "CHOICE C" olmalıdır. Bunu yapmanın en kolay yolu nedir?

Bunu VB.net, Lua ve JavaScript de dahil olmak üzere birden çok dile uygulamayı planlıyorum. Bu noktada sözde kod kabul edilebilir. Belirli bir dil için örnek verebilirseniz, bu da takdir edilmektedir!


3
Genellikle bu tür şeyler yapan algoritmalar, incelenen bir dizeyi hedef dizeye dönüştürmek için kaç değişikliğin gerektiğini belirlemeye çalışır. Bu tür algoritmalar böyle bir durumda hiç iyi çalışmaz. Bence bir bilgisayarı bu işi halletmek çok zor olacak.
Matt Greer

3
Java, Ruby, Python, PHP, vb: Birçok dilde Levenshtein mesafesi kaynak kodu en.wikibooks.org/wiki/Algorithm_Implementation/Strings/...
joelparkerhenderson

9
Genel olarak, "en yakın karakter dizisi" olarak kabul edilen şey, kullanılan benzerlik ölçüsüne ve hizalamada boşluklar oluşturmak için kullanılan cezalara bağlı olacaktır. Örneğin, "inek" ve "tavuk" un "inek" ve "kırmızı" dan daha benzer olduğunu düşünüyor musunuz (ilgili kavramlar oldukları için) veya başka bir yol mu var? )? Ancak bir benzerlik ölçüsü ve boşluk cezası verildiğinde, aşağıdaki Levenshtein algoritmasının size en yakın dizeyi bulmayı garanti ettiği gösterilebilir. Aynısı Needleman-Wunsch ve Smith-Waterman için de geçerlidir (daha aşağıda).
Sten L

Karakter gruplaması veya kelime gruplaması yapın. Puan verin.
Casey

Yanıtlar:


952

Bu sorunla ilgili olarak, bir yıl önce çeşitli bilgi veritabanında bir petrol teçhizatı hakkında bilgi girmeye geldiğinde bu sorunla karşılaştım. Amaç, veritabanı girdisini en yaygın öğelerle tanımlayabilen bir tür bulanık dize araması yapmaktı.

Araştırmanın bir kısmı, bir dize veya cümle üzerinde başka bir dize veya cümleye dönüştürmek için kaç değişiklik yapılması gerektiğini belirleyen Levenshtein mesafe algoritmasını uygulamayı içeriyordu .

Geldiğim uygulama nispeten basitti ve iki ifadenin uzunluğunun, her bir cümle arasındaki değişikliklerin sayısının ve her kelimenin hedef girişinde bulunup bulunamayacağının ağırlıklı bir karşılaştırmasını içeriyordu.

Makale özel bir sitede, bu yüzden ilgili içeriği buraya eklemek için elimden geleni yapacağım:


Bulanık Dize Eşleme, iki kelime veya kelime öbeğinin benzerliğine dair insan benzeri bir tahmin gerçekleştirme işlemidir. Birçok durumda, birbirine en çok benzeyen kelimeleri veya ifadeleri tanımlamayı içerir. Bu makalede, bulanık dizgi eşleştirme sorununa yönelik şirket içi bir çözüm ve daha önce sıkıcı kullanıcı katılımı gerektiren görevleri otomatikleştirmemize olanak tanıyan çeşitli sorunları çözmedeki yararlılığı açıklanmaktadır.

Giriş

Meksika Körfezi Doğrulayıcı aracını geliştirirken başlangıçta bulanık dize eşleştirme yapma ihtiyacı ortaya çıktı. Var olan, Meksika petrol kuleleri ve platformlarının bilinen körfezinin bir veri tabanıydı ve sigorta satın alan insanlar bize varlıkları hakkında kötü bir şekilde yazılmış bazı bilgiler verecekti ve bunu bilinen platformların veritabanıyla eşleştirmek zorunda kaldık. Çok az bilgi verildiğinde yapabileceğimiz en iyi şey, atıfta bulundukları bilgiyi "tanımak" ve uygun bilgileri çağırmak için bir sigortacıya güvenmektir. İşte bu otomatik çözüm işe yarıyor.

Bulanık dizgi eşleştirme yöntemlerini araştırmak için bir gün geçirdim ve sonunda Wikipedia'da çok yararlı Levenshtein mesafe algoritmasına tökezledim.

uygulama

Arkasındaki teoriyi okuduktan sonra, onu optimize etmenin yollarını buldum. Kodum VBA'da şöyle görünür:

'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

Basit, hızlı ve çok kullanışlı bir metrik. Bunu kullanarak, iki dizenin benzerliğini değerlendirmek için iki ayrı metrik oluşturdum. Biri "valuePhrase", diğeri "valueWords" diyorum. valuePhrase, yalnızca iki kelime öbeği arasındaki Levenshtein mesafesidir ve valueWords, dizeyi boşluklar, tire işaretleri ve istediğiniz başka herhangi bir şey gibi sınırlayıcılara dayalı olarak tek tek sözcüklere böler ve her kelimeyi birbirleriyle en kısa şekilde karşılaştırır İki kelimeyi birleştiren Levenshtein mesafesi. Esasen, bir kelime öbeğindeki bilginin, tıpkı bir kelime bilimi permütasyonu gibi, gerçekten diğerinde bulunup bulunmadığını ölçer. Bir projeyi sınırlayıcılara dayalı bir dizeyi bölmenin mümkün olan en verimli yolunu bulmak için birkaç gün geçirdim.

valueWords, valuePhrase ve Split işlevi:

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

Benzerlik Önlemleri

Bu iki metriği ve sadece iki dize arasındaki mesafeyi hesaplayan bir üçüncü kullanarak, en fazla sayıda eşleşmeyi elde etmek için bir optimizasyon algoritması çalıştırabileceğim bir dizi değişkenim var. Bulanık dize eşleme, kendisi, bulanık bir bilimdir ve bu nedenle dize benzerliğini ölçmek için doğrusal olarak bağımsız metrikler oluşturarak ve birbirimizle eşleştirmek istediğimiz bilinen bir dizi diziye sahip olarak, belirli stillerimiz için dizeleri, en iyi bulanık maç sonuçları verir.

Başlangıçta, metriğin amacı kesin bir eşleşme için düşük bir arama değerine sahip olmak ve gittikçe daha fazla izin verilen önlemler için arama değerlerini artırmaktı. Pratik olmayan bir durumda, bu, iyi tanımlanmış bir dizi permütasyon kullanarak tanımlamak ve son formülü istendiği gibi artan arama değerleri sonuçlarına sahip olacak şekilde tasarlamak oldukça kolaydır.

Bulanık Dize Eşleştirme Permütasyonları

Yukarıdaki ekran görüntüsünde, arama terimim ve sonuç arasındaki algılanan farkımla güzel bir şekilde ölçeklendiğini hissettiğim bir şey bulmak için buluşsal yöntemimi değiştirdim. İçin kullanılan sezgisel ben Value Phraseyukarıdaki e-tabloda oldu =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2)). Levenstein mesafesinin cezasını iki "kelime öbeğinin" uzunluğundaki farkın% 80'i kadar etkili bir şekilde azaltıyordum. Bu şekilde, aynı uzunlukta "ifadeler" tam cezayı alır, ancak "ek bilgi" (daha uzun) içeren "ifadeler" dışında, yine de çoğunlukla aynı karakterleri paylaşan cezalar daha az ceza alır. Value Wordsİşlevi olduğu gibi kullandım ve son SearchValbuluşsal yöntemim şöyle tanımlandı:=MIN(D2,E2)*0.8+MAX(D2,E2)*0.2- ağırlıklı ortalama. İki skordan hangisi daha düşükse,% 80, daha yüksek skorun% 20'si ağırlıklı olmuştur. Bu sadece iyi bir eşleşme oranı elde etmek için kullanım durumuma uygun bir buluşsal yöntemdi. Bu ağırlıklar, test verileriyle en iyi eşleşme oranını elde etmek için ayarlanabilecek bir şeydir.

Bulanık Dize Eşleşen Değer İfadesi

Bulanık Dize Eşleşen Değer Kelimeleri

Gördüğünüz gibi, bulanık dize eşleme metrikleri olan son iki metrik, eşleşmesi gereken dizelere (diyagonalden aşağı) düşük puan verme eğilimindedir. Bu çok iyi.

Uygulama Bulanık eşleşmenin optimizasyonuna izin vermek için her metriği ağırlıklandırıyorum. Bu nedenle, bulanık dizgi eşleşmesinin her uygulaması parametreleri farklı şekilde ağırlıklandırabilir. Nihai puanı tanımlayan formül, metriklerin ve ağırlıklarının basit bir kombinasyonudur:

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

Bir optimizasyon algoritması kullanarak (sinir ağı en iyisi burada ayrık, çok boyutlu bir sorun olduğu için), hedef artık eşleşme sayısını en üst düzeye çıkarmaktır. Bu son ekran görüntüsünde görülebileceği gibi, her bir kümenin birbirleriyle doğru eşleşme sayısını tespit eden bir işlev oluşturdum. Bir sütun veya satır, en düşük puanın eşleştirilmesi istenen dizeye atanması durumunda bir puan alır ve en düşük puan için bir bağ varsa ve doğru eşleşen dizeler arasında doğru eşleşme varsa kısmi puan verilir. Daha sonra optimize ettim. Yeşil hücrenin geçerli satırla en iyi eşleşen sütun olduğunu ve hücrenin etrafındaki mavi kare geçerli sütuna en iyi uyan satır olduğunu görebilirsiniz. Alt köşedeki puan kabaca başarılı eşleşmelerin sayısıdır ve optimizasyon sorunumuzu en üst düzeye çıkarmak için söylediklerimiz budur.

Bulanık Dize Eşleştirme Optimize Edilmiş Metrik

Algoritma harika bir başarıydı ve çözüm parametreleri bu tür bir sorun hakkında çok şey söylüyor. Optimize edilmiş puanın 44 olduğunu ve mümkün olan en iyi puanın 48 olduğunu göreceksiniz. Sondaki 5 sütun tuzaklardır ve satır değerleriyle hiç eşleşmez. Ne kadar çok tuzak varsa, doğal olarak en iyi eşleşmeyi bulmak o kadar zor olacaktır.

Bu özel eşleştirme durumunda, dizelerin uzunluğu önemsizdir, çünkü daha uzun kelimeleri temsil eden kısaltmalar bekleriz, bu nedenle uzunluk için en uygun ağırlık -0.3'tür, yani uzunlukları değişen dizeleri cezalandırmıyoruz. Bu kısaltmaların beklentisindeki skoru azaltarak, dizenin daha kısa olması nedeniyle daha az ikame gerektiren kelime dışı eşleşmelerin yerine kısmi kelime eşleşmeleri için daha fazla alan veririz.

Weight kelimesi 1,0 iken, cümle ağırlığı yalnızca 0,5'tir, yani bir dizede eksik olan tüm kelimeleri cezalandırırız ve tüm ifadenin bozulmadan daha fazla değer vermesi anlamına gelir. Bu yararlıdır, çünkü bu dizelerin birçoğunun ortak bir kelimesi vardır (tehlike), kombinasyonun (bölge ve tehlike) korunup korunmadığı gerçekten önemli olan şeydir.

Son olarak, minimum ağırlık 10'da ve maksimum ağırlık 1'de optimize edilir. Bunun anlamı, iki puanın en iyisi (değer ifadesi ve değer kelimeleri) çok iyi değilse, maç büyük ölçüde cezalandırılır, ancak iki skorun en kötüsünü cezalandırmaz. Esasen, gerek bu koyar vurgu ya valueWord veya valuePhrase iyi bir puan, ancak ikisi birden sahip olmak. Bir nevi "alabileceğimizi al" zihniyeti.

Bu 5 ağırlığın optimize edilmiş değerinin, meydana gelen bulanık dizgi eşleşmesi hakkında söylediği şey gerçekten büyüleyici. Bulanık dize eşleşmesinin tamamen farklı pratik durumları için, bu parametreler çok farklıdır. Şimdiye kadar 3 ayrı uygulama için kullandım.

Son optimizasyonda kullanılmasa da, diyagonalden aşağıya kadar tüm mükemmel sonuçlar için sütunları kendileriyle eşleşen ve kullanıcının puanların 0'dan sapma hızını kontrol etmek ve arama ifadeleri ( teoride sonuçlarda yanlış pozitifleri dengelemek için kullanılabilir)

Bulanık Dize Eşleştirme Ölçütü

Diğer Uygulamalar

Bu çözüm, kullanıcının bir bilgisayar sisteminin mükemmel bir eşleşmenin olmadığı bir dizi dizede bir dizeyi tanımlamasını istediği her yerde kullanılabilme potansiyeline sahiptir. (Dizeler için yaklaşık bir maç vlookup gibi).


Bundan dolayı, Levenshtein mesafe algoritmasının uygulanmasıyla birlikte muhtemelen yüksek seviyeli sezgiselliğin bir kombinasyonunu (diğer tümcede bir cümlenin kelimelerini bulmak, her iki cümlenin uzunluğu, vb.) Bir arada kullanmak istediğinizdir. Çünkü hangisinin "en iyi" eşleşmeye karar vermesi sezgisel (bulanık) bir karardır - benzerliği belirlemek için bulduğunuz metrikler için bir dizi ağırlık bulmanız gerekir.

Uygun sezgisel tarama ve ağırlık kümeleriyle, karşılaştırma programınızı vereceğiniz kararları hızlı bir şekilde alırsınız.


13
Bonus: Herkes ağırlıklı sezgisel taramalarına ek metrikler eklemek istiyorsa, (yalnızca doğrusal olarak bağımsız olmayan 3 tane verdiğimden beri) - işte wikipedia'da bir liste: en.wikipedia.org/wiki/String_metric
Alain

1
S2'nin çok fazla kelimesi varsa (ve çok sayıda küçük nesne oluşturmak tercih ettiğiniz dilde engelleyici derecede yavaş değilse) bir trie işleri hızlandırabilir. Bir Trie kullanarak hızlı ve kolay Levenshtein mesafesi denemeler hakkında harika bir makale.
JanX2

1
@Alain Bu ilginç bir yaklaşım! Ben sadece senin fikrin (C ++) ile biraz oynuyorum ama bir puan, değerini anlamıyorum valuePhrase. Kodunuzda doğru görürsem, Levenshtein mesafe fonksiyonunun dönüş değeri. Nasıl oluyor da 'abcd efgh' arama tablosundaki bir çift / float değeri? Levenshtein mesafesi bir tamsayı değerdir ve kodunuzda bir kayan nokta yapan başka hesaplamalar göremiyorum. Neyi özledim?
Andreas W. Wylach

1
@ AndreasW.Wylach Büyük gözlem. Gösterdiğim VBA sadece Levenshtein mesafesini hesaplamaktı, ama elektronik =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))çizelgemde kullandığım sezgisel öyleydi. Bu yüzden levenstein mesafesinin cezasını iki "ifadenin" uzunluğundaki farkın% 80'ine indiriyordum. Bu şekilde, aynı uzunlukta "ifadeler" tam cezayı alır, ancak "ek bilgi" (daha uzun) içeren "ifadeler" dışında, yine de çoğunlukla aynı karakterleri paylaşan cezalar daha az ceza alır.
Alain

1
@Alain Soruma geri döndüğün için teşekkürler, bunu takdir ediyorum. Açıklamanız şimdi işleri daha net hale getiriyor. Bu arada, bir cümlenin jetonlarını biraz daha derinlemesine inceleyen bir value_phrase yöntemi uyguladım, bu, jeton jetonlarının, sorgu olmayan jeton dizilerinin sırası / konumlarıdır ve bir şey söz konusu olduğunda biraz daha bulanıklığı kabul eder. "abbd" ile karşılaştırıldığında "acbd" gibi. Tüm ifade_değeri puanlarının eğilimi sizinkine eşittir, ancak burada biraz daha aşağıya inin. Bir kez daha, harika bir egzersiz ve bulanık arama algoritması için bana ilham verdi!
Andreas W. Wylach

88

Bu sorun biyoinformatikte her zaman ortaya çıkıyor. Yukarıdaki kabul edilen cevap (bu arada harikaydı) biyoenformatikte Needleman-Wunsch (iki dizeyi karşılaştır) ve Smith-Waterman (daha uzun bir dizede yaklaşık bir alt dizge bul) algoritmaları olarak bilinir. Harika çalışıyorlar ve onlarca yıldır işgücü oldular.

Peki ya karşılaştırılacak bir milyon dizeniz varsa? Bu, her biri O (n * m) olan bir trilyon çift karşılaştırmasıdır! Modern DNA sekanslayıcıları , her biri yaklaşık 200 DNA "harfi" uzunluğunda olan bir milyar kısa DNA sekansı kolayca üretir . Tipik olarak, bu tür her dize için insan genomuna (3 milyar harf) en iyi eşleşmeyi bulmak istiyoruz. Açıkçası, Needleman-Wunsch algoritması ve akrabaları yapmayacaktır.

Bu sözde "uyum sorunu" aktif bir araştırma alanıdır. En popüler algoritmalar şu anda makul donanımda (örneğin, sekiz çekirdek ve 32 GB RAM) 1 milyar kısa tel ve insan genomu arasındaki hatalı eşleşmeleri bulabilmektedir.

Bu algoritmaların çoğu kısa kesin eşleşmeleri (tohumlar) hızlı bir şekilde bularak ve daha yavaş bir algoritma (örneğin Smith-Waterman) kullanarak bunları tam dizgeye genişleterek çalışır. Bu çalışmanın nedeni, sadece birkaç yakın maçla gerçekten ilgilenmemizdir, bu nedenle ortak hiçbir şeye sahip olmayan çiftlerin% 99,9 ...% 'ından kurtulmak işe yarar.

Tam eşleşmeleri bulmak, kesin olmayan eşleşmeleri bulmaya nasıl yardımcı olur ? Diyelim ki sorgu ile hedef arasında yalnızca tek bir fark var. Bu farkın, sorgunun sağ veya sol yarısında olması ve diğer yarının da tam olarak eşleşmesi gerektiğini görmek kolaydır. Bu fikir birden fazla uyumsuzluğa genişletilebilir ve Illumina DNA sıralayıcılarında yaygın olarak kullanılan ELAND algoritmasının temelini oluşturur .

Tam dize eşleştirmesi yapmak için birçok çok iyi algoritma vardır. 200 uzunluğunda bir sorgu dizesi ve 3 milyar uzunluğunda bir hedef dizesi (insan genomu) göz önüne alındığında, hedefte, sorgunun bir alt dizesiyle tam olarak eşleşen bir k uzunluğu alt dizesinin bulunduğu herhangi bir yer bulmak istiyoruz. Basit bir yaklaşım, hedefi dizine ekleyerek başlamaktır: tüm k-uzun alt dizeleri alın, bunları bir diziye koyun ve sıralayın. Ardından, sorgunun her bir k-uzun alt dizesini alın ve sıralanan dizinde arama yapın. Sıralama ve arama O (log n) zamanında yapılabilir.

Ancak depolama bir sorun olabilir. 3 milyar harfli hedefin bir endeksinin 3 milyar işaretçi ve 3 milyar k uzunluğunda kelime tutması gerekecektir. Bunu onlarca gigabayttan daha az RAM'e sığdırmak zor görünüyor. Ancak şaşırtıcı bir şekilde, Burrows-Wheeler dönüşümünü kullanarak endeksi büyük ölçüde sıkıştırabiliriz ve yine de verimli bir şekilde sorgulanabilir. İnsan genomunun bir indeksi 4 GB'den az RAM'e sığabilir. Bu fikir, Bowtie ve BWA gibi popüler dizi hizalayıcıların temelidir .

Alternatif olarak, yalnızca işaretçileri saklayan, ancak hedef dizgideki tüm soneklerin eşzamanlı bir dizinini temsil eden bir sonek dizisi kullanabiliriz (esas olarak, tüm olası k değerleri için eşzamanlı bir dizin; aynı Burrows-Wheeler dönüşümü için de geçerlidir ). Eğer 32-bit işaretçiler kullanırsak, insan genomunun bir sonek dizi indeksi 12 GB RAM alacaktır.

Yukarıdaki bağlantılar zengin bilgi ve birincil araştırma makalelerine bağlantılar içermektedir. ELAND bağlantısı, ilgili kavramları gösteren yararlı şekiller içeren bir PDF'ye gider ve ekleme ve silme işlemleriyle nasıl başa çıkılacağını gösterir.

Son olarak, bu algoritmalar temel olarak tek insan genomlarını (bir milyar kısa dizgiyi) dizme sorununu çözmüş olsa da, DNA dizileme teknolojisi Moore yasasınınkinden bile daha hızlı gelişir ve trilyon harfli veri kümelerine hızla yaklaşıyoruz. Örneğin, şu anda , her biri bir milyar harf uzunluğunda olan 10.000 omurgalı türün genomlarını sıralamak için yürütülen projeler var . Doğal olarak, veriler üzerinde çift olarak hatalı dize eşleşmesi yapmak isteyeceğiz ...


3
Gerçekten iyi halsiz. Birkaç düzeltme: Ekleri sıralamak en az O (n) alır, O (log n) değil. Ve O (log n) araması aslında pratikte çok yavaş olduğundan, normalde O (1) aramasını (q-gram indeksi) almak için ek bir tablo oluşturursunuz. Dahası, bunu neden sonek dizisinden farklı olarak ele aldığınızdan emin değilim - bu sadece ikincisinin optimizasyonu, hayır (aslında sabit bir uzunluktan daha fazlasına ihtiyacımız olmadığından sonekler yerine sabit uzunluklu ekleri sıralama).
Konrad Rudolph

1
Ayrıca, bu algoritmalar de novo sekanslaması için hala pratik değildir . İnsan genomlarının dizilişini, sadece eşlemede kullanılabilecek bir referans dizilimimiz olduğu sürece çözdüler. Ancak de novo montajı için diğer algoritmalara ihtiyaç vardır (iyi, haritalamaya dayanan bazı hizalayıcılar vardır, ancak bitişiklerin bir araya getirilmesi bütün bir 'sorun değildir). Son olarak, utanmaz fiş: lisans tezim ELAND algoritmasının ayrıntılı bir açıklamasını içerir.
Konrad Rudolph

1
Teşekkürler. Hatayı düzenledim. Sabit uzunluklu diziyi tanımlayarak başlamamın nedeni, anlaşılması kolay olmasıdır. Sonek dizilerini ve BWT'yi kavramak biraz daha zordur, ancak aslında bazen farklı k değerlerine sahip bir dizin kullanmak isteriz. Örneğin, STAR eklenmiş hizalamaları verimli bir şekilde bulmak için sonek dizileri kullanır . Bu elbette RNA'yı genomla hizalamak için kullanışlıdır.
Sten L

30

Orijinal dize olmaktan sadece 4 karakter (ve 2 siler) olduğu için B seçiminin test dizesine daha yakın olduğunu tartışıyorum. C'yi daha yakın görürsünüz çünkü hem kahverengi hem de kırmızı içerir. Bununla birlikte, daha büyük bir düzenleme mesafesi olacaktır.

İki giriş arasındaki düzenleme mesafesini ölçen Levenshtein Distance adlı bir algoritma vardır .

İşte bu algoritma için bir araç.

  1. Oranları A mesafesi 15 olarak.
  2. Seçim B'yi 6 mesafesi olarak derecelendirir.
  3. Seçim C'yi 9 mesafe olarak değerlendirir.

EDIT: Üzgünüm, levenshtein aracında dizeleri karıştırmaya devam ediyorum. Doğru cevaplar için güncellendi.


2
Tamam, sanırım bu doğru. Buna bir göz atacağım. Şahsen , oldukça yakın olduğu sürece hedefe ne kadar yakın olduğu umurumda değil . Mükemmelliğe gerek yok;) Cevabınızın sonuçlarını doğrulayana kadar sizi işaret eder :)
Freesnöw

18

Lua uygulaması, gelecek nesiller için:

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

Bu blog gönderisiyle ilgileniyor olabilirsiniz.

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

Fuzzywuzzy, string eşleşmesi için Levenshtein mesafesi gibi kolay mesafe ölçümleri sağlayan bir Python kütüphanesidir. Standart kütüphanede difflib üzerine inşa edilmiştir ve mevcutsa C uygulaması Python-levenshtein'den faydalanacaktır.

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


Bunu okuyan diğerleri için, Fuzzywuzzy aslında Alain'in harika gönderisinde birçok fikir uygular. Aslında bu fikirlerden bazılarını kullanmak istiyorsanız, başlamak için harika bir yer.
Gregory Arenius


2

Bunu bir arama motoru bağlamında veya bir veritabanına karşı ön uç bağlamında yapıyorsanız , ComplexPhraseQueryParser eklentisiyle Apache Solr gibi bir araç kullanmayı düşünebilirsiniz . Bu kombinasyon, Levenshtein mesafesine göre belirlenen sonuçlarla alaka düzeyine göre sıralanmış bir dizgi dizininde arama yapmanızı sağlar.

Gelen sorgunun bir veya daha fazla yazım hatası olabileceği geniş bir sanatçı ve şarkı başlığı koleksiyonuna karşı kullanıyoruz ve oldukça iyi çalıştı (ve koleksiyonların milyonlarca dizede olduğu dikkate alındığında oldukça hızlı).

Buna ek olarak, Solr ile JSON aracılığıyla istek üzerine indekse göre arama yapabilirsiniz, böylece baktığınız farklı diller arasındaki çözümü yeniden keşfetmeniz gerekmez.


1

Bu tür algoritmalar için çok, çok iyi bir kaynak Simmetrics: http://sourceforge.net/projects/simmetrics/

Ne yazık ki bir sürü belge içeren müthiş web sitesi gitti :( Tekrar gelmesi durumunda, önceki adresi şuydu: http://www.dcs.shef.ac.uk/~sam/simmetrics.html

Voila ("Wayback Machine" in izniyle): http://web.archive.org/web/20081230184321/http://www.dcs.shef.ac.uk/~sam/simmetrics.html

Kod kaynağını inceleyebilirsiniz, bu tür karşılaştırmalar için her biri farklı bir değişime sahip düzinelerce algoritma vardır. Uygulamalar Java biçimindedir.


1

Büyük bir metin kümesini verimli bir şekilde sorgulamak için Mesafeyi Düzenle / Önek Mesafeyi Düzenle kavramını kullanabilirsiniz.

Mesafeyi Düzenle ED (x, y): x teriminden y terimine ulaşmak için minimum transfrom sayısı

Ancak her terim ve sorgu metni arasındaki ED'yi hesaplamak kaynak ve zaman açısından zahmetlidir. Bu nedenle, her terim için ED'yi hesaplamak yerine, Qgram Dizini adı verilen bir tekniği kullanarak olası eşleşme terimlerini elde edebiliriz. ve sonra seçili terimlere ED hesaplaması uygulayın.

Qgram indeks tekniğinin bir avantajı, Bulanık Arama için desteklemesidir.

QGram endeksini uyarlamak için olası bir yaklaşım, Qgramları kullanarak Tersine Endeks oluşturmaktır. Burada, belirli Qgram ile oluşan tüm kelimeleri o Qgram altında saklıyoruz. (Tam dizeyi saklamak yerine, her dize için benzersiz kimlik kullanabilirsiniz). Bunun için Java'da Ağaç Haritası veri yapısını kullanabilirsiniz. Aşağıda terimlerin depolanmasına ilişkin küçük bir örnek verilmiştir

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

Ardından sorgulama yaparken, sorgu metni ile kullanılabilir terimler arasındaki ortak Qgram sayısını hesaplıyoruz.

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

ortak q-gram sayısı = 4.

Çok sayıda ortak Qgram içeren terimler için, ED / PED'yi sorgu terimine göre hesaplar ve ardından terimi son kullanıcıya öneririz.

bu teorinin bir uygulamasını aşağıdaki projede bulabilirsiniz (Bkz. "QGramIndex.java"). Soru sormaktan çekinmeyin. https://github.com/Bhashitha-Gamage/City_Search

Düzen Mesafe, hakkında daha çalışmak için Önek Düzenleme Mesafe Qgram endeksi aşağıdaki Prof. Dr. Hannah Bast videosunu izleyin https://www.youtube.com/embed/6pUg2wmGJRo (20:06 dan Ders başlar)


1

Girdi verileri çok büyükse problemi uygulamak zordur (milyonlarca dizeyi söyleyin). Bunu çözmek için elastik arama kullandım.

Hızlı başlangıç: https://www.elastic.co/guide/en/elasticsearch/client/net-api/6.x/elasticsearch-net.html

Tüm giriş verilerini DB'ye ekleyin ve herhangi bir dizeyi herhangi bir düzenleme mesafesine göre hızlı bir şekilde arayabilirsiniz. İşte size düzenleme mesafesine göre sıralanmış (küçükten büyüğe) bir sonuç listesi verecek bir C # snippet'i

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

Hangi kütüphaneyi kullanıyorsunuz? Bunun yardımcı olması için biraz daha bilgi gerekiyor.
bahisleri

0

Burada verilen kelimeler arasındaki mesafeleri hesaplamak için bir golang POC'ye sahip olabilirsiniz. Diğer kapsamları minDistanceve differenceiçin ayarlayabilirsiniz .

Oyun alanı: 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)

}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.