web-dev-qa-db-fra.com

Algorithme de saisie semi-automatique?

Je fais référence à l'algorithme utilisé pour donner des suggestions de requête lorsqu'un utilisateur tape un terme de recherche dans Google.

Je suis principalement intéressé par: 1. Les résultats les plus importants (les requêtes les plus probables plutôt que tout ce qui correspond) 2. Les sous-chaînes de correspondance 3. Les correspondances floues

Je sais que vous pourriez utiliser Trie ou un tri généralisé pour trouver des correspondances, mais cela ne répondrait pas aux exigences ci-dessus ...

Questions similaires posées plus tôt ici

58
StackUnderflow

Pour (heh) des algorithmes de correspondance de chaîne floue/partielle impressionnants, consultez Damn Cool Algorithms:

Ceux-ci ne remplacent pas les essais, mais empêchent plutôt les recherches par force brute dans les essais - ce qui est toujours une énorme victoire. Ensuite, vous voulez probablement un moyen de délimiter la taille du trie:

  • conserver un trie de N récents/premiers mots utilisés à l'échelle mondiale;
  • pour chaque utilisateur, conservez un tri de N récents/premiers mots pour cet utilisateur.

Enfin, vous voulez empêcher les recherches autant que possible ...

  • résultats de recherche dans le cache: si l'utilisateur clique sur les résultats de recherche, vous pouvez les afficher très rapidement, puis récupérer de manière asynchrone la recherche partielle/floue complète.
  • résultats de la recherche de précalcul: si l'utilisateur a tapé "appl", il est probable qu'il continue avec "Apple", "applique".
  • prefetch data: par exemple, une application web peut envoyer un ensemble de résultats plus petit au navigateur, suffisamment petit pour rendre la recherche par force brute dans JS viable.
56
fearlesstost

Je voudrais juste dire ... Une bonne solution à ce problème va incorporer plus qu'un arbre de recherche ternaire. Ngrams et Shingles (Phrases) sont nécessaires. Les erreurs de limite de mot doivent également être détectées. "hell o" devrait être "bonjour" ... et "whitesocks" devrait être "chaussettes blanches" - ce sont des étapes de prétraitement. Si vous ne prétraitez pas correctement les données, vous n'obtiendrez pas de résultats de recherche précieux. Les arbres de recherche ternaire sont un composant utile pour déterminer ce qu'est un mot, et également pour implémenter la devinette des mots associés lorsqu'un mot tapé n'est pas un mot valide dans l'index.

L'algorithme de google effectue la suggestion et la correction de phrases. L'algorithme de Google a également une certaine notion de contexte ... si le premier mot que vous recherchez est lié à la météo et que vous les combinez "weatherforcst" vs "monsoonfrcst" vs "deskfrcst" - je suppose que dans les coulisses les classements sont modifiés dans le suggestion basée sur le premier mot rencontré - prévisions et météo sont des mots liés donc les prévisions obtiennent un rang élevé dans la supposition Did-You-Mean.

Partiels de mots (ngrams), termes de phrases (bardeaux), proximité de mots (Word-clustering-index), arbre de recherche ternaire (recherche de mots).

8
Ben DeMott

L'algorithme exact de Google est inconnu, mais il est dit pour fonctionner par analyse statistique des entrées des utilisateurs. Une approche qui ne convient pas à la plupart des cas. Plus généralement, la complétion automatique est implémentée à l'aide de l'un des éléments suivants:

  • Arbres . En indexant le texte consultable dans une structure arborescente (arbre de préfixe, arbre de suffixe, dawg, etc.), on peut exécuter des recherches très rapides au détriment du stockage en mémoire. La traversée de l'arbre peut être adaptée pour une correspondance approximative.
  • Partitionnement des motifs . En partitionnant le texte en jetons (ngrams), on peut exécuter des recherches d'occurrences de modèle en utilisant un schéma de hachage simple.
  • Filtrage . Trouvez un ensemble de correspondances potentielles, puis appliquez un algorithme séquentiel pour vérifier chaque candidat.

Jetez un oeil à complètement , une Java bibliothèque de saisie semi-automatique qui implémente certains de ces derniers concepts.

5

Il existe des outils tels que soundex et distance levenshtein qui peuvent être utilisés pour trouver des correspondances floues dans une certaine plage.

Soundex trouve des mots qui semblent similaires et la distance levenshtein trouve des mots qui sont à une certaine distance d'édition d'un autre mot.

4
Ólafur Waage

Jetez un oeil à algorithme de barre génial de Firefox

La suggestion de Google est utile, car elle prend en compte les millions de requêtes populaires + vos requêtes liées antérieures.

Il n'a cependant pas un bon algorithme de complétion/UI:

  1. Ne fait pas de sous-chaînes
  2. On dirait un algorithme de préfixe de limite de mot relativement simple.
    Par exemple: Essayez Tomcat tut -> suggère correctement "Tutoriel Tomcat". Essayez maintenant Tomcat rial -> aucune suggestion) -:
  3. Ne prend pas en charge "vouliez-vous dire?" - comme dans les résultats de recherche google.
