जावास्क्रिप्ट फजी खोज जो समझ में आता है


98

मैं एक सरणी को फ़िल्टर करने के लिए एक फजी सर्च जावास्क्रिप्ट लाइब्रेरी की तलाश में हूं। मैंने fuzzyset.js और fuse.js का उपयोग करने की कोशिश की है , लेकिन परिणाम बहुत ही भयानक हैं (ऐसे डेमो हैं जिन्हें आप लिंक किए गए पृष्ठों पर आज़मा सकते हैं)।

लेवेंसहेटिन दूरी पर कुछ पढ़ने के बाद, यह मुझे एक खराब अनुमान के रूप में प्रभावित करता है कि उपयोगकर्ता जब टाइप करते हैं, तो वे क्या देख रहे हैं। जो नहीं जानते हैं, उनके लिए सिस्टम यह गणना करता है कि दो स्ट्रिंग्स मैच करने के लिए कितने सम्मिलन , विलोपन और प्रतिस्थापन की आवश्यकता है।

एक स्पष्ट दोष है, जो Levenshtein-Demerau मॉडल में तय हो गई है, कि दोनों है blub और स्तन समान रूप से करने के लिए इसी तरह के विचार कर रहे हैं बल्ब (प्रत्येक दो प्रतिस्थापन की आवश्यकता होती है)। यह स्पष्ट है, हालांकि, यह बल्ब उल्लू की तुलना में ब्लब के समान है, और जिस मॉडल का मैंने अभी उल्लेख किया है, वह मानता है कि ट्रांसपोज़िशन के लिए अनुमति देकर ।

मैं इसे पाठ पूरा होने के संदर्भ में उपयोग करना चाहता हूं, इसलिए यदि मेरे पास एक सरणी है ['international', 'splint', 'tinder'], और मेरी क्वेरी int है , तो मुझे लगता है कि अंतरराष्ट्रीय को स्प्लिंट से अधिक रैंक करना चाहिए , भले ही पूर्व में 10 का स्कोर (उच्च = खराब) हो। बनाम बाद के 3।

तो मैं जो खोज रहा हूं (और अगर वह मौजूद नहीं है तो बनाएगा), एक पुस्तकालय है जो निम्नलिखित कार्य करता है:

  • विभिन्न पाठ जोड़तोड़ वजन
  • प्रत्येक हेरफेर को अलग-अलग तरीके से निर्भर करता है जहां वे एक शब्द में दिखाई देते हैं (शुरुआती जोड़तोड़ देर से जोड़तोड़ की तुलना में अधिक महंगा है)
  • प्रासंगिकता द्वारा क्रमबद्ध परिणामों की सूची लौटाता है

किसी को भी इस तरह से कुछ भी आया है? मुझे लगता है कि StackOverflow सॉफ्टवेयर सिफारिशों के लिए पूछने की जगह नहीं है, लेकिन उपरोक्त में (अब और नहीं!) निहित है: क्या मैं इस बारे में सही तरीके से सोच रहा हूं?


संपादित करें

मुझे इस विषय पर एक अच्छा पेपर (पीडीएफ) मिला । कुछ नोट्स और अंश:

एडिट-डिस्टेंस फ़ंक्शंस सम्मिलित या विलोपन के अनुक्रम में अपेक्षाकृत कम लागत प्रदान करते हैं

मोंगर-एलकन डिस्टेंस फंक्शन (Monge & Elkan 1996), जो विशेष रूप से लागत मापदंडों के साथ स्मिथ-वाटरमैन डिस्टेंस फंक्शन (डरबन एट अल 1998) का एक affine वेरिएंट है।

के लिए स्मिथ-वाटरमैन दूरी (विकिपीडिया) , "इसके बजाय कुल अनुक्रम को देखने का, स्मिथ-वाटरमैन एल्गोरिथ्म सभी संभव लंबाई के क्षेत्रों तुलना करता है और समानता उपाय अनुकूलित करता है।" यह n- ग्राम दृष्टिकोण है।

मोटे तौर पर एक समान मीट्रिक, जो एडिट-डिस्टेंस मॉडल पर आधारित नहीं है, जोरो मीट्रिक (जारो 1995; 1989; विंकलर 1999) है। रिकॉर्ड-लिंकेज साहित्य में, इस पद्धति के वेरिएंट का उपयोग करके अच्छे परिणाम प्राप्त किए गए हैं, जो दो तारों के बीच आम पात्रों की संख्या और क्रम पर आधारित है।

विंकलर (1999) के कारण इसका एक प्रकार सबसे लंबे समय तक सामान्य उपसर्ग की लंबाई पी का उपयोग करता है

(मुख्य रूप से शॉर्ट स्ट्रिंग्स के लिए अभिप्रेत लगता है)

