รับการจับคู่สตริงที่ใกล้เคียงที่สุด


397

ฉันต้องการวิธีเปรียบเทียบหลาย ๆ สายกับสตริงทดสอบและคืนค่าสตริงที่มีความใกล้เคียงกับมันมากขึ้น:

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

(ถ้าฉันทำอย่างถูกต้อง) สตริงที่ใกล้เคียงกับ "TEST STRING" ควรเป็น "ทางเลือก C" วิธีที่ง่ายที่สุดในการทำเช่นนี้คืออะไร?

ฉันวางแผนที่จะใช้สิ่งนี้เป็นหลายภาษารวมถึง VB.net, Lua และ JavaScript ถึงจุดนี้โค้ดหลอกจะเป็นที่ยอมรับ หากคุณสามารถให้ตัวอย่างสำหรับภาษาเฉพาะสิ่งนี้ก็เป็นที่ชื่นชมเช่นกัน!


3
อัลกอริทึมที่โดยทั่วไปแล้วสิ่งประเภทนี้ทำงานในการกำหนดจำนวนการเปลี่ยนแปลงที่ใช้ในการเปลี่ยนสายตรวจสอบเป็นสตริงเป้าหมาย อัลกอริทึมประเภทเหล่านั้นทำงานได้ไม่ดีนักในสถานการณ์เช่นนี้ ฉันคิดว่าการใช้คอมพิวเตอร์เพื่อดึงสิ่งนี้ออกจะยากมาก
Matt Greer

3
รหัสแหล่ง Levenshtein ระยะทางในหลายภาษา: Java, Ruby, Python, PHP, ฯลฯen.wikibooks.org/wiki/Algorithm_Implementation/Strings/...
joelparkerhenderson

9
โดยทั่วไปสิ่งที่นับเป็น "สตริงที่ใกล้เคียงที่สุด" จะขึ้นอยู่กับการวัดความคล้ายคลึงกันที่ใช้และบทลงโทษที่ใช้สำหรับการแนะนำช่องว่างในการจัดตำแหน่ง ตัวอย่างเช่นคุณพิจารณาว่า "วัว" และ "ไก่" คล้ายกันมากกว่า "วัว" และ "สีแดง" (เพราะเป็นแนวคิดที่เกี่ยวข้อง) หรือเป็นวิธีอื่น ๆ (เพราะ "ไก่" มีตัวอักษรมากกว่า "วัว" )? แต่ด้วยการวัดความคล้ายคลึงกันและการปรับช่องว่างจะสามารถแสดงให้เห็นได้ว่าอัลกอริทึม Levenshtein ด้านล่างรับประกันว่าจะพบสตริงที่ใกล้ที่สุด เช่นเดียวกันกับ Needleman-Wunsch และ Smith-Waterman (เพิ่มเติมด้านล่าง)
Sten L

ทำการจัดกลุ่มตัวละครหรือการจัดกลุ่มคำ ให้คะแนน
Casey

คำตอบ:


952

ฉันถูกนำเสนอด้วยปัญหานี้เมื่อประมาณหนึ่งปีก่อนเมื่อผู้ใช้ค้นหาข้อมูลที่ป้อนเกี่ยวกับแท่นขุดเจาะน้ำมันในฐานข้อมูลข้อมูลเบ็ดเตล็ด เป้าหมายคือทำการค้นหาสตริงแบบคลุมเครือซึ่งสามารถระบุรายการฐานข้อมูลด้วยองค์ประกอบที่พบบ่อยที่สุด

ส่วนหนึ่งของงานวิจัยที่เกี่ยวข้องกับการใช้อัลกอริทึมLevenshtein distanceซึ่งกำหนดจำนวนการเปลี่ยนแปลงที่ต้องทำกับสตริงหรือวลีเพื่อเปลี่ยนเป็นสตริงหรือวลีอื่น

การนำมาใช้นั้นค่อนข้างง่ายและเกี่ยวข้องกับการเปรียบเทียบความยาวของสองวลีจำนวนการเปลี่ยนแปลงระหว่างแต่ละวลีและพบว่าแต่ละคำสามารถพบได้ในรายการเป้าหมายหรือไม่

บทความนี้อยู่ในเว็บไซต์ส่วนตัวดังนั้นฉันจะพยายามอย่างดีที่สุดเพื่อเพิ่มเนื้อหาที่เกี่ยวข้องที่นี่:


การจับคู่สตริงฟัซซีเป็นกระบวนการของการประมาณค่าความเหมือนของมนุษย์ของความคล้ายคลึงกันของสองคำหรือวลี ในหลายกรณีมันเกี่ยวข้องกับการระบุคำหรือวลีที่คล้ายกันมากที่สุด บทความนี้จะอธิบายวิธีการแก้ปัญหาภายในของปัญหาการจับคู่สตริงฟัซซีและประโยชน์ในการแก้ปัญหาที่หลากหลายซึ่งสามารถช่วยให้เราทำงานอัตโนมัติซึ่งต้องมีส่วนร่วมของผู้ใช้ที่น่าเบื่อ

บทนำ