3
Dekel

Pour les sous-chaînes et les correspondances floues, l'algorithme de distance de Levenshtein a assez bien fonctionné pour moi. Bien que j'admette, cela ne semble pas être aussi parfait que les implémentations industrielles de la saisie semi-automatique/suggèrent. Google et Intellisense de Microsoft font un meilleur travail, je pense parce qu'ils ont affiné cet algorithme de base pour peser le type d'opérations d'édition qu'il faut pour faire correspondre les chaînes dissemblables. Par exemple. la transposition de deux caractères ne devrait probablement compter que pour 1 opération, et non 2 (insertion et suppression).

Mais même ainsi, je trouve que c'est assez proche. Voici son implémentation en C # ...

// This is the traditional Levenshtein Distance algorithem, though I've tweaked it to make
// it more like Google's autocomplete/suggest.  It returns the number of operations 
// (insert/delete/substitute) required to change one string into another, with the 
// expectation that userTyped is only a partial version of fullEntry.
// Gives us a measurement of how similar the two strings are.
public static int EditDistance(string userTyped, string fullEntry)
{
    if (userTyped.Length == 0) // all entries are assumed to be fully legit possibilities 
        return 0; // at this point, because the user hasn't typed anything.

    var inx = fullEntry.IndexOf(userTyped[0]);
    if (inx < 0) // If the 1st character doesn't exist anywhere in the entry, it's not
        return Int32.MaxValue; // a possible match.

    var lastInx = inx;
    var lastMatchCount = 0;
TryAgain:
    // Is there a better starting point?
    var len = fullEntry.Length - inx;
    var matchCount = 1;
    var k = 1;
    for (; k < len; k++)
    {
        if (k == userTyped.Length || userTyped[k] != fullEntry[k + inx])
        {
            if (matchCount > lastMatchCount)
            {
                lastMatchCount = matchCount;
                lastInx = inx;
            }
            inx = fullEntry.IndexOf(userTyped[0], inx + 1);
            matchCount = 0;
            if (inx > 0)
                goto TryAgain;
            else
                break;
        }
        else
            matchCount++;
    }
    if (k == len && matchCount > lastMatchCount)
        lastInx = inx;

    if (lastInx > 0)
        fullEntry = fullEntry.Substring(lastInx); // Jump to 1st character match, ignoring previous values 

    // The start of the Levenshtein Distance algorithem.
    var m = userTyped.Length;
    var n = Math.Min(m, fullEntry.Length);

    int[,] d = new int[m + 1, n + 1]; // "distance" - meaning number of operations.

    for (var i = 0; i <= m; i++)
        d[i, 0] = i; // the distance of any first string to an empty second string
    for (var j = 0; j <= n; j++)
        d[0, j] = j; // the distance of any second string to an empty first string

    for (var j = 1; j <= n; j++)
        for (var i = 1; i <= m; i++)
            if (userTyped[i - 1] == fullEntry[j - 1])
                d[i, j] = d[i - 1, j - 1];       // no operation required
            else
                d[i, j] = Math.Min
                           (
                             d[i - 1, j] + 1,  // a deletion
                             Math.Min(
                             d[i, j - 1] + 1,  // an insertion
                             d[i - 1, j - 1] + 1 // a substitution
                             )
                           );

    return d[m, n];
}
2
Gabe Halsmer

Si vous recherchez une conception globale du problème, essayez de lire le contenu sur https://www.interviewbit.com/problems/search-typeahead/ .

Ils commencent par construire la saisie semi-automatique à travers une approche naïve de l'utilisation d'un trie, puis s'appuient dessus. Ils expliquent également les techniques d'optimisation comme l'échantillonnage et les mises à jour hors ligne pour répondre à des cas d'utilisation spécifiques.

Pour garder la solution évolutive, vous devez répartir vos données de tri intelligemment.

1
user3273189

Je pense qu'il serait préférable de construire un trie spécialisé, plutôt que de poursuivre une structure de données complètement différente.

Je pouvais voir cette fonctionnalité se manifester dans un trie dans lequel chaque feuille avait un champ qui reflétait la fréquence des recherches de son mot correspondant.

La méthode de requête de recherche afficherait les nœuds terminaux descendants avec les plus grandes valeurs calculées à partir de la multiplication de la distance à chaque nœud terminal descendant par la fréquence de recherche associée à chaque nœud terminal descendant.

La structure des données (et par conséquent l'algorithme) que Google utilise est probablement beaucoup plus compliquée, prenant potentiellement en compte un grand nombre d'autres facteurs, tels que les fréquences de recherche de votre propre compte (et l'heure de la journée ... et la saison ... de la météo) ... et phase lunaire ... et ...). Cependant, je crois que la structure de base des données trie peut être étendue à tout type de préférence de recherche spécialisée en incluant des champs supplémentaires à chacun des nœuds et en utilisant ces champs dans la méthode de requête de recherche.

0
T.K.