가장 가까운 문자열 일치


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"에 가장 가까운 문자열은 "CHOICE C"여야합니다. 가장 쉬운 방법은 무엇입니까?

VB.net, Lua 및 JavaScript를 포함한 여러 언어로이를 구현할 계획입니다. 이 시점에서 의사 코드가 허용됩니다. 특정 언어에 대한 예를 제공 할 수 있다면 이것 또한 감사합니다!


3
일반적으로 이러한 유형의 작업을 수행하는 알고리즘은 검사 된 문자열을 대상 문자열로 변환하는 데 걸리는 변경 횟수를 결정하는 데 사용됩니다. 이러한 유형의 알고리즘은 이와 같은 상황에서 전혀 작동하지 않습니다. 컴퓨터를 꺼내는 것이 매우 힘들 것 같습니다.
매트 그리어

3
자바, 루비, 파이썬, PHP 등 : 여러 언어 Levenshtein 거리 소스 코드 en.wikibooks.org/wiki/Algorithm_Implementation/Strings/...
joelparkerhenderson

9
일반적으로 "가장 가까운 문자열"로 간주되는 것은 사용 된 유사성 측정 값과 정렬에 간격을 도입하는 데 사용되는 처벌에 따라 달라집니다. 예를 들어, "cow"및 "chicken"이 "cow"및 "red"(유사한 개념이기 때문에)와 더 유사하다고 생각합니까? 아니면 "chicken"이 "cow"보다 글자가 많기 때문에 다른 방법입니까? )? 그러나 유사성 측정과 갭 페널티가 주어지면 아래의 Levenshtein 알고리즘이 가장 가까운 문자열을 찾을 수 있음을 알 수 있습니다. Needleman-Wunsch와 Smith-Waterman (아래에 있음)도 마찬가지입니다.
Sten L

문자 그룹화 또는 단어 그룹화를 수행하십시오. 점수를 줘.
Casey

답변:


952

약 1 년 전에 사용자가 기타 정보 데이터베이스에 석유 굴착 장치에 대한 정보를 입력했을 때이 문제가 발생했습니다. 목표는 가장 일반적인 요소로 데이터베이스 항목을 식별 할 수있는 일종의 퍼지 문자열 검색을 수행하는 것이 었습니다.

연구의 일환으로 레 벤슈 테인 거리 알고리즘을 구현하는 것이 포함 되었는데,이 알고리즘은 문자열이나 구를 다른 문자열이나 구로 바꾸는 횟수를 결정합니다.

내가 생각해 낸 구현은 비교적 간단했으며 두 구의 길이, 각 구 사이의 변경 횟수 및 각 단어를 대상 항목에서 찾을 수 있는지 여부를 가중 비교했습니다.

이 기사는 개인 사이트에 있으므로 여기에 관련 내용을 추가하기 위해 최선을 다하겠습니다.


퍼지 문자열 일치는 두 단어 나 구의 유사성을 인간과 같은 방식으로 평가하는 프로세스입니다. 많은 경우에, 그것은 서로 가장 유사한 단어 나 문구를 식별하는 것을 포함합니다. 이 기사에서는 퍼지 문자열 일치 문제에 대한 사내 솔루션과 이전에 지루한 사용자 참여가 필요했던 작업을 자동화 할 수있는 다양한 문제를 해결하는 데 유용한 점에 대해 설명합니다.

소개

퍼지 문자열 일치는 원래 멕시코만 검사기 도구를 개발하는 동안 시작되었습니다. 기존의 멕시코 석유 굴착 장치 및 플랫폼의 데이터베이스가 존재했으며 보험을 구매하는 사람들은 자산에 대한 정보를 잘못 입력했으며 알려진 플랫폼의 데이터베이스와 일치시켜야했습니다. 정보가 거의 없었을 때, 우리가 할 수있는 최선의 방법은 보험업자가 자신이 언급 한 정보를 "인식"하고 적절한 정보를 불러오는 것입니다. 이 자동화 된 솔루션이 유용합니다.

나는 퍼지 문자열 매칭 방법을 연구하면서 하루를 보냈고 결국 Wikipedia에서 매우 유용한 Levenshtein 거리 알고리즘을 발견했습니다.

이행