ความจำเป็นในการจับคู่สตริงฟัซซี่นั้นเกิดขึ้นในขณะที่พัฒนาเครื่องมือตรวจสอบอ่าวเม็กซิโก สิ่งที่มีอยู่คือฐานข้อมูลอ่าวที่รู้จักของแท่นขุดเจาะน้ำมันและแท่นขุดน้ำมันของเม็กซิโกและผู้ซื้อประกันจะให้ข้อมูลที่ไม่ดีเกี่ยวกับทรัพย์สินของพวกเขาและเราต้องจับคู่กับฐานข้อมูลของแพลตฟอร์มที่รู้จัก เมื่อมีข้อมูลที่ได้รับน้อยมากสิ่งที่ดีที่สุดที่เราทำได้คือการใช้ผู้จัดจำหน่ายเพื่อ "จดจำ" สิ่งที่พวกเขาอ้างถึงและเรียกข้อมูลที่เหมาะสม นี่คือที่โซลูชันอัตโนมัตินี้มีประโยชน์

ฉันใช้เวลาหนึ่งวันในการค้นคว้าวิธีของการจับคู่สตริงฟัซซีและในที่สุดก็สะดุดกับอัลกอริทึมระยะทางที่มีประโยชน์มากของ Levenshtein ใน Wikipedia

การดำเนินงาน

หลังจากอ่านเกี่ยวกับทฤษฎีที่อยู่เบื้องหลังฉันได้นำไปใช้และพบวิธีเพิ่มประสิทธิภาพ นี่คือลักษณะรหัสของฉันใน 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

ง่ายรวดเร็วและมีประโยชน์มาก เมื่อใช้สิ่งนี้ฉันได้สร้างการวัดสองชุดแยกกันเพื่อประเมินความคล้ายคลึงกันของสองสตริง หนึ่งฉันเรียกว่า "valuePhrase" และอีกหนึ่งฉันเรียกว่า "valueWords" valuePhrase เป็นเพียงระยะทาง Levenshtein ระหว่างสองวลีและ valueWords แยกสตริงออกเป็นคำแต่ละคำตามตัวคั่นเช่นช่องว่างขีดกลางและสิ่งอื่น ๆ ที่คุณต้องการและเปรียบเทียบแต่ละคำกับคำอื่น ๆ Levenshtein ระยะทางเชื่อมต่อสองคำใด ๆ โดยพื้นฐานแล้วมันจะวัดว่าข้อมูลใน 'วลี' หนึ่งมีอยู่จริงในอีกประโยคหนึ่งหรือไม่เช่นเดียวกับการเรียงสับเปลี่ยนคำ ฉันใช้เวลาสองสามวันในการโปรเจ็กต์ด้านข้างด้วยวิธีที่มีประสิทธิภาพที่สุดในการแบ่งสตริงตามตัวคั่น

ฟังก์ชัน valueWords, valuePhrase และ 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

มาตรการความคล้ายคลึงกัน

การใช้สองเมทริกเหล่านี้และอันที่สามซึ่งคำนวณระยะห่างระหว่างสองสตริงฉันมีชุดของตัวแปรที่ฉันสามารถรันอัลกอริธึมการปรับให้เหมาะสมเพื่อให้ได้จำนวนแมตช์ที่มากที่สุด การจับคู่สตริงฟัซซี่คือตัวเองวิทยาศาสตร์ฟัซซี่และโดยการสร้างตัวชี้วัดอิสระเชิงเส้นสำหรับการวัดความคล้ายคลึงกันของสตริงและมีชุดของสตริงที่รู้จักที่เราต้องการที่จะจับคู่กันเราสามารถหาพารามิเตอร์ที่สำหรับลักษณะเฉพาะของเรา สตริงให้ผลลัพธ์การแข่งขันฟัซซี่ที่ดีที่สุด

เริ่มแรกเป้าหมายของการวัดคือการมีค่าการค้นหาต่ำสำหรับการจับคู่ที่ตรงกันและการเพิ่มมูลค่าการค้นหาสำหรับการวัดที่อนุญาตมากขึ้น ในกรณีที่ไม่สามารถปฏิบัติได้สิ่งนี้ค่อนข้างง่ายต่อการกำหนดโดยใช้ชุดการเรียงสับเปลี่ยนที่กำหนดไว้อย่างดีและกำหนดสูตรขั้นสุดท้ายเพื่อให้ได้ผลลัพธ์การค้นหาที่เพิ่มขึ้นตามที่ต้องการ

พีชคณิตการจับคู่สตริงการคลุมเครือ

ในภาพหน้าจอด้านบนฉันปรับแต่งฮิวริสติกของฉันเพื่อหาบางสิ่งที่ฉันรู้สึกว่าได้รับความแตกต่างอย่างมากจากความแตกต่างระหว่างคำค้นหาและผลลัพธ์ ผมแก้ปัญหาที่ใช้สำหรับในสเปรดชีตดังกล่าวข้างต้นเป็นValue Phrase =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))ฉันลดระยะทางของเลเวนสไตน์ลง 80% ของความแตกต่างของความยาวของ "วลี" ทั้งสอง ด้วยวิธีนี้ "วลี" ที่มีความยาวเท่ากันจะได้รับโทษเต็มรูปแบบ แต่ "วลี" ซึ่งมี 'ข้อมูลเพิ่มเติม' (นานกว่า) แต่นอกเหนือจากนั้นยังคงมีตัวละครเดียวกันที่ได้รับโทษน้อยลง ฉันใช้Value Wordsฟังก์ชั่นตามที่เป็นอยู่จากนั้นSearchValฮิวริสติกสุดท้ายของฉันก็ถูกกำหนดเป็น=MIN(D2,E2)*0.8+MAX(D2,E2)*0.2- ค่าเฉลี่ยถ่วงน้ำหนัก คะแนนใดก็ตามของทั้งสองที่ต่ำกว่านั้นมีน้ำหนัก 80% และ 20% ของคะแนนที่สูงกว่า นี่เป็นเพียงฮิวริสติกที่เหมาะกับกรณีการใช้งานของฉันเพื่อให้ได้อัตราการจับคู่ที่ดี น้ำหนักเหล่านี้เป็นสิ่งที่เราสามารถปรับแต่งเพื่อให้ได้อัตราการจับคู่ที่ดีที่สุดกับข้อมูลการทดสอบ

