web-dev-qa-db-fra.com

Recherche floue Javascript qui a du sens

Je recherche une bibliothèque JavaScript de recherche floue pour filtrer un tableau. J'ai essayé d'utiliser fuzzyset.js et Fuse.js , mais les résultats sont terribles (il existe des démos que vous pouvez essayer sur les pages liées).

Après avoir fait quelques lectures sur la distance de Levenshtein, cela me semble être une mauvaise approximation de ce que les utilisateurs recherchent lorsqu'ils tapent. Pour ceux qui ne le savent pas, le système calcule combien insertions , suppressions et des substitutions sont nécessaires pour faire correspondre deux chaînes.

Un défaut évident, qui est corrigé dans le modèle de Levenshtein-Demerau, est que les deux blub et boob sont considérés comme similaires à bulbe (chacun nécessitant deux substitutions). Il est clair, cependant, que bulbe est plus similaire à blub que boob est, et le modèle que je viens de mentionner reconnaît cela en autorisant les transpositions .

Je veux l'utiliser dans le contexte de l'achèvement du texte, donc si j'ai un tableau ['international', 'splint', 'tinder'], et ma requête est int, je pense que international devrait être mieux classé que attelle, même si le le premier a un score (supérieur = pire) de 10 contre 3 pour le second.

Donc ce que je recherche (et je vais créer s'il n'existe pas), c'est une bibliothèque qui fait ce qui suit:

  • Pondère les différentes manipulations de texte
  • Pondère chaque manipulation différemment selon l'endroit où elles apparaissent dans un mot (les premières manipulations étant plus coûteuses que les dernières)
  • Renvoie une liste de résultats triés par pertinence

Quelqu'un a-t-il rencontré quelque chose comme ça? Je me rends compte que StackOverflow n'est pas l'endroit idéal pour demander des recommandations de logiciels, mais implicite (plus maintenant!) Dans ce qui précède est: est-ce que je pense à cela de la bonne façon?


Modifier

J'ai trouvé un bon papier (pdf) sur le sujet. Quelques notes et extraits:

Les fonctions Affine Edit-Distance attribuent un coût relativement inférieur à une séquence d'insertions ou de suppressions

la fonction de distance Monger-Elkan (Monge & Elkan 1996), qui est une variante affine de la fonction de distance Smith-Waterman (Durban et al. 1998) avec des paramètres de coût particuliers

Pour la distance Smith-Waterman (wikipedia) , "Au lieu de regarder la séquence totale, l'algorithme Smith-Waterman compare des segments de toutes les longueurs possibles et optimise la mesure de similitude." C'est l'approche n-gram.

Une métrique largement similaire, qui n'est pas basée sur un modèle de distance d'édition, est la métrique Jaro (Jaro 1995; 1989; Winkler 1999). Dans la littérature sur le couplage d'enregistrements, de bons résultats ont été obtenus en utilisant des variantes de cette méthode, qui est basée sur le nombre et l'ordre des caractères communs entre deux chaînes.

Une variante de ceci due à Winkler (1999) utilise également la longueur P du préfixe commun le plus long

(semble être destiné principalement aux cordes courtes)

Aux fins de l'achèvement du texte, les approches Monger-Elkan et Jaro-Winkler semblent les plus sensées. L'ajout de Winkler à la métrique Jaro pondère plus efficacement les débuts des mots. Et l'aspect affine de Monger-Elkan signifie que la nécessité de compléter un mot (qui est simplement une séquence d'additions) ne le défavorisera pas trop.

Conclusion:

le classement TFIDF a été le plus performant parmi plusieurs métriques de distance basées sur des jetons, et une métrique de distance de modification à écart affiné proposée par Monge et Elkan a été la meilleure parmi plusieurs métriques de distance de modification de chaîne. Une métrique de distance étonnamment bonne est un schéma heuristique rapide, proposé par Jaro et plus tard étendu par Winkler. Cela fonctionne presque aussi bien que le schéma Monge-Elkan, mais est un ordre de grandeur plus rapide. Un moyen simple de combiner la méthode TFIDF et Jaro-Winkler consiste à remplacer les correspondances de jetons exactes utilisées dans TFIDF par des correspondances de jetons approximatives basées sur le schéma Jaro-Winkler. Cette combinaison fonctionne légèrement mieux que Jaro-Winkler ou TFIDF en moyenne, et occasionnellement beaucoup mieux. Ses performances sont également proches d'une combinaison apprise de plusieurs des meilleures mesures prises en compte dans cet article.