그 배후의 이론에 대해 읽은 후, 나는 그것을 최적화하는 방법을 구현하고 발견했습니다. 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는 공백, 대시 및 기타 원하는 항목과 같은 구분 기호를 기반으로 문자열을 개별 단어로 나누고 각 단어를 서로 단어와 비교하여 가장 짧은 단어를 합칩니다. 두 단어를 연결하는 레 벤슈 테인 거리. 본질적으로 한 단어 문구의 정보가 단어 단위 순열과 같이 다른 문구에 실제로 포함되어 있는지 여부를 측정합니다. 구분 기호를 기반으로 문자열을 분할하는 가장 효율적인 방법을 제안하는 부수 프로젝트로 며칠을 보냈습니다.

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 좋은 점수 있지만 모두 가지고 있습니다. 일종의 "우리가 얻을 수있는 것을 가져라"정신.

퍼지 스트링 매칭의 종류에 대해이 5 가지 가중치의 최적화 된 값이 말하는 것은 정말 매력적입니다. 퍼지 문자열 일치의 완전히 다른 실제 사례의 경우 이러한 매개 변수는 매우 다릅니다. 지금까지 3 가지 응용 프로그램에 사용했습니다.

최종 최적화에서 사용되지는 않았지만 벤치마킹 시트가 확립되어 대각선 아래로 모든 완벽한 결과를 얻기 위해 열을 자체와 일치시키고 사용자가 점수가 0에서 분기되는 속도를 제어하도록 매개 변수를 변경하고 검색 구 사이의 고유 유사성을 기록 할 수 있습니다 ( 이론적으로 결과에서 오 탐지를 상쇄하는 데 사용될 수 있음)

퍼지 문자열 일치 벤치 마크

추가 응용

이 솔루션은 사용자가 컴퓨터 시스템이 완벽하게 일치하지 않는 일련의 문자열에서 문자열을 식별하도록 원하는 모든 위치에서 사용될 수 있습니다. (문자열에 대한 대략적인 일치 vlookup처럼).


따라서 이것에서 취해야 할 것은 레 벤슈 테인 거리 알고리즘의 구현과 함께 높은 수준의 휴리스틱 (하나의 문구에서 다른 문구의 단어 찾기, 두 문구의 길이 등) 조합을 사용하고 싶을 것입니다. "최상"일치 항목을 결정하는 것은 휴리스틱 (퍼지) 결정이므로 유사성을 결정하기 위해 어떤 메트릭에 대해서도 가중치를 설정해야합니다.

적절한 휴리스틱 및 가중치 세트를 사용하면 비교 프로그램에서 결정을 신속하게 내릴 수 있습니다.


13
보너스 : 누군가가 가중 휴리스틱에 추가 메트릭을 포함시키려는 경우 (나는 선형 적으로 독립적이지 않은 3 개만 제공했기 때문에) -wikipedia
Alain

1
S2에 단어가 많으면 (선택한 언어에서 많은 작은 개체를 만드는 것이 엄청나게 느리지 않은 경우) 삼배 기가 속도를 높일 수 있습니다. Trie를 사용한 빠르고 쉬운 Levenshtein 거리는 시도에 대한 훌륭한 기사입니다.
JanX2

1
@Alain 이것은 흥미로운 접근법입니다! 나는 당신의 아이디어 (C ++에서)로 조금 놀고 있지만 한 점,의 가치를 이해하지 못합니다 valuePhrase. 코드에서 바로 보면 Levenshtein 거리 함수의 반환 값입니다. 'abcd efgh'검색 테이블에서 이중 / 부동 값은 어떻게됩니까? Levenshtein 거리는 정수 값이며 코드에서 부동 소수점으로 만드는 추가 계산을 볼 수 없습니다. 내가 무엇을 그리워합니까?
Andreas W. Wylach

1
@ AndreasW.Wylach 위대한 관찰. 내가 보여준 VBA는 레 벤슈 테인 거리를 계산하는 것이지만 스프레드 시트에서 사용한 휴리스틱 =valuePhrase(A2,B2)-0.8*ABS(LEN(B2)-LEN(A2))은 레벤 슈타인 거리의 페널티를 두 "프레이즈"길이의 차이의 80 % 줄였습니다. 이러한 방식으로 길이가 같은 "구문"은 전체 페널티를 받지만 "추가 정보"(더 길게)를 포함하지만 여전히 대부분 동일한 문자를 공유하는 "구문"은 페널티가 줄어 듭니다.
Alain