วลีค่าการจับคู่สตริงฟัซซี่

คำการจับคู่สตริงฟัซซี่

อย่างที่คุณเห็นตัวชี้วัดสองตัวสุดท้ายซึ่งเป็นตัวชี้วัดการจับคู่สตริงแบบคลุมเครือนั้นมีแนวโน้มที่จะให้คะแนนต่ำกับสตริงที่มีความหมายในการจับคู่ (ตามแนวทแยงมุม) อยู่แล้ว นี่เป็นสิ่งที่ดีมาก

แอปพลิเคชัน เพื่อให้การเพิ่มประสิทธิภาพของการจับคู่แบบฟัซซีฉันให้น้ำหนักแต่ละเมตริก ดังนั้นแอปพลิเคชันของการจับคู่สตริงฟัซซี่ทุกตัวสามารถให้น้ำหนักพารามิเตอร์ต่างกัน สูตรที่กำหนดคะแนนสุดท้ายคือการผสมผสานระหว่างตัวชี้วัดและน้ำหนักของพวกเขา:

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

การใช้อัลกอริธึมการปรับให้เหมาะสม (เครือข่ายประสาทดีที่สุดที่นี่เพราะมันเป็นปัญหาที่แยกกันและหลายมิติ) ตอนนี้เป้าหมายคือการเพิ่มจำนวนการแข่งขันให้ได้มากที่สุด ฉันสร้างฟังก์ชันที่ตรวจจับจำนวนการจับคู่ที่ถูกต้องของแต่ละชุดซึ่งกันและกันซึ่งสามารถเห็นได้ในสกรีนช็อตสุดท้ายนี้ คอลัมน์หรือแถวจะได้รับคะแนนถ้าคะแนนต่ำสุดถูกกำหนดให้กับสตริงที่หมายถึงการจับคู่และจะมีการให้คะแนนบางส่วนหากมีการเสมอกันสำหรับคะแนนต่ำสุดและการจับคู่ที่ถูกต้องเป็นหนึ่งในสตริงที่ตรงกัน ฉันปรับให้เหมาะสมแล้ว คุณจะเห็นว่าเซลล์สีเขียวเป็นคอลัมน์ที่ตรงกับแถวปัจจุบันมากที่สุดและสี่เหลี่ยมสีน้ำเงินรอบ ๆ เซลล์คือแถวที่ตรงกับคอลัมน์ปัจจุบันมากที่สุด คะแนนที่มุมล่างคือจำนวนการจับคู่ที่ประสบความสำเร็จโดยประมาณและนี่คือสิ่งที่เราบอกให้ปัญหาการเพิ่มประสิทธิภาพของเราเพิ่มขึ้น

การจับคู่สตริงแบบคลุมเครือการเพิ่มประสิทธิภาพ

อัลกอริทึมนั้นประสบความสำเร็จอย่างยอดเยี่ยมและพารามิเตอร์ของโซลูชันพูดได้มากมายเกี่ยวกับปัญหาประเภทนี้ คุณจะสังเกตเห็นว่าคะแนนที่ปรับให้เหมาะสมคือ 44 และคะแนนที่ดีที่สุดที่เป็นไปได้คือ 48 5 คอลัมน์สุดท้ายคือล่อและไม่มีการจับคู่ใด ๆ กับค่าแถว ยิ่งมีล่อมากเท่าไหร่ก็ยิ่งยากที่จะหาคู่ที่ดีที่สุด

ในกรณีที่จับคู่นี้ความยาวของสตริงไม่เกี่ยวข้องเนื่องจากเราคาดหวังว่าตัวย่อที่เป็นตัวแทนของคำที่ยาวกว่าดังนั้นน้ำหนักที่เหมาะสมที่สุดสำหรับความยาวคือ -0.3 ซึ่งหมายความว่าเราไม่ได้ลงโทษสตริงที่แปรผันตามความยาว เราลดความคาดหวังของตัวย่อเหล่านี้เพื่อให้มีพื้นที่มากขึ้นสำหรับการจับคู่คำบางคำเพื่อแทนที่การจับคู่ที่ไม่ใช่คำที่ต้องการการแทนที่น้อยกว่าเนื่องจากสตริงสั้นกว่า

