의미있는 자바 스크립트 퍼지 검색


98

배열을 필터링하기 위해 퍼지 검색 JavaScript 라이브러리를 찾고 있습니다. fuzzyset.jsfuse.js 사용을 시도 했지만 결과가 끔찍합니다 (링크 된 페이지에서 시도 할 수있는 데모가 있습니다).

Levenshtein 거리에 대해 읽은 후 사용자가 입력 할 때 찾는 내용에 대한 근사치가 나쁘다는 생각이 듭니다. 모르는 사람들을 위해 시스템 은 두 개의 문자열을 일치시키는 데 필요한 삽입 , 삭제대체 수를 계산합니다 .

Levenshtein-Demerau 모델에서 수정 한 명백한 결점은 양이다 BluB 효소가슴이 똑같이 유사한 고려 전구 (각각 두 개의 치환을 요구하는). 그러나, 분명 전구는 더 유사하다 BluB 효소 보다 가슴이 , 그리고 내가 방금 언급 한 모델을 가능하게하여 해당 인식 트랜스 포지션 .

나는 이것을 텍스트 완성의 맥락에서 사용하고 싶다. 그래서 내가 array를 가지고 ['international', 'splint', 'tinder']있고 내 쿼리가 int 이라면 , 전자가 10 점 (higher = worse)을 가지고 있더라도 internationalsplint 보다 더 높은 순위를 가져야 한다고 생각 한다. 후자의 3.

그래서 내가 찾고있는 것은 (존재하지 않으면 만들 것입니다) 다음을 수행하는 라이브러리입니다.

  • 다양한 텍스트 조작에 가중치 적용
  • 단어에 나타나는 위치에 따라 각 조작에 가중치를 부여합니다 (초기 조작은 늦은 조작보다 비용이 많이 듭니다).
  • 관련성별로 정렬 된 결과 목록을 반환합니다.

이런 걸 본 사람이 있습니까? StackOverflow가 소프트웨어 권장 사항을 요청하는 곳이 아니라는 것을 알고 있지만 위의 암시 적 (더 이상은 아닙니다!)은 다음과 같습니다. 올바른 방식으로 생각하고 있습니까?


편집하다

주제에 대한 좋은 논문 (pdf) 을 찾았습니다 . 일부 참고 및 발췌 :

Affine 편집 거리 기능은 일련의 삽입 또는 삭제에 상대적으로 낮은 비용을 할당합니다.

특정 비용 매개 변수가있는 Smith-Waterman 거리 함수 (Durban et al. 1998)의 유사 변형 인 Monger-Elkan 거리 함수 (Monge & Elkan 1996)

들어 스미스 - 워터맨 거리 (위키) "대신에 전체 시퀀스를 찾고, 스미스 - 워터맨 알고리즘은 모든 가능한 길이의 세그먼트를 비교하여 유사도 측정치를 최적화한다." 그것은 n-gram 접근 방식입니다.

편집 거리 모델을 기반으로하지 않는 광범위하게 유사한 메트릭은 Jaro 메트릭입니다 (Jaro 1995; 1989; Winkler 1999). 레코드-연결 문헌에서 두 문자열 사이의 공통 문자 수와 순서를 기반으로하는이 방법의 변형을 사용하여 좋은 결과를 얻었습니다.

Winkler (1999)로 인한 변형은 또한 가장 긴 공통 접두사의 길이 P를 사용합니다.

(주로 짧은 문자열을위한 것으로 보임)

텍스트 완성을 위해 Monger-Elkan 및 Jaro-Winkler 접근 방식이 가장 합리적입니다. Jaro 메트릭에 Winkler의 추가는 효과적으로 단어의 시작 부분에 더 많은 가중치를 부여합니다. 그리고 Monger-Elkan의 affine 측면은 단어를 완성해야하는 필요성 (단순히 추가되는 순서)이 너무 많이 싫어하지 않을 것임을 의미합니다.