1
@Alain 내 질문으로 돌아가 주셔서 감사합니다. 감사합니다. 당신의 설명은 이제 더 명확 해집니다. 한편 나는 구문 토큰을 조금 더 깊이 분석하는 value_phrase 메소드를 구현했습니다. 즉, 구문 토큰의 순서 / 위치, 비 쿼리 토큰 시퀀스이며 무언가에 관해서는 조금 더 화려 함을 받아들입니다. "abcd"와 비교하여 "acbd"와 같습니다. phrase_value 점수의 경향은 당신과 같지만 여기 저기 조금 낮아집니다. 다시 한번, 훌륭한 운동과 퍼지 검색 알고리즘에 대한 영감을 얻었습니다!
Andreas W. Wylach

88

이 문제는 생물 정보학에서 항상 나타납니다. 위에서 받아 들인 대답 (바이오 정보학에서 Needleman-Wunsch (두 문자열 비교)) 및 Smith-Waterman (긴 문자열에서 근사 하위 문자열 찾기) 알고리즘으로 알려져 있습니다. 그들은 훌륭하게 일했고 수십 년 동안 일꾼이었습니다.

그러나 비교할 백만 개의 문자열이 있다면 어떨까요? 그것은 1 조 쌍의 비교이며, 각각 O (n * m)입니다! 현대 DNA 시퀀서 는 각각 약 200 개의 DNA "문자"길이 인 10 억 개의 짧은 DNA 서열을 쉽게 생성한다 . 일반적으로, 우리는 그러한 각 문자열에 대해 인간 게놈과 가장 잘 어울리는 것을 찾고 싶습니다 (30 억 자). 분명히 Needleman-Wunsch 알고리즘과 그 친척은 그렇지 않습니다.

소위 "정렬 문제"는 활발한 연구 분야입니다. 가장 널리 사용되는 알고리즘은 현재 합리적인 하드웨어 (예 : 8 코어 및 32GB RAM)에서 몇 시간 만에 10 억 개의 짧은 문자열과 인간 게놈간에 부정확 한 일치 항목을 찾을 수 있습니다.

이러한 알고리즘의 대부분은 짧은 정확한 일치 (시드)를 빠르게 찾은 다음 느린 알고리즘 (예 : Smith-Waterman)을 사용하여 전체 문자열로 확장하여 작동합니다. 이것이 효과가있는 이유는 우리가 실제로는 몇 개의 근접한 경기에만 관심이 있기 때문에 공통점이없는 99.9 ... %의 페어를 제거하는 것입니다.

정확히 일치하는 것을 찾는 것이 부정확 한 일치를 찾는 데 어떻게 도움이 됩니까? 쿼리와 대상간에 단 하나의 차이 만 허용한다고 가정 해 보겠습니다. 이 차이는 쿼리의 오른쪽 또는 왼쪽 절반에서 발생해야하며 나머지 절반은 정확히 일치해야합니다. 이 아이디어는 여러 불일치로 확장 될 수 있으며 Illumina DNA 시퀀서에 일반적으로 사용되는 ELAND 알고리즘 의 기초입니다 .

정확한 문자열 일치를 수행하기위한 매우 좋은 알고리즘이 많이 있습니다. 길이가 200 인 쿼리 문자열과 길이가 30 억 (인간 게놈) 인 목표 문자열이 주어지면 대상의 어느 위치에서 쿼리의 하위 문자열과 정확히 일치하는 길이 k의 하위 문자열이 있는지 확인하려고합니다. 간단한 접근법은 대상을 색인화하여 시작하는 것입니다. 모든 k 길이의 하위 문자열을 가져 와서 배열에 넣고 정렬하십시오. 그런 다음 쿼리의 각 k- 길이 하위 문자열을 가져 와서 정렬 된 인덱스를 검색하십시오. 정렬 및 검색은 O (log n) 시간 내에 수행 할 수 있습니다.