น้ำหนักคำคือ 1.0 ในขณะที่น้ำหนักวลีเพียง 0.5 ซึ่งหมายความว่าเราลงโทษทั้งคำที่หายไปจากหนึ่งสายและค่ามากกว่าวลีทั้งหมดเป็นเหมือนเดิม สิ่งนี้มีประโยชน์เพราะสตริงเหล่านี้จำนวนมากมีหนึ่งคำที่เหมือนกัน (อันตราย) ซึ่งสิ่งที่สำคัญจริงๆก็คือการรวมกัน (ภูมิภาคและอันตราย) หรือไม่

ในที่สุดน้ำหนักขั้นต่ำจะได้รับการปรับปรุงที่ 10 และน้ำหนักสูงสุดที่ 1 สิ่งนี้หมายความว่าหากคะแนนที่ดีที่สุดของทั้งสองคะแนน (วลีมูลค่าและคำมูลค่า) ไม่ดีมากการแข่งขันจะถูกลงโทษอย่างมาก แต่เราไม่ ลงโทษอย่างรุนแรงที่สุดของคะแนนทั้งสอง หลักนี้เน้นวางอยู่บนที่กำหนดให้ทั้ง valueWord หรือ valuePhrase จะมีคะแนนดี แต่ไม่ใช่ทั้งสอง ประเภทของ "รับสิ่งที่เราจะได้รับ" ความคิด

เป็นเรื่องที่น่าสนใจจริง ๆ ที่สิ่งที่มีค่าสูงสุดของน้ำหนักทั้งห้านี้พูดเกี่ยวกับการจับคู่สายฟัซซี่ สำหรับกรณีที่แตกต่างกันโดยสิ้นเชิงของการจับคู่สตริงฟัซซีพารามิเตอร์เหล่านี้จะแตกต่างกันมาก ฉันใช้มันเพื่อแอพพลิเคชั่น 3 ตัว

ในขณะที่ไม่ได้ใช้งานในการปรับให้เหมาะสมสุดท้ายแผ่นชีทการเปรียบเทียบถูกสร้างขึ้นซึ่งจับคู่คอลัมน์กับตัวเองเพื่อให้ได้ผลลัพธ์ที่สมบูรณ์แบบในแนวทแยงมุมและให้ผู้ใช้เปลี่ยนพารามิเตอร์เพื่อควบคุมอัตราที่คะแนนแตกต่างจาก 0 และบันทึกความคล้ายคลึง ซึ่งในทางทฤษฎีสามารถใช้เพื่อชดเชยผลบวกเท็จในผลลัพธ์)

เกณฑ์มาตรฐานการจับคู่สตริง Fuzzy

การใช้งานเพิ่มเติม

โซลูชันนี้มีศักยภาพที่จะใช้ได้ทุกที่ที่ผู้ใช้ต้องการให้ระบบคอมพิวเตอร์ระบุสตริงในชุดของสตริงที่ไม่มีคู่ที่สมบูรณ์แบบ (เช่น vlookup ของการจับคู่โดยประมาณสำหรับสตริง)


ดังนั้นสิ่งที่คุณควรนำมาจากสิ่งนี้คือคุณอาจต้องการใช้การวิเคราะห์ฮิวริสติกระดับสูง (การค้นหาคำจากวลีหนึ่งในวลีอื่นความยาวของทั้งสองวลี ฯลฯ ) พร้อมกับการใช้อัลกอริทึมระยะทางของ Levenshtein เนื่องจากการตัดสินใจว่าการจับคู่แบบใด "ดีที่สุด" คือการตัดสินใจแบบฮิวริสติก (ฟัซซี่) - คุณจะต้องมีชุดน้ำหนักสำหรับตัวชี้วัดใด ๆ ที่คุณคิดขึ้นเพื่อกำหนดความคล้ายคลึงกัน

ด้วยชุดของฮิวริสติกและตุ้มน้ำหนักที่เหมาะสมคุณจะมีโปรแกรมการเปรียบเทียบของคุณในการตัดสินใจอย่างรวดเร็ว


13
โบนัส: หากใครต้องการที่จะรวมการวัดเพิ่มเติมลงใน heuristic ถ่วงน้ำหนักของพวกเขา (ตั้งแต่ฉันให้ 3 ซึ่งไม่ทั้งหมดอิสระเชิงเส้น) - นี่คือรายการทั้งหมดในวิกิพีเดีย: en.wikipedia.org/wiki/String_metric
Alain

1
หาก S2 มีคำจำนวนมาก (และการสร้างวัตถุขนาดเล็กจำนวนมากไม่ได้ จำกัด ความเร็วในภาษาที่คุณเลือก) มีความเร็วในการเร่งความเร็ว ระยะทางที่รวดเร็วและง่ายดาย Levenshtein โดยใช้ Trieเป็นบทความที่ยอดเยี่ยมเกี่ยวกับความพยายาม
JanX2

1
@Alain นี่เป็นวิธีการที่น่าสนใจ! ฉันเพียงแค่เล่น bit กับความคิดของคุณ (ใน C ++) valuePhraseแต่ไม่เข้าใจจุดหนึ่งค่าของ ถ้าฉันเห็นรหัสของคุณถูกต้องค่าส่งคืนของฟังก์ชันระยะทาง Levenshtein ทำไมจึงเป็นค่า double / float ในตารางการค้นหา 'abcd efgh' ระยะทางของ Levenshtein เป็นค่าจำนวนเต็มและฉันไม่สามารถเห็นการคำนวณเพิ่มเติมในรหัสของคุณที่ทำให้เกิดการลอย ฉันคิดถึงอะไร
Andreas W. Wylach