पाठ पूरा करने के उद्देश्यों के लिए, मोंगर-एल्कान और जारो-विंकलर दृष्टिकोण सबसे अधिक समझ में आता है। जोकर मेट्रिक के लिए विंकलर के अतिरिक्त प्रभावी रूप से शब्दों की शुरुआत को अधिक जोर से तौलता है। और मोंगर-एलकन के स्नेह पहलू का मतलब है कि एक शब्द को पूरा करने की आवश्यकता (जो कि केवल परिवर्धन का एक क्रम है) इसे बहुत भारी नहीं होगा।

निष्कर्ष:

TFIDF रैंकिंग ने कई टोकन-आधारित दूरी मेट्रिक्स के बीच सर्वश्रेष्ठ प्रदर्शन किया, और मेन्ज और एल्कान द्वारा प्रस्तावित ट्यून एफाइन-गैप-एडिट-डिस्टेंस मीट्रिक ने कई स्ट्रिंग एडिट-डिस्टेंस मेट्रिक्स के बीच सर्वश्रेष्ठ प्रदर्शन किया। एक आश्चर्यजनक रूप से अच्छी दूरी की मीट्रिक एक तेजी से अनुमानी योजना है, जो जरो द्वारा प्रस्तावित है और बाद में विंकलर द्वारा विस्तारित की गई है। यह लगभग स्पंज-एल्कान योजना के रूप में अच्छी तरह से काम करता है, लेकिन तेजी से परिमाण का एक क्रम है। TFIDF विधि और Jaro-Winkler के संयोजन का एक सरल तरीका है, TFIDF में उपयोग किए जाने वाले सटीक टोकन मैचों को Jaro- Winkler योजना के आधार पर अनुमानित टोकन मैचों के साथ बदलना। यह संयोजन औसत रूप से यारो-विंकलर या टीएफआईडीएफ की तुलना में थोड़ा बेहतर प्रदर्शन करता है, और कभी-कभी बहुत बेहतर प्रदर्शन करता है। यह इस पत्र में माने जाने वाले कई बेहतरीन मैट्रिक्स के सीखे हुए संयोजन के प्रदर्शन के करीब भी है।


बड़ा सवाल है। मैं ऐसा ही कुछ करना चाह रहा हूं, लेकिन समान स्ट्रिंग तुलनात्मक विचारों के साथ। क्या आपने कभी अपनी स्ट्रिंग तुलना का एक जावास्क्रिप्ट कार्यान्वयन पाया / बनाया? धन्यवाद।
निकोलस

1
@nicholas मैंने केवल छोटे क्वेरी स्ट्रिंग के लिए खाते के लिए gubub पर fuzzyset.js को forked किया है, हालांकि यह भारित स्ट्रिंग जोड़तोड़ के लिए खाता नहीं है, परिणाम स्ट्रिंग पूर्णता के मेरे इच्छित आवेदन के लिए काफी अच्छे हैं। रेपो
विलमा

धन्यवाद। मैं इसे आजमाऊंगा। मुझे यह स्ट्रिंग तुलना फ़ंक्शन भी मिला: github.com/zdyn/jaro-winkler-js । बहुत अच्छा काम करने लगता है।
निकोलस


1
@michaelday खाते में टाइपो नहीं लेता है। डेमो में, टाइपिंग kroleवापस नहीं आती है Final Fantasy V: Krile, हालांकि मैं इसे पसंद करूंगा। परिणाम में एक ही क्रम में उपस्थित होने के लिए क्वेरी के सभी वर्णों की आवश्यकता होती है, जो कि बहुत ही अदूरदर्शी है। यह लगता है कि अच्छी फजी खोज करने का एकमात्र तरीका सामान्य टाइपोस का डेटाबेस होना है।
२०:५०

जवाबों:


21

अच्छा प्रश्न! लेकिन मेरा विचार यह है कि लेवेंसहाइट-डेमारू को संशोधित करने की कोशिश करने के बजाय, आप एक अलग एल्गोरिथ्म की कोशिश करने या दो एल्गोरिदम से परिणामों को संयोजित / वजन करने के लिए बेहतर हो सकते हैं।

यह मुझ पर हमला करता है कि "शुरुआती उपसर्ग" के लिए सटीक या करीबी मैच कुछ ऐसा है, जो लेवेन्स्चेटिन-डेमेराउ को कोई विशेष भार नहीं देता है - लेकिन आपकी स्पष्ट उपयोगकर्ता अपेक्षाएं होंगी।

मैंने "लेवेंसहाइट से बेहतर" की खोज की और अन्य चीजों के अलावा, यह पाया:

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