그러나 스토리지는 문제가 될 수 있습니다. 30 억 글자 목표의 색인은 30 억 포인터와 30 억 k- 길이 단어를 보유해야합니다. 수십 기가 바이트 미만의 RAM에 이것을 맞추기가 어려워 보일 것입니다. 그러나 놀랍게도 Burrows-Wheeler transform을 사용하여 인덱스를 크게 압축 할 수 있으며 여전히 효율적으로 쿼리 할 수 ​​있습니다. 인간 게놈의 인덱스는 4GB 미만의 RAM에 적합 할 수 있습니다. 이 아이디어는 BowtieBWA 와 같은 인기있는 시퀀스 정렬 기의 기초입니다 .

또는 포인터 만 저장하지만 대상 문자열에있는 모든 접미사의 동시 색인을 나타내는 접미사 배열을 사용할 수 있습니다 . ). 32 비트 포인터를 사용하면 인간 게놈의 접미사 배열 인덱스에 12GB의 RAM이 필요합니다.

위의 링크에는 풍부한 정보와 기본 연구 논문에 대한 링크가 포함되어 있습니다. ELAND 링크는 관련된 개념을 보여주는 유용한 그림이있는 PDF로 이동하고 삽입 및 삭제를 처리하는 방법을 보여줍니다.

마지막으로,이 알고리즘은 기본적으로 단일 인간 게놈 (10 억 개의 짧은 문자열)의 (재) 시퀀싱 문제를 해결했지만 DNA 시퀀싱 기술은 무어의 법칙보다 훨씬 빠르게 개선되며, 우리는 수조 자의 데이터 세트에 빠르게 접근하고 있습니다. 예를 들어, 현재 10 억 자 정도 의 10,000 개의 척추 동물 종 의 게놈을 시퀀싱하는 프로젝트가 진행 중 입니다. 당연히, 우리는 데이터에 대해 페어 단위의 부정확 한 문자열 매칭을 원할 것입니다 ...


3
정말 좋은 런 다운. 몇 가지 수정 사항 : 접두사를 정렬하는 데 O (log n)가 아닌 O (n)이 필요합니다. 실제로 O (log n) 검색은 실제로 너무 느리기 때문에 일반적으로 O (1) 조회 (q-gram index)를 얻기 위해 추가 테이블을 작성합니다. 또한 왜 이것을 접미사 배열과 다르게 취급하는지 잘 모르겠습니다. 후자의 최적화 일뿐입니다 (실제로 고정 길이 이상을 필요로하지 않기 때문에 접미사 대신 고정 길이 접두사 정렬).
Konrad Rudolph

1
또한, 이들 알고리즘은 여전히 노보 시퀀싱에 비실용적 이다. 그들은 우리가 매핑하는 데 사용할 수있는 참조 서열이있는 한 인간 게놈의 시퀀싱을 해결했습니다. 그러나 새로운 어셈블리에는 다른 알고리즘이 필요합니다 (매핑을 기반으로하지만 정렬을 함께 스티칭하는 것은 전혀 다른 문제입니다). 마지막으로, 뻔뻔한 플러그 : 내 학사 논문 에는 ELAND 알고리즘에 대한 자세한 설명이 포함되어 있습니다.
Konrad Rudolph

1
감사. 오류를 편집했습니다. 고정 길이 배열을 설명하면서 시작한 이유는 이해하기 쉽기 때문입니다. 접미사 배열과 BWT는 파악하기가 다소 어렵지만 실제로는 k 값이 다른 인덱스를 사용하려고합니다. 예를 들어 STAR 는 접미사 배열을 사용하여 스플 라이스 정렬 을 효율적으로 찾습니다 . 이것은 물론 RNA를 게놈에 정렬하는데 유용하다.
Sten L

30

선택 B가 테스트 문자열에 더 가깝다고 주장합니다. 원래 문자열이 아닌 4 문자 (및 2 삭제)이기 때문입니다. C에는 갈색과 빨간색이 모두 포함되어 있기 때문에 C가 더 가깝습니다. 그러나 편집 거리가 더 큽니다.

두 입력 사이의 편집 거리 를 측정하는 Levenshtein Distance 알고리즘이 있습니다 .

여기에 그 알고리즘을위한 도구입니다.

  1. 거리 A로서 15를 선택하십시오.
  2. 거리 B를 6으로 평가하십시오.
  3. 거리 C를 9로 평가합니다.