1
@ AndreasW.Wylach การสังเกตที่ยอดเยี่ยม VBA ที่ฉันแสดงให้เห็นนั้นเป็นเพียงการคำนวณระยะทาง Levenshtein แต่การวิเคราะห์พฤติกรรมที่ฉันใช้ในสเปรดชีตของฉันคือ=valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))ดังนั้นฉันจึงลดโทษของระยะทาง levenstein ลง 80% ของความแตกต่างของความยาวของวลีทั้งสอง ด้วยวิธีนี้ "วลี" ที่มีความยาวเท่ากันจะได้รับบทลงโทษเต็มรูปแบบ แต่ "วลี" ซึ่งมี 'ข้อมูลเพิ่มเติม' (นานกว่า) แต่นอกเหนือจากนั้น
Alain

1
@ ถึงขอบคุณสำหรับการกลับไปที่คำถามของฉันฉันขอขอบคุณ คำอธิบายของคุณทำให้สิ่งต่าง ๆ ชัดเจนขึ้น ในขณะเดียวกันฉันใช้เมธอด value_phrase ที่ลึกเข้าไปในการวิเคราะห์โทเค็นของวลีอีกเล็กน้อยนั่นคือลำดับ / ตำแหน่งของโทเค็นวลีลำดับโทเค็นที่ไม่ใช่เคียวรีและยอมรับความคลุมเครือเล็กน้อยเมื่อพูดถึงบางสิ่ง เช่น "acbd" เปรียบเทียบกับ "abcd" แนวโน้มของคะแนน Phrases_value เท่ากับคะแนนของคุณ แต่จะลดลงไปเล็กน้อย อีกครั้งการออกกำลังกายที่ยอดเยี่ยมและมันให้แรงบันดาลใจสำหรับอัลกอริทึมการค้นหาแบบคลุมเครือ!
Andreas W. Wylach

88

ปัญหานี้เกิดขึ้นตลอดเวลาในด้านชีวสารสนเทศศาสตร์ คำตอบที่ได้รับการยอมรับข้างต้น (ซึ่งยอดเยี่ยมมาก) เป็นที่รู้กันดีว่าชีวสารสนเทศศาสตร์เป็น Needleman-Wunsch (เปรียบเทียบสองสาย) และ Smith-Waterman (ค้นหาสตริงย่อยโดยประมาณในสตริงที่ยาวขึ้น) พวกเขาทำงานได้ดีและเป็นนักเขียนมานานหลายทศวรรษ

แต่ถ้าคุณมีสตริงเป็นล้านเปรียบเทียบ?นั่นคือการเปรียบเทียบจำนวนล้านล้านคู่ซึ่งแต่ละครั้งก็คือ O (n * m)! ซีเควน DNA ที่ทันสมัยสร้างลำดับดีเอ็นเอสั้น ๆได้ง่าย ๆ เป็นพันล้านเส้นยาว "ตัวอักษร" ดีเอ็นเอประมาณ 200 ตัว โดยทั่วไปแล้วเราต้องการค้นหาสตริงที่ตรงกันที่สุดสำหรับจีโนมมนุษย์ (3 พันล้านตัวอักษร) เห็นได้ชัดว่าอัลกอริทึม Needleman-Wunsch และญาติของมันจะไม่ทำ

สิ่งนี้เรียกว่า "ปัญหาการจัดตำแหน่ง" เป็นสาขาการวิจัยที่ใช้งาน อัลกอริทึมที่ได้รับความนิยมมากที่สุดในขณะนี้สามารถค้นหาการจับคู่ที่ไม่แน่นอนระหว่างสตริงสั้น 1 พันล้านกับจีโนมมนุษย์ในเวลาไม่กี่ชั่วโมงสำหรับฮาร์ดแวร์ที่เหมาะสม (พูดแปดคอร์และ 32 GB RAM)

อัลกอริทึมเหล่านี้ส่วนใหญ่ทำงานโดยค้นหาการจับคู่แบบสั้น (เมล็ด) จากนั้นขยายไปยังสตริงเต็มโดยใช้อัลกอริทึมที่ช้ากว่า (ตัวอย่างเช่น Smith-Waterman) เหตุผลที่ทำให้งานนี้เกิดขึ้นเพราะเราสนใจในการแข่งขันที่ใกล้ชิดเพียงไม่กี่ครั้งเท่านั้นดังนั้นเราจึงกำจัด 99.9 ... % ของคู่ที่ไม่มีอะไรเหมือนกัน

การค้นหาการจับคู่แบบตรงช่วยให้ค้นหาการจับคู่แบบไม่แน่นอนได้อย่างไร สมมติว่าเราอนุญาตเพียงข้อแตกต่างระหว่างแบบสอบถามและเป้าหมายเท่านั้น เป็นการง่ายที่จะเห็นว่าความแตกต่างนี้ต้องเกิดขึ้นในครึ่งทางขวาหรือซ้ายของแบบสอบถามและอีกครึ่งหนึ่งต้องตรงกันทุกประการ ความคิดนี้สามารถขยายไปหลายที่ไม่ตรงกันและเป็นพื้นฐานสำหรับอัลกอริทึมELANDที่ใช้กันทั่วไปกับลำดับซีเควนของ Illumina DNA