결론:

TFIDF 순위는 여러 토큰 기반 거리 메트릭 중에서 가장 잘 수행되었으며 Monge와 Elkan이 제안한 조정 된 affine-gap 편집 거리 메트릭은 여러 문자열 편집-거리 메트릭 중에서 가장 잘 수행되었습니다. 놀랍게도 좋은 거리 측정법은 Jaro가 제안하고 나중에 Winkler가 확장 한 빠른 휴리스틱 방식입니다. 이것은 Monge-Elkan 방식과 거의 비슷하지만 훨씬 더 빠릅니다. TFIDF 방법과 Jaro-Winkler를 결합하는 간단한 방법 중 하나는 TFIDF에서 사용되는 정확한 토큰 일치를 Jaro-Winkler 체계를 기반으로하는 대략적인 토큰 일치로 바꾸는 것입니다. 이 조합은 평균적으로 Jaro-Winkler 또는 TFIDF보다 약간 더 나은 성능을 제공하며 때로는 훨씬 더 나은 성능을 제공합니다. 또한이 백서에서 고려한 몇 가지 최고의 메트릭의 학습 된 조합에 가깝습니다.


좋은 질문입니다. 비슷한 작업을 수행하려고하지만 동일한 문자열 비교 고려 사항이 있습니다. 문자열 비교의 자바 스크립트 구현을 찾거나 빌드 한 적이 있습니까? 감사.
nicholas

1
@nicholas 나는 단순히 더 작은 쿼리 문자열을 설명하기 위해 github에서 fuzzyset.js를 포크했으며 가중치가 적용된 문자열 조작을 설명하지는 않지만 결과는 내가 의도 한 문자열 완성 응용 프로그램에 매우 적합합니다. 참조 의 repo
willlma

감사. 시도해 볼게. 나는 또한이 문자열 비교 기능을 발견했습니다 : github.com/zdyn/jaro-winkler-js . 너무 잘 작동하는 것 같습니다.
nicholas

1
이것을 시도하십시오 : subtexteditor.github.io/fuzzysearch.js
michaelday

1
@michaelday 오타를 고려하지 않습니다. 데모에서는를 입력 krole해도 반환되지 않습니다 Final Fantasy V: Krile. 쿼리의 모든 문자가 결과에서 동일한 순서로 나타나야하므로 근시안적입니다. 퍼지 검색을 잘하는 유일한 방법은 일반적인 오타 데이터베이스를 사용하는 것입니다.
willlma

답변:


21

좋은 질문! 그러나 제 생각에는 Levenshtein-Demerau를 수정하는 것보다 다른 알고리즘을 시도하거나 두 알고리즘의 결과를 결합 / 가중치는 것이 더 나을 수 있습니다.

"시작 접두사"와 정확히 일치하거나 근접한 일치는 Levenshtein-Demerau가 특별한 가중치를주지 않는 것입니다.

나는 "Levenshtein보다 더 나은"을 검색했고, 무엇보다도 이것을 발견했다 :

http://www.joyofdata.de/blog/comparison-of-string-distance-algorithms/

이것은 많은 "문자열 거리"측정을 언급합니다. 귀하의 요구 사항과 특히 관련이있는 세 가지는 다음과 같습니다.

  1. 가장 긴 공통 부분 문자열 거리 : 결과 부분 문자열이 동일 할 때까지 두 문자열에서 제거해야하는 최소 기호 수입니다.

  2. q- 그램 거리 : 두 문자열의 N- 그램 벡터 간의 절대 차이의 합계입니다.

  3. Jaccard 거리 : 1 분 공유 N- 그램과 관찰 된 모든 N- 그램의 몫입니다.

Levenshtein (공통 하위 문자열, 공통 N-gram 또는 Jaccard는 모두 유사한 문자열을 강력하게 선호 함) 함께 이러한 메트릭의 가중치 조합 (또는 최소값)을 사용할 수 있습니다. 아니면 Jaccard를 사용해보십시오.