편집 : 죄송합니다, levenshtein 도구에서 문자열을 계속 혼합합니다. 답변을 수정하도록 업데이트되었습니다.


2
좋아, 나는 그것이 사실이라고 생각한다. 이것 좀 살펴 볼게요. 나는 그것이 꽤 가까이 붙어있는 한 목표에 얼마나 가까운 지 신경 쓰지 않습니다 . 완벽에 대한 필요가 없습니다;) 답변의 결과를 확인할 수있을 때까지 당신을위한 포인트 :)
Freesnöw

18

후손을위한 루아 구현 :

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는 문자열 일치를위한 Levenshtein 거리와 같은 쉬운 거리 측정을 제공하는 Python 라이브러리입니다. 표준 라이브러리에서 difflib 위에 빌드되며 가능한 경우 C 구현 Python-levenshtein을 사용합니다.

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


Fuzzywuzzy는 이것을 읽는 다른 사람들에게 실제로 Alain의 훌륭한 게시물에 많은 아이디어를 구현합니다. 실제로 이러한 아이디어 중 일부를 사용하려는 경우 시작하기에 좋은 곳입니다.
그레고리 아 레니 우스

12

이 라이브러리가 도움이 될 수 있습니다! http://code.google.com/p/google-diff-match-patch/

현재 Java, JavaScript, Dart, C ++, C #, Objective C, Lua 및 Python으로 제공됩니다.

꽤 잘 작동합니다. 루아 프로젝트에서 사용합니다.

그리고 다른 언어로 포팅하는 것이 너무 어려울 것이라고 생각하지 않습니다!


2

검색 엔진 또는 데이터베이스에 대한 프론트 엔드와 관련하여이를 수행하는 경우 ComplexPhraseQueryParser 플러그인 과 함께 Apache Solr 과 같은 도구를 사용하는 것이 좋습니다 . 이 조합을 사용하면 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

효율적인 방식으로 큰 텍스트 집합을 쿼리하려면 거리 편집 / 접두사 편집 거리 개념을 사용할 수 있습니다.

거리 ED (x, y) 편집 : 항 x에서 항 y로가는 최소 트랜스 포트 수

그러나 각 용어와 쿼리 텍스트 간의 ED 계산은 리소스와 시간이 많이 소요됩니다. 따라서 먼저 각 항에 대해 ED를 계산하는 대신 Qgram Index라는 기술을 사용하여 가능한 일치 항을 추출 할 수 있습니다. 선택한 용어에 대해 ED 계산을 적용합니다.

Qgram 인덱스 기술의 장점은 퍼지 검색을 지원한다는 것입니다.

QGram 인덱스를 적용하는 한 가지 가능한 방법은 Qgram을 사용하여 Inverted Index를 만드는 것입니다. 거기에는 특정 Qgram으로 구성된 모든 단어를 해당 Qgram 아래에 저장합니다. (전체 문자열을 저장하는 대신 각 문자열에 고유 ID를 사용할 수 있습니다). 이를 위해 Java에서 트리 맵 데이터 구조를 사용할 수 있습니다. 다음은 용어 저장에 대한 작은 예입니다.

COL : COL의 MBIA, COL의 Ombo에서, 질화 갈륨 (COL) A, 타 COL AMA

그런 다음 쿼리 할 때 쿼리 텍스트와 사용 가능한 용어 사이의 공통 Qgram 수를 계산합니다.

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

공통 q- 그램 수 = 4

공통 Qgram 수가 많은 용어의 경우 쿼리 용어에 대해 ED / PED를 계산 한 다음 최종 사용자에게 해당 용어를 제안합니다.

다음 프로젝트에서이 이론의 구현을 찾을 수 있습니다 ( "QGramIndex.java"참조). 궁금한 점이 있으면 언제든지 문의하십시오. https://github.com/Bhashitha-Gamage/City_Search

편집 거리에 대해 더 공부하려면 접두사 편집 거리 Qgram 인덱스 다음 교수 박사 한나 인피부의 비디오를 시청하십시오 https://www.youtube.com/embed/6pUg2wmGJRo (20시 6분에서 수업 시작)


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

주어진 단어 사이의 거리를 계산하기 위해 golang POC를 가질 수 있습니다. minDistancedifference다른 범위를 조정할 수 있습니다 .

운동장 : 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.