इसमें कई "स्ट्रिंग डिस्टेंस" उपायों का उल्लेख है। तीन जो आपकी आवश्यकता के लिए विशेष रूप से प्रासंगिक थे, वे होंगे:

  1. सबसे लंबा सामान्य पदार्थ दूरी: न्यूनतम संख्या में प्रतीकों को दोनों तारों में हटाया जाना चाहिए जब तक कि परिणामी समरूपता समान न हो।

  2. क्यू-ग्राम की दूरी: दोनों तारों के एन-ग्राम वैक्टर के बीच पूर्ण अंतर का योग।

  3. जैकार्ड दूरी: 1 साझा एन-ग्राम के भागफल का अनुमान लगाता है और सभी ने एन-ग्राम का अवलोकन किया।

हो सकता है कि आप इन मेट्रिक्स के भारित संयोजन (या न्यूनतम) का उपयोग कर सकते थे, लेवेंसहेटिन के साथ - आम विकल्प, आम एन-ग्राम या जैकार्ड सभी दृढ़ता से समान स्ट्रिंग्स पसंद करेंगे - या शायद सिर्फ जेकार्ड का उपयोग करने का प्रयास करें?

आपकी सूची / डेटाबेस के आकार के आधार पर, ये एल्गोरिदम थोड़े महंगे हो सकते हैं। मेरे द्वारा लागू की गई एक अस्पष्ट खोज के लिए, मैंने DB से "पुनर्प्राप्ति कुंजी" के रूप में एन-ग्राम के एक विन्यास योग्य संख्या का उपयोग किया, फिर उन्हें वरीयता क्रम में क्रमबद्ध करने के लिए महंगे स्ट्रिंग-डिस्टेंस उपाय चलाए।

मैंने SQL में Fuzzy String Search पर कुछ नोट्स लिखे। देख:


64

मैंने फ्यूज़.जेएस जैसी मौजूदा फ़ज़ी लाइब्रेरी का उपयोग करने की कोशिश की और उन्हें भी भयानक पाया, इसलिए मैंने एक लिखा जो मूल रूप से उदात्त की खोज की तरह व्यवहार करता है। https://github.com/farzher/fuzzysort

केवल टाइपो इसकी अनुमति देता है एक संक्रमण है। यह बहुत ठोस है (1k तारे, 0 अंक) , बहुत तेज , और आपके मामले को आसानी से संभालता है:

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


4
मैं फ्यूज.जेएस से नाखुश था और आपकी लाइब्रेरी को बाहर करने की कोशिश की - महान काम करता है! शाबाश :)
डेव

1
इस लाइब्रेरी के साथ एकमात्र समस्या जिसका मुझे सामना करना पड़ा है जब यह शब्द पूरा हो गया है लेकिन उदाहरण के लिए गलत तरीके से लिखा गया है, अगर सही शब्द "XRP" था और अगर मैंने "XRT" खोजा तो यह मुझे स्कोर नहीं देगा
PirateApp

1
@PirateApp हाँ, मैं गलतियाँ नहीं संभालता (क्योंकि उदात्त की खोज नहीं है)। मैं अब इस पर गौर कर रहा हूं कि लोग शिकायत कर रहे हैं। आप मुझे उदाहरण उपयोग मामलों के साथ प्रदान कर सकते हैं जहां यह खोज
जीथुब

3
आप में से जो लोग इस परिवाद के बारे में सोच रहे हैं, उनके लिए अब वर्तनी जांच भी लागू है! मैं fusejs और दूसरों पर इस काम की सलाह देता हूं
PirateApp

1
@ user4815162342 आपको इसे स्वयं कोड करना होगा। इस धागे को चेकआउट करें, इसमें एक कोड नमूना है github.com/farzher/fuzzysort/issues/19
Farzher

18

यहाँ एक तकनीक है जिसका मैंने कुछ बार उपयोग किया है ... यह बहुत अच्छे परिणाम देता है। यद्यपि आपने जो कुछ भी मांगा है वह सब कुछ नहीं करता है। इसके अलावा, यह महंगा हो सकता है अगर सूची बड़े पैमाने पर हो।

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इस बात पर निर्भर करेगी कि वे कितने समान हैं। यह उदाहरण लो-डैश का उपयोग करता है

उपयोग उदाहरण…।

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; मैं <v.length; i ++) {v [i] = s.slice (i, i + 2); } वापसी v; } function string_similarity (str1, str2) {if (str1.length> 0 && str2.length> 0) {var pair1 = get_bigrams (str1); var pair2 = get_bigrams (str2); var Union = pair1.length + pair2.length; var हिट = 0; के लिए (var x = 0; x <pair1.length; x ++) {के लिए (var y = 0; y <pair2.length; y ++) {if (जोड़े 1 [x] == जोड़े 2 [y]) hit_change ++; }} यदि (हिट> 0) रिटर्न ((2.0 * हिट) / यूनियन); } वापसी 0.0}
जया

इसे उन वस्तुओं में कैसे उपयोग करें जिसमें आप कई कुंजी में खोजना चाहेंगे?
user3808307