목록 / 데이터베이스의 크기에 따라 이러한 알고리즘은 다소 비쌀 수 있습니다. 필자가 구현 한 퍼지 검색의 경우 구성 가능한 수의 N- 그램을 DB에서 "검색 키"로 사용한 다음 값 비싼 문자열 거리 측정을 실행하여 선호하는 순서대로 정렬했습니다.

SQL의 퍼지 문자열 검색에 대한 메모를 작성했습니다. 보다:


63

나는 fuse.js와 같은 기존의 퍼지 라이브러리를 사용해 보았고 또한 그것들이 끔찍하다는 것을 알았 기 때문에 기본적으로 숭고한 검색과 같이 작동하는 라이브러리를 작성했습니다. https://github.com/farzher/fuzzysort

허용되는 유일한 오타는 전치입니다. 그것은 꽤 고체이다 (1K 별, 0 문제) , 매우 빨리 , 쉽게 케이스를 처리합니다

fuzzysort.go('int', ['international', 'splint', 'tinder'])
// [{highlighted: '*int*ernational', score: 10}, {highlighted: 'spl*int*', socre: 3003}]


4
나는 Fuse.js가 마음에 들지 않았고 라이브러리를 사용해 보았습니다. 훌륭하게 작동합니다! 잘 :) 일
데이브

1
단어가 완료하지만, 올바른 단어는 "XRP"인 경우, 예를 들면 잘못 철자와 내가 검색하면 "XRT는"그것은 나에게 점수를 제공 나던 때 내가 직면 한이 라이브러리에 대한 유일한 문제는
PirateApp

1
@PirateApp yup, 맞춤법 오류를 처리하지 않습니다 (숭고한 검색이 수행하지 않기 때문에). 나는 사람들이 불평하고있는 지금 이것을 조사하고있다. 이 검색은 GitHub의 문제로 실패 예를 들어, 사용 사례로 날을 제공 할 수 있습니다
Farzher

3
이 lib에 대해 궁금한 분들을 위해 이제 맞춤법 검사도 구현되었습니다! 나는 fusejs 등을 통해이 LIB 추천
PirateApp

1
@ user4815162342 직접 코딩해야합니다. 이 스레드를 체크 아웃하면 코드 샘플이 있습니다. github.com/farzher/fuzzysort/issues/19
Farzher 2018-08-27

19

여기 제가 몇 번 사용한 기술이 있습니다 ... 꽤 좋은 결과를 제공합니다. 그래도 요청한 모든 것을 수행하지는 않습니다. 또한 목록이 방대하면 비용이 많이들 수 있습니다.

get_bigrams = (string) ->
    s = string.toLowerCase()
    v = new Array(s.length - 1)
    for i in [0..v.length] by 1
        v[i] = s.slice(i, i + 2)
    return v

string_similarity = (str1, str2) ->
    if str1.length > 0 and str2.length > 0
        pairs1 = get_bigrams(str1)
        pairs2 = get_bigrams(str2)
        union = pairs1.length + pairs2.length
        hit_count = 0
        for x in pairs1
            for y in pairs2
                if x is y
                    hit_count++
        if hit_count > 0
            return ((2.0 * hit_count) / union)
    return 0.0

에 두 개의 문자열 패스 string_similarity사이의 숫자를 반환하는 0그리고 1.0그들이 얼마나 비슷한에 따라. 이 예에서는 Lo-Dash를 사용합니다.

사용 예 ....

query = 'jenny Jackson'
names = ['John Jackson', 'Jack Johnson', 'Jerry Smith', 'Jenny Smith']

results = []
for name in names
    relevance = string_similarity(query, name)
    obj = {name: name, relevance: relevance}
    results.push(obj)

results = _.first(_.sortBy(results, 'relevance').reverse(), 10)

console.log results

또한 .. 바이올린을