มีอัลกอริทึมที่ดีมากสำหรับการจับคู่สตริงที่แน่นอน ด้วยสตริงการสืบค้นที่มีความยาว 200 และสตริงเป้าหมายที่มีความยาว 3 พันล้าน (จีโนมมนุษย์) เราต้องการค้นหาตำแหน่งใด ๆ ในเป้าหมายที่มีสตริงย่อยของความยาว k ที่ตรงกับสตริงย่อยของเคียวรีอย่างแน่นอน วิธีง่าย ๆ คือการเริ่มต้นด้วยการจัดทำดัชนีเป้าหมาย: ใช้สตริงย่อย k-long ทั้งหมดวางไว้ในอาร์เรย์และจัดเรียง จากนั้นใช้สตริงย่อย k-long แต่ละอันของเคียวรีและค้นหาดัชนีที่เรียงลำดับ จัดเรียงและค้นหาสามารถทำได้ในเวลา O (log n)

แต่การจัดเก็บอาจเป็นปัญหา ดัชนีเป้าหมาย 3 พันล้านตัวอักษรนั้นจะต้องใช้พอยน์เตอร์ 3 พันล้านคำและคำยาว 3 พันล้านตัว ดูเหมือนยากที่จะใส่ลงใน RAM น้อยกว่าหลายสิบกิกะไบต์ แต่น่าประหลาดใจที่เราสามารถบีบอัดดัชนีได้อย่างมากโดยใช้การแปลง Burrows-Wheelerและมันจะยังสามารถสืบค้นได้อย่างมีประสิทธิภาพ ดัชนีของจีโนมมนุษย์สามารถมีหน่วยความจำน้อยกว่า 4 GB แนวคิดนี้เป็นพื้นฐานของเครื่องมือจัดเรียงลำดับยอดนิยมเช่นBowtieและBWA BWA

หรือเราสามารถใช้อาร์เรย์ต่อท้ายซึ่งเก็บเฉพาะพอยน์เตอร์ แต่ยังแสดงดัชนีพร้อมกันของส่วนต่อท้ายทั้งหมดในสตริงเป้าหมาย (โดยหลักแล้วดัชนีพร้อมกันสำหรับค่าที่เป็นไปได้ทั้งหมดของ k ซึ่งเป็นจริงของการแปลง Burrows-Wheeler ) ดัชนีอาเรย์ต่อท้ายของจีโนมมนุษย์จะใช้ RAM 12 GB ถ้าเราใช้พอยน์เตอร์ 32- บิต

ลิงค์ด้านบนมีข้อมูลมากมายและลิงค์ไปยังรายงานการวิจัยเบื้องต้น ลิงก์ ELAND ไปที่ PDF พร้อมตัวเลขที่มีประโยชน์ซึ่งแสดงแนวคิดที่เกี่ยวข้องและแสดงวิธีจัดการกับการแทรกและการลบ

ในที่สุดขณะที่อัลกอริธึมเหล่านี้ได้แก้ไขปัญหาของการเรียงลำดับจีโนมมนุษย์เพียงครั้งเดียว (หนึ่งพันสายสั้น ๆ ) แต่เทคโนโลยีการเรียงลำดับดีเอ็นเอช่วยปรับปรุงเร็วกว่ากฎของมัวร์และเรากำลังใกล้ชุดข้อมูลตัวอักษรล้านล้านอย่างรวดเร็ว ตัวอย่างเช่นขณะนี้มีโครงการกำลังดำเนินการเพื่อจัดลำดับจีโนมของสัตว์มีกระดูกสันหลัง 10,000 ชนิดแต่ละตัวมีความยาวหนึ่งล้านตัวอักษรหรือมากกว่านั้น โดยธรรมชาติแล้วเราจะต้องการจับคู่สตริงที่ไม่แน่นอนกับข้อมูล ...


3
วิ่งลงได้ดีจริงๆ การแก้ไขสองขั้นตอน: การเรียงลำดับ infixes ต้องใช้ O (n) อย่างน้อยไม่ใช่ O (log n) และเนื่องจากในทางปฏิบัติการค้นหา O (log n) ช้าเกินไปคุณควรสร้างตารางเพิ่มเติมเพื่อรับการค้นหา O (1) (ดัชนี q-gram) นอกจากนี้ฉันไม่แน่ใจว่าทำไมคุณปฏิบัติต่อสิ่งนี้แตกต่างจากอาร์เรย์ส่วนต่อท้าย - เป็นเพียงการเพิ่มประสิทธิภาพของส่วนหลังเท่านั้นไม่มี (การเรียงลำดับความยาวคงที่แบบคงที่แทนที่จะเป็นส่วนต่อท้ายเนื่องจากเราไม่ต้องการมากกว่าความยาวคงที่)
Konrad Rudolph

