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
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:
Enfin, vous voulez empêcher les recherches autant que possible ...
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).
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:
Jetez un oeil à complètement , une Java bibliothèque de saisie semi-automatique qui implémente certains de ces derniers concepts.
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.
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:
Tomcat tut
-> suggère correctement "Tutoriel Tomcat". Essayez maintenant Tomcat rial
-> aucune suggestion) -: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];
}
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.
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.