Algorithme pour autocomplete?

Je me réfère à l’algorithme utilisé pour donner des suggestions de requête lorsqu’un utilisateur tape un terme de recherche dans Google.

Je m’intéresse principalement à la manière dont l’algorithme de Google est capable de montrer: 1. Les résultats les plus importants (requêtes les plus probables plutôt que celles qui correspondent) 2. Correspondance des sous-chaînes 3. Correspondances floues

Je sais que tu pourrais utiliser Trie ou Trie généralisé pour trouver des correspondances, mais cela ne répondrait pas aux exigences ci-dessus …

Des questions similaires posées plus tôt ici

Pour les algorithmes de correspondance de chaînes partielles / partielles impressionnants, consultez les algorithmes de Damn Cool:

Celles-ci ne remplacent pas les tentatives, mais empêchent plutôt les recherches en force brute dans les essais – ce qui est encore une énorme victoire. Ensuite, vous voulez probablement un moyen de lier la taille du sortinge:

  • garder un sortingo de mots N récents / top utilisés globalement;
  • pour chaque utilisateur, conservez un sortingo de mots N récents / supérieurs pour cet utilisateur.

Enfin, vous voulez empêcher les recherches lorsque cela est possible …

  • Résultats de la recherche dans le cache: si l’utilisateur clique sur un résultat de recherche, vous pouvez le traiter très rapidement, puis récupérer de manière asynchrone la recherche partielle / complète.
  • résultats de la recherche de précompense: si l’utilisateur a tapé “appl”, il est probable qu’il continue avec “apple”, “apply”.
  • Pré-récupérer les données: par exemple, une application Web peut envoyer un ensemble de résultats plus petit au navigateur, suffisamment petit pour rendre la recherche par force dans JS viable.

Je voudrais juste dire … Une bonne solution à ce problème va incorporer plus qu’un arbre de recherche ternaire. Les Ngrams et les Bardeaux (Phrases) sont nécessaires. Des erreurs de limites de mots doivent également être détectées. “hell o” devrait être “hello” … et “whitesocks” devrait être “white socks” – 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 ternaires sont un composant utile pour déterminer ce qu’est un mot, et également pour implémenter la recherche de mots liés lorsqu’un mot saisi n’est pas un mot valide dans l’index.

L’algorithme google effectue la suggestion de phrase et la correction. L’algorithme de google a aussi un concept 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” – mes suppositions sont en arrière-plan La suggestion basée sur le premier mot rencontré – les prévisions et la météo sont des mots apparentés.

partiels de mots (ngrams), termes de phrase (bardeaux), proximité de mots (indexation de mots), arbre de recherche ternaire (recherche de mots).

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

Soundex trouve des mots qui sonnent de manière similaire et la distance levenshtein trouve des mots qui se trouvent à une certaine distance d’édition d’un autre mot.

Jetez un coup d’oeil à l’algorithme de barre Awesome de Firefox

Google suggère qu’il est utile, car il prend en compte les millions de requêtes populaires et vos requêtes liées passées.

Il ne dispose pas d’un bon algorithme d’achèvement / interface utilisateur:

  1. Ne fait pas de sous-chaînes
  2. Cela ressemble à un algorithme de préfixe de limite de mots relativement simple.
    Par exemple: Essayez tomcat tut -> suggérez correctement “tutoriel tomcat”. Maintenant, essayez tomcat rial -> aucune suggestion) -:
  3. Ne supporte pas “tu voulais dire?” – comme dans les résultats de recherche google.

L’algorithme exact de Google est inconnu, mais il est dit qu’il fonctionne par parsing statistique des entrées des utilisateurs. Une approche inadaptée à la plupart des cas. Plus généralement, l’auto-complétion est implémentée en utilisant l’un des éléments suivants:

  • Arbres En indexant le texte consultable dans une arborescence (arbre de préfixe, arborescence de suffixes, dawg, etc.), il est possible d’exécuter des recherches très rapides au désortingment du stockage en mémoire. La traversée de l’arbre peut être adaptée pour une correspondance approximative.
  • Partitionnement de motifs . En partitionnant le texte en jetons (ngrams), il est possible d’effectuer des recherches sur les occurrences de motifs à l’aide d’un simple schéma de hachage.
  • Filtrage Trouvez un ensemble de correspondances potentielles, puis appliquez un algorithme séquentiel pour vérifier chaque candidat.

Jetez un coup d’oeil à une bibliothèque d’auto- complétion Java qui implémente certains de ces concepts.

Pour les sous-chaînes et les correspondances floues, l’algorithme de distance de Levenshtein a plutôt bien fonctionné pour moi. Bien que je puisse admettre que cela ne semble pas être aussi parfait que les implémentations d’autocomplétion / suggestion de l’indussortinge. Je pense que Google et Intellisense font tous deux un meilleur travail, car ils ont affiné cet algorithme de base pour évaluer le type d’opérations de assembly nécessaires pour faire correspondre les chaînes différentes. Par exemple, la transposition de deux caractères ne devrait probablement compter que pour 1 opération, et non 2 (un insert & delete).

Mais même si je trouve cela assez proche. Voici l’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 ssortingng into another, with the // expectation that userTyped is only a partial version of fullEntry. // Gives us a measurement of how similar the two ssortingngs are. public static int EditDistance(ssortingng userTyped, ssortingng fullEntry) { if (userTyped.Length == 0) // all ensortinges 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.Subssortingng(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]; } 

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 l’auto-complétion par le biais d’une approche naïve consistant à utiliser un sortinge, puis à en tirer parti. Ils expliquent également les techniques d’optimisation telles que l’échantillonnage et les mises à jour hors ligne pour répondre à des cas d’utilisation spécifiques.

Pour que la solution rest évolutive, vous devez partager intelligemment vos données.

Je pense qu’il serait préférable de construire un système 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 sortinge où 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 feuilles descendants avec les plus grandes valeurs calculées en multipliant la distance à chaque nœud feuille descendant par la fréquence de recherche associée à chaque nœud feuille descendant.

La structure de 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 compte spécifique (et l’heure et la météo). … et la phase lunaire … et …). Cependant, je crois que la structure de données de base peut être étendue à tout type de préférence de recherche spécialisée en incluant des champs supplémentaires dans chacun des nœuds et en utilisant ces champs dans la méthode de requête de recherche.