1
นอกจากนี้อัลกอริทึมเหล่านี้ยังคงใช้การไม่ได้สำหรับการหาลำดับเดอโนโว พวกเขาได้แก้ไขลำดับของจีโนมมนุษย์เพียงตราบเท่าที่เรามีลำดับการอ้างอิงที่สามารถใช้ในการทำแผนที่กับ แต่สำหรับเดอโนโวแอสเซมบลีจำเป็นต้องใช้อัลกอริธึมอื่น ๆ (เช่นกันมีผู้จัดตำแหน่งบางส่วนที่ยึดตามการทำแผนที่ ในที่สุดปลั๊กไร้ยางอาย: วิทยานิพนธ์ระดับปริญญาตรีของฉันมีคำอธิบายโดยละเอียดของอัลกอริทึม ELAND
Konrad Rudolph

1
ขอบคุณ ฉันแก้ไขข้อผิดพลาด เหตุผลที่ฉันเริ่มด้วยการอธิบายอาเรย์ที่มีความยาวคงที่นั้นเป็นเพราะมันเข้าใจง่าย Suffix arrays และ BWT นั้นยากกว่าที่จะเข้าใจ แต่จริงๆแล้วบางครั้งเราต้องการใช้ดัชนีที่มีค่าต่างกัน ตัวอย่างเช่นSTARใช้อาร์เรย์ต่อท้ายเพื่อค้นหาการจัดแนวแบบต่อเชื่อมได้อย่างมีประสิทธิภาพ แน่นอนว่ามีประโยชน์สำหรับการจัดแนว RNA กับจีโนม
Sten L

30

ฉันประกวดว่าตัวเลือก B นั้นใกล้กับสตริงทดสอบเนื่องจากมีเพียง 4 ตัวอักษร (และ 2 ลบ) จากการเป็นสตริงดั้งเดิม โดยที่คุณเห็น C ใกล้เคียงเพราะมีทั้งสีน้ำตาลและสีแดง อย่างไรก็ตามมันจะมีระยะแก้ไขที่มากกว่า

มีอัลกอริธึมที่เรียกว่าLevenshtein Distanceซึ่งวัดระยะทางแก้ไขระหว่างสองอินพุต

นี่คือเครื่องมือสำหรับอัลกอริทึมนั้น

  1. อัตราทางเลือก A เป็นระยะทาง 15
  2. ราคาทางเลือก B เป็นระยะทาง 6
  3. ราคาทางเลือก C เป็นระยะทาง 9

แก้ไข: ขออภัยฉันผสมสตริงในเครื่องมือ levenshtein ต่อไป อัปเดตเพื่อแก้ไขคำตอบ


2
ตกลงฉันเดาว่าเป็นเรื่องจริง ฉันจะดูที่นี้ ฉันเองไม่สนใจว่ามันจะใกล้เป้าหมายแค่ไหนตราบใดที่ใกล้สวย ไม่จำเป็นต้องมีความสมบูรณ์แบบ;) คะแนนสำหรับคุณจนกว่าฉันจะสามารถตรวจสอบผลลัพธ์ของคำตอบของคุณได้ :)
Freesnöw

18

การดำเนินการ Lua สำหรับลูกหลาน:

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

คุณอาจสนใจโพสต์บล็อกนี้

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

Fuzzywuzzy เป็นห้องสมุด Python ที่ให้การวัดระยะทางได้ง่ายเช่นระยะทาง Levenshtein สำหรับการจับคู่สตริง มันถูกสร้างขึ้นบน difflib ในไลบรารีมาตรฐานและจะใช้ประโยชน์จากการใช้งาน C Python-levenshtein ถ้ามี

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


สำหรับคนอื่นที่อ่านบทความนี้จริง ๆ แล้ว Fuzzywuzzy ใช้ความคิดมากมายในโพสต์ที่ยอดเยี่ยมของ Alain หากคุณต้องการใช้ความคิดเหล่านี้เป็นจุดเริ่มต้นที่ดี
Gregory Arenius

12

คุณอาจพบว่าห้องสมุดนี้มีประโยชน์! http://code.google.com/p/google-diff-match-patch/

ปัจจุบันมีให้บริการใน Java, JavaScript, Dart, C ++, C #, Objective C, Lua และ Python

มันใช้งานได้ค่อนข้างดีเช่นกัน ฉันใช้มันในโครงการ Lua ของฉันสองสามครั้ง

และฉันไม่คิดว่ามันจะยากเกินไปที่จะส่งไปยังภาษาอื่น!


2

หากคุณกำลังทำสิ่งนี้ในบริบทของเครื่องมือค้นหาหรือส่วนหน้ากับฐานข้อมูลคุณอาจลองใช้เครื่องมือเช่นApache Solrด้วยComplexPhraseQueryParserปลั๊กอินชุดค่าผสมนี้ช่วยให้คุณค้นหากับดัชนีของสตริงด้วยผลลัพธ์ที่เรียงตามความเกี่ยวข้องตามที่กำหนดโดยระยะทางของ Levenshtein