본체가 열려 있는지 확인하십시오. 그렇지 않으면 아무것도 표시되지 않습니다. :)


3
고마워요, 바로 제가 찾던 것입니다. 그것은 일반 JS했다 경우에만 더 나은 것)
lucaswxp

1
function get_bigrams (string) {var s = string.toLowerCase () var v = s.split ( ''); for (var i = 0; i <v.length; i ++) {v [i] = s.slice (i, i + 2); } return v; } function string_similarity (str1, str2) {if (str1.length> 0 && str2.length> 0) {var pairs1 = get_bigrams (str1); var 쌍 2 = get_bigrams (str2); var union = pairs1.length + pairs2.length; var hits = 0; for (var x = 0; x <pairs1.length; x ++) {for (var y = 0; y <pairs2.length; y ++) {if (pairs1 [x] == pairs2 [y]) hit_count ++; }} if (hits> 0) return ((2.0 * hits) / union); } return 0.0}
jaya

여러 키에서 검색하려는 객체에서 이것을 사용하는 방법은 무엇입니까?
user3808307

여기에는 몇 가지 문제가 있습니다. 1) 문자열의 시작과 끝에있는 문자의 가중치를 낮 춥니 다. 2) 바이그램 비교는 O (n ^ 2)입니다. 3) 구현으로 인해 유사성 점수가 1 이상일 수 있습니다. 이것은 분명히 말이되지 않습니다. 아래의 내 대답에서 이러한 모든 문제를 해결합니다.
MgSam

8

이것은 퍼지 매치에 대한 짧고 간결한 기능입니다.

function fuzzyMatch(pattern, str) {
  pattern = '.*' + pattern.split('').join('.*') + '.*';
  const re = new RegExp(pattern);
  return re.test(str);
}

대부분의 경우에 당신이 원하는 것은 아니지만 그것은 정확히 저를위한 것이 었습니다.
schmijos


2

2019 년 11 월 업데이트. 나는 퓨즈가 꽤 괜찮은 업그레이드를 발견했습니다. 그러나 bool (예 : OR, AND 등의 연산자)을 사용하거나 API 검색 인터페이스를 사용하여 결과를 필터링 할 수 없습니다.

나는 발견했다 nextapps-de/flexsearch: https://github.com/nextapps-de/flexsearch 그리고 나는 그것이 내가 시도한 다른 많은 자바 스크립트 검색 라이브러리를 훨씬 능가한다고 믿으며 bool, 검색 필터링 및 페이지 매김을 지원합니다.

검색 데이터 (예 : 스토리지)에 대한 자바 스크립트 개체 목록을 입력 할 수 있으며 API는 매우 잘 문서화되어 있습니다. https://github.com/nextapps-de/flexsearch#api-overview

지금까지 10,000 개에 가까운 레코드를 인덱싱했으며 내 검색은 바로 옆에 있습니다. 즉, 각 검색에 대해 눈에 띄지 않는 시간.


2

여기 @InternalFX에서 제공하는 솔루션이 있지만 JS에서 (나는 공유하기 위해 사용했습니다) :

function get_bigrams(string){
  var s = string.toLowerCase()
  var v = s.split('');
  for(var i=0; i<v.length; i++){ v[i] = s.slice(i, i + 2); }
  return v;
}

function string_similarity(str1, str2){
  if(str1.length>0 && str2.length>0){
    var pairs1 = get_bigrams(str1);
    var pairs2 = get_bigrams(str2);
    var union = pairs1.length + pairs2.length;
    var hits = 0;
    for(var x=0; x<pairs1.length; x++){
      for(var y=0; y<pairs2.length; y++){
        if(pairs1[x]==pairs2[y]) hits++;
    }}
    if(hits>0) return ((2.0 * hits) / union);
  }
  return 0.0
}

2

InternalFx의 CoffeeScript bigram 솔루션의 문제를 해결하고 일반 n-gram 솔루션으로 만들었습니다 (그램의 크기를 사용자 지정할 수 있음).