73
willlma

Bonne question! Mais ma pensée est que, plutôt que d'essayer de modifier Levenshtein-Demerau, il vaut mieux essayer un algorithme différent ou combiner/pondérer les résultats de deux algorithmes.

Il me semble que les correspondances exactes ou proches du "préfixe de départ" sont quelque chose que Levenshtein-Demerau n'accorde pas de poids particulier - mais vos attentes apparentes des utilisateurs le seraient.

J'ai cherché "mieux que Levenshtein" et, entre autres, j'ai trouvé ceci:

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

Cela mentionne un certain nombre de mesures de "distance de chaîne". Trois qui semblaient particulièrement pertinentes pour votre besoin seraient:

  1. Distance de sous-chaîne commune la plus longue: Nombre minimum de symboles qui doivent être supprimés dans les deux chaînes jusqu'à ce que les sous-chaînes résultantes soient identiques.

  2. Distance q-gram: Somme des différences absolues entre les vecteurs N-gram des deux chaînes.

  3. Distance de Jaccard: 1 minue le quotient des N-grammes partagés et de tous les N-grammes observés.

Vous pouvez peut-être utiliser une combinaison pondérée (ou minimale) de ces mesures, avec Levenshtein - une sous-chaîne commune, un N-gramme commun ou Jaccard préféreront tous fortement similaire chaînes - ou peut-être essayez simplement d'utiliser Jaccard?

Selon la taille de votre liste/base de données, ces algorithmes peuvent être modérément chers. Pour une recherche floue que j'ai implémentée, j'ai utilisé un nombre configurable de N-grammes comme "clés de récupération" de la base de données, puis j'ai exécuté la mesure de distance de chaîne coûteuse pour les trier dans l'ordre de préférence.

J'ai écrit quelques notes sur la recherche de chaînes floues en SQL. Voir:

18
Thomas W

J'ai essayé d'utiliser des bibliothèques floues existantes comme Fuse.js et je les ai également trouvées terribles, alors j'en ai écrit une qui se comporte essentiellement comme la recherche de sublime. https://github.com/farzher/fuzzysort

La seule faute de frappe qu'elle autorise est une transposition. C'est assez solide (1k étoiles, 0 problème), très rapide , et gère votre cas facilement:

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

33
Farzher

Voici une technique que j'ai utilisée plusieurs fois ... Elle donne de très bons résultats. Ne fait cependant pas tout ce que vous avez demandé. En outre, cela peut être coûteux si la liste est longue.

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

Passez deux chaînes à string_similarity qui renverra un nombre entre 0 et 1.0 selon leur similitude. Cet exemple utilise Lo-Dash

Exemple d'utilisation ....

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

Aussi .... avoir un violon

Assurez-vous que votre console est ouverte ou vous ne verrez rien :)

14
InternalFX

vous pouvez jeter un oeil à Atom's https://github.com/atom/fuzzaldrin/ lib.

il est disponible sur npm, a une API simple et a bien fonctionné pour moi.

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

c'est ma fonction courte et compacte pour la correspondance floue:

function fuzzyMatch(pattern, str) {
  pattern = '.*' + pattern.split('').join('.*') + '.*';
  const re = new RegExp(pattern);
  return re.test(str);
}
1
Roi Dayan
(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/

1
guest271314

Découvrez mon module complémentaire Google Sheets appelé Flookup et utilisez cette fonction:

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

Les détails des paramètres sont les suivants:

  1. lookupValue: la valeur que vous recherchez
  2. tableArray: la table que vous souhaitez rechercher
  3. lookupCol: la colonne que vous souhaitez rechercher
  4. indexNum: la colonne à partir de laquelle les données doivent être renvoyées
  5. threshold: pourcentage de similitude en dessous duquel les données ne doivent pas être renvoyées
  6. rank: la nième meilleure correspondance (c'est-à-dire si la première correspondance ne vous convient pas)

Cela devrait répondre à vos besoins ... bien que je ne sois pas sûr du point numéro 2.

En savoir plus sur le site officiel .

0
Andrew