เราได้ใช้มันกับคอลเลกชันขนาดใหญ่ของศิลปินและชื่อเพลงเมื่อข้อความค้นหาที่เข้ามาอาจมีความผิดพลาดอย่างน้อยหนึ่งรายการและมันใช้งานได้ค่อนข้างดี (และน่าทึ่งมากเมื่อพิจารณาว่า

นอกจากนี้ด้วย Solr คุณสามารถค้นหากับดัชนีตามต้องการผ่าน JSON ดังนั้นคุณไม่จำเป็นต้องคิดค้นวิธีการแก้ปัญหาระหว่างภาษาต่าง ๆ ที่คุณกำลังดู


1

แหล่งข้อมูลที่ดีมากสำหรับอัลกอริทึมประเภทนี้คือ Simmetrics: http://sourceforge.net/projects/simmetrics/

น่าเสียดายที่เว็บไซต์ที่ยอดเยี่ยมที่มีเอกสารจำนวนมากหายไป :( ในกรณีที่มันกลับมาอีกครั้งที่อยู่เดิมของมันคือ: http://www.dcs.shef.ac.uk/~sam/simmetrics.html

Voila (มารยาทของ "Wayback Machine"): http://web.archive.org/web/20081230184321/http://www.dcs.shef.ac.uk/~sam/simmetrics.html

คุณสามารถศึกษาซอร์สโค้ดซึ่งมีอัลกอริทึมมากมายสำหรับการเปรียบเทียบประเภทเหล่านี้แต่ละอันมีการแลกเปลี่ยนที่แตกต่างกัน การใช้งานอยู่ใน Java


1

ในการสืบค้นข้อความจำนวนมากอย่างมีประสิทธิภาพคุณสามารถใช้แนวคิดของ Edit Distance / Prefix Edit Distance

แก้ไข Distance ED (x, y): จำนวน transfroms น้อยที่สุดเพื่อรับจากคำว่า x ถึงคำ y

แต่การคำนวณ ED ระหว่างแต่ละคำและข้อความค้นหาเป็นทรัพยากรและใช้เวลามาก ดังนั้นแทนที่จะคำนวณ ED สำหรับแต่ละคำศัพท์ก่อนเราจึงสามารถแยกคำที่ตรงกันที่เป็นไปได้โดยใช้เทคนิคที่เรียกว่าดัชนี Qgram จากนั้นใช้การคำนวณ ED กับคำที่เลือก

ข้อได้เปรียบของเทคนิคดัชนี Qgram คือรองรับการค้นหาแบบคลุมเครือ

วิธีหนึ่งที่เป็นไปได้ในการปรับดัชนี QGram คือสร้าง Inverted Index โดยใช้ Qgrams ในนั้นเราเก็บคำทั้งหมดซึ่งประกอบด้วย Qgram เฉพาะไว้ใต้ Qgram นั้น (แทนที่จะเก็บสตริงเต็มคุณสามารถใช้ ID เฉพาะสำหรับแต่ละสตริงได้) คุณสามารถใช้โครงสร้างข้อมูลแผนผังต้นไม้ใน Java สำหรับสิ่งนี้ ต่อไปนี้เป็นตัวอย่างเล็ก ๆ ในการจัดเก็บคำศัพท์

คอลัมน์: คอลัมน์ฟาน, เทือกเขา Ombo กานเทือกเขาที่ตาเทือกเขาอะ

จากนั้นเมื่อทำการสืบค้นเราจะคำนวณจำนวน Qgrams ทั่วไประหว่างข้อความค้นหาและคำที่มีให้

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

จำนวนของคิว -g ร่วมกัน = 4

สำหรับคำศัพท์ที่มี Qgrams ทั่วไปจำนวนมากเราจะคำนวณ ED / PED เทียบกับคำสืบค้นแล้วจึงแนะนำคำดังกล่าวให้กับผู้ใช้ปลายทาง

คุณสามารถค้นหาการนำทฤษฎีนี้ไปใช้ในโครงการต่อไปนี้ (ดูที่ "QGramIndex.java") อย่าลังเลที่จะถามคำถามใด ๆ https://github.com/Bhashitha-Gamage/City_Search

หากต้องการศึกษาเพิ่มเติมเกี่ยวกับการแก้ไขระยะทางคำนำหน้าแก้ไขระยะทางดัชนี Qgram โปรดดูวิดีโอต่อไปนี้ของศาสตราจารย์ดร. ฮันนาห์บาสต์https://www.youtube.com/embed/6pUg2wmGJRo (บทเรียนเริ่มจาก 20:06)


1

ปัญหายากที่จะนำไปใช้หากข้อมูลอินพุตมีขนาดใหญ่เกินไป (พูดสตริงนับล้าน) ฉันใช้การค้นหาแบบยืดหยุ่นเพื่อแก้ปัญหานี้

เริ่มต้นอย่างรวดเร็ว: https://www.elastic.co/guide/en/elasticsearch/client/net-api/6.x/elasticsearch-net.html

เพียงแค่แทรกข้อมูลอินพุตทั้งหมดลงใน DB และคุณสามารถค้นหาสตริงใด ๆ ตามระยะทางแก้ไขใด ๆ ได้อย่างรวดเร็ว นี่คือข้อมูลโค้ด C # ซึ่งจะให้รายการผลลัพธ์เรียงตามระยะทางแก้ไข (เล็กไปถึงสูงกว่า)

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

คุณใช้ห้องสมุดอะไร ต้องการข้อมูลเพิ่มเติมเพื่อให้มีประโยชน์
เดิมพัน

0

ที่นี่คุณสามารถมี POC ของ golang สำหรับคำนวณระยะทางระหว่างคำที่กำหนด คุณสามารถปรับminDistanceและdifferenceสำหรับขอบเขตอื่น ๆ

สนามเด็กเล่น: 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)

}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.