इसकी कुछ समस्याएं हैं: 1) यह स्ट्रिंग की शुरुआत और अंत में पात्रों को रेखांकित करता है। 2) बिग्राम तुलना ओ (n ^ 2) हैं। 3) कार्यान्वयन के कारण समानता स्कोर 1 से अधिक हो सकता है। यह स्पष्ट रूप से कोई मतलब नहीं है। मैं अपने जवाब में इन सभी समस्याओं को ठीक करता हूं।
मगसम

9

यह फजी मैच के लिए मेरा छोटा और कॉम्पैक्ट फ़ंक्शन है:

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

यद्यपि आप ज्यादातर मामलों में नहीं चाहते हैं, यह वास्तव में मेरे लिए था।
शमीजोस

क्या आप आदेश की अनदेखी कर सकते हैं? fuzzyMatch('c a', 'a b c')वापस आना चाहिएtrue
vsync

5

आप एटम के https://github.com/atom/fuzzaldrin/ lib पर एक नज़र डाल सकते हैं ।

यह npm पर उपलब्ध है, इसमें साधारण API है, और मेरे लिए ठीक काम किया है।

> fuzzaldrin.filter(['international', 'splint', 'tinder'], 'int');
< ["international", "splint"]

मुझे एटम की लाइब्रेरी के साथ भी सफलता मिली, जिसमें एक साधारण एपीआई और तेज बिजली =) है। github.com/cliffordfajardo/cato
केकोडर

2

नवंबर 2019 अपडेट। मैं कुछ बहुत अच्छे उन्नयन के लिए फ्यूज पाया। हालाँकि, मैं इसे बूल (यानी, और, आदि) ऑपरेटरों का उपयोग करने के लिए नहीं प्राप्त कर सका और न ही परिणामों को फ़िल्टर करने के लिए एपीआई खोज इंटरफ़ेस का उपयोग कर सका।

मुझे पता चला nextapps-de/flexsearch: https://github.com/nextapps-de/flexsearch और मेरा मानना ​​है कि यह अब तक मेरे द्वारा खोजी गई अन्य जावास्क्रिप्ट खोज पुस्तकालयों से बहुत अधिक है, और इसमें boolखोज और पृष्ठांकन को फ़िल्टर करने का समर्थन है ।

आप अपने खोज डेटा (यानी भंडारण) के लिए जावास्क्रिप्ट ऑब्जेक्ट्स की एक सूची इनपुट कर सकते हैं, और एपीआई काफी अच्छी तरह से प्रलेखित है: https://github.com/nextapps-de/flexsearch#api-overview

अब तक मैं 10,000 रिकॉर्ड के करीब अनुक्रमित कर चुका हूं, और मेरी खोज तत्काल के बगल में है; प्रत्येक खोज के लिए समय की निश्चित मात्रा नहीं है।


यह प्रोजेक्ट फूला हुआ है ( > 100kb) है और इसमें बड़ी संख्या में अन-अटेंडेड मुद्दे और पीआर हैं। मैं इसका उपयोग उन दो कारणों से नहीं करूंगा।
vsync

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

मैंने इंटरनैफ़एक्सएक्स द्वारा कॉफ़ीस्क्रिप्ट बिग्राम समाधान के साथ समस्याओं को ठीक किया और इसे एक सामान्य एन-ग्राम समाधान बनाया (आप ग्राम के आकार को अनुकूलित कर सकते हैं)।

यह टाइपस्क्रिप्ट है, लेकिन आप प्रकार एनोटेशन को निकाल सकते हैं और यह वैनिला जावास्क्रिप्ट के रूप में भी ठीक काम करता है।

/**
 * 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"))

इसे टाइपस्क्रिप्ट प्लेग्राउंड में आज़माएं


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

मेरे Google पत्रक ऐड-ऑन को Flookup कहा जाता है और इस फ़ंक्शन का उपयोग करें:

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

पैरामीटर विवरण हैं:

  1. lookupValue: जिस मूल्य को आप देख रहे हैं
  2. tableArray: वह तालिका जिसे आप खोजना चाहते हैं
  3. lookupCol: वह कॉलम जिसे आप खोजना चाहते हैं
  4. indexNum: वह कॉलम जिसे आप चाहते हैं कि डेटा से लौटाया जाए
  5. threshold: प्रतिशत समानता जिसके नीचे डेटा वापस नहीं किया जाना चाहिए
  6. rank: सबसे अच्छा मैच (यानी अगर पहला मैच आपकी पसंद के हिसाब से नहीं है)

यह आपकी आवश्यकताओं को पूरा करना चाहिए ... हालाँकि मुझे बिंदु संख्या 2 के बारे में निश्चित नहीं है।

आधिकारिक वेबसाइट पर अधिक जानकारी प्राप्त करें

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.