이것은 TypeScript이지만 유형 어노테이션을 제거 할 수 있으며 바닐라 JavaScript로도 잘 작동합니다.

/**
 * Compares the similarity between two strings using an n-gram comparison method. 
 * The grams default to length 2.
 * @param str1 The first string to compare.
 * @param str2 The second string to compare.
 * @param gramSize The size of the grams. Defaults to length 2.
 */
function stringSimilarity(str1: string, str2: string, gramSize: number = 2) {
  function getNGrams(s: string, len: number) {
    s = ' '.repeat(len - 1) + s.toLowerCase() + ' '.repeat(len - 1);
    let v = new Array(s.length - len + 1);
    for (let i = 0; i < v.length; i++) {
      v[i] = s.slice(i, i + len);
    }
    return v;
  }

  if (!str1?.length || !str2?.length) { return 0.0; }

  //Order the strings by length so the order they're passed in doesn't matter 
  //and so the smaller string's ngrams are always the ones in the set
  let s1 = str1.length < str2.length ? str1 : str2;
  let s2 = str1.length < str2.length ? str2 : str1;

  let pairs1 = getNGrams(s1, gramSize);
  let pairs2 = getNGrams(s2, gramSize);
  let set = new Set<string>(pairs1);

  let total = pairs2.length;
  let hits = 0;
  for (let item of pairs2) {
    if (set.delete(item)) {
      hits++;
    }
  }
  return hits / total;
}

예 :

console.log(stringSimilarity("Dog", "Dog"))
console.log(stringSimilarity("WolfmanJackIsDaBomb", "WolfmanJackIsDaBest"))
console.log(stringSimilarity("DateCreated", "CreatedDate"))
console.log(stringSimilarity("a", "b"))
console.log(stringSimilarity("CreateDt", "DateCreted"))
console.log(stringSimilarity("Phyllis", "PyllisX"))
console.log(stringSimilarity("Phyllis", "Pylhlis"))
console.log(stringSimilarity("cat", "cut"))
console.log(stringSimilarity("cat", "Cnut"))
console.log(stringSimilarity("cc", "Cccccccccccccccccccccccccccccccc"))
console.log(stringSimilarity("ab", "ababababababababababababababab"))
console.log(stringSimilarity("a whole long thing", "a"))
console.log(stringSimilarity("a", "a whole long thing"))
console.log(stringSimilarity("", "a non empty string"))
console.log(stringSimilarity(null, "a non empty string"))

TypeScript Playground에서 사용해보세요


0
(function (int) {
    $("input[id=input]")
        .on("input", {
        sort: int
    }, function (e) {
        $.each(e.data.sort, function (index, value) {
          if ( value.indexOf($(e.target).val()) != -1 
              && value.charAt(0) === $(e.target).val().charAt(0) 
              && $(e.target).val().length === 3 ) {
                $("output[for=input]").val(value);
          };
          return false
        });
        return false
    });
}(["international", "splint", "tinder"]))

jsfiddle http://jsfiddle.net/guest271314/QP7z5/


0

Flookup이라는 Google 스프레드 시트 추가 기능을 확인하고 다음 기능을 사용하세요.

Flookup (lookupValue, tableArray, lookupCol, indexNum, threshold, [rank])

매개 변수 세부 사항은 다음과 같습니다.

  1. lookupValue: 찾고있는 값
  2. tableArray: 검색하려는 테이블
  3. lookupCol: 검색하려는 열
  4. indexNum: 데이터를 반환 할 열
  5. threshold: 데이터가 반환되지 않아야하는 유사성 비율
  6. rank: n 번째 베스트 매치 (즉, 첫 매치가 마음에 들지 않는 경우)

이것은 귀하의 요구 사항을 충족시킬 것입니다 ... 포인트 번호 2에 대해서는 잘 모르겠습니다.

자세한 내용은 공식 웹 사이트 에서 확인하십시오 .

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.