J'ai donc un tableau de noms javascript aléatoire ...
[@ larry, @ nicholas, @ notch] etc.
Ils commencent tous par le symbole @. Je voudrais les trier par la distance de Levenshtein afin que ceux en haut de la liste soient les plus proches du terme de recherche. À l'heure actuelle, j'ai du javascript qui utilise la fonction .grep()
de jQuery dessus en utilisant la méthode javascript .match()
autour du terme de recherche entré lors de la pression de la touche:
(code modifié depuis la première publication)
limitArr = $.grep(imTheCallback, function(n){
return n.match(searchy.toLowerCase())
});
modArr = limitArr.sort(levenshtein(searchy.toLowerCase(), 50))
if (modArr[0].substr(0, 1) == '@') {
if (atRes.childred('div').length < 6) {
modArr.forEach(function(i){
atRes.append('<div class="oneResult">' + i + '</div>');
});
}
} else if (modArr[0].substr(0, 1) == '#') {
if (tagRes.children('div').length < 6) {
modArr.forEach(function(i){
tagRes.append('<div class="oneResult">' + i + '</div>');
});
}
}
$('.oneResult:first-child').addClass('active');
$('.oneResult').click(function(){
window.location.href = 'http://hashtag.ly/' + $(this).html();
});
Il contient également des instructions if qui détectent si le tableau contient des hashtags (#) ou des mentions (@). Ignore ça. imTheCallback
est le tableau des noms, soit des hashtags ou des mentions, puis modArr
est le tableau trié. Ensuite, les éléments .atResults
Et .tagResults
Sont les éléments qu'il ajoute à chaque fois dans le tableau, ce qui forme une liste de noms basée sur les termes de recherche saisis.
I aussi ai l'algorithme de distance de Levenshtein:
var levenshtein = function(min, split) {
// Levenshtein Algorithm Revisited - WebReflection
try {
split = !("0")[0]
} catch(i) {
split = true
};
return function(a, b) {
if (a == b)
return 0;
if (!a.length || !b.length)
return b.length || a.length;
if (split) {
a = a.split("");
b = b.split("")
};
var len1 = a.length + 1,
len2 = b.length + 1,
I = 0,
i = 0,
d = [[0]],
c, j, J;
while (++i < len2)
d[0][i] = i;
i = 0;
while (++i < len1) {
J = j = 0;
c = a[I];
d[i] = [i];
while(++j < len2) {
d[i][j] = min(d[I][j] + 1, d[i][J] + 1, d[I][J] + (c != b[J]));
++J;
};
++I;
};
return d[len1 - 1][len2 - 1];
}
}(Math.min, false);
Comment puis-je travailler avec un algorithme (ou un similaire) dans mon code actuel pour le trier sans mauvaises performances?
METTRE À JOUR:
J'utilise donc maintenant la fonction Lev Dist de James Westgate. Fonctionne rapidement WAYYYY. Les performances sont donc résolues, le problème est maintenant de les utiliser avec la source ...
modArr = limitArr.sort(function(a, b){
levDist(a, searchy)
levDist(b, searchy)
});
Mon problème est maintenant la compréhension générale de l'utilisation de la méthode .sort()
. L'aide est appréciée, merci.
Merci!
J'ai écrit un correcteur orthographique en ligne il y a quelques années et j'ai implémenté un algorithme Levenshtein - car il était en ligne et pour IE8, j'ai fait beaucoup d'optimisation des performances.
var levDist = function(s, t) {
var d = []; //2d matrix
// Step 1
var n = s.length;
var m = t.length;
if (n == 0) return m;
if (m == 0) return n;
//Create an array of arrays in javascript (a descending loop is quicker)
for (var i = n; i >= 0; i--) d[i] = [];
// Step 2
for (var i = n; i >= 0; i--) d[i][0] = i;
for (var j = m; j >= 0; j--) d[0][j] = j;
// Step 3
for (var i = 1; i <= n; i++) {
var s_i = s.charAt(i - 1);
// Step 4
for (var j = 1; j <= m; j++) {
//Check the jagged ld total so far
if (i == j && d[i][j] > 4) return n;
var t_j = t.charAt(j - 1);
var cost = (s_i == t_j) ? 0 : 1; // Step 5
//Calculate the minimum
var mi = d[i - 1][j] + 1;
var b = d[i][j - 1] + 1;
var c = d[i - 1][j - 1] + cost;
if (b < mi) mi = b;
if (c < mi) mi = c;
d[i][j] = mi; // Step 6
//Damerau transposition
if (i > 1 && j > 1 && s_i == t.charAt(j - 2) && s.charAt(i - 2) == t_j) {
d[i][j] = Math.min(d[i][j], d[i - 2][j - 2] + cost);
}
}
}
// Step 7
return d[n][m];
}
Je suis arrivé à cette solution:
var levenshtein = (function() {
var row2 = [];
return function(s1, s2) {
if (s1 === s2) {
return 0;
} else {
var s1_len = s1.length, s2_len = s2.length;
if (s1_len && s2_len) {
var i1 = 0, i2 = 0, a, b, c, c2, row = row2;
while (i1 < s1_len)
row[i1] = ++i1;
while (i2 < s2_len) {
c2 = s2.charCodeAt(i2);
a = i2;
++i2;
b = i2;
for (i1 = 0; i1 < s1_len; ++i1) {
c = a + (s1.charCodeAt(i1) === c2 ? 0 : 1);
a = row[i1];
b = b < a ? (b < c ? b + 1 : c) : (a < c ? a + 1 : c);
row[i1] = b;
}
}
return b;
} else {
return s1_len + s2_len;
}
}
};
})();
Voir aussi http://jsperf.com/levenshtein-distance/12
La plupart de la vitesse a été gagnée en éliminant certaines utilisations de la baie.
Mise à jour: http://jsperf.com/levenshtein-distance/5
La nouvelle révision annihile tous les autres repères. Je cherchais spécifiquement les performances de Chromium/Firefox car je n'ai pas d'environnement de test IE8/9/10, mais les optimisations apportées devraient s'appliquer en général à la plupart des navigateurs.
Distance Levenshtein
La matrice pour effectuer Levenshtein Distance peut être réutilisée encore et encore. C'était une cible évidente pour l'optimisation (mais attention, cela impose maintenant une limite sur la longueur de la chaîne (sauf si vous deviez redimensionner la matrice dynamiquement)).
La seule option d'optimisation non poursuivie dans jsPerf Revision 5 est la mémoisation. Selon votre utilisation de Levenshtein Distance, cela pourrait aider considérablement, mais a été omis en raison de sa nature spécifique à la mise en œuvre.
// Cache the matrix. Note this implementation is limited to
// strings of 64 char or less. This could be altered to update
// dynamically, or a larger value could be used.
var matrix = [];
for (var i = 0; i < 64; i++) {
matrix[i] = [i];
matrix[i].length = 64;
}
for (var i = 0; i < 64; i++) {
matrix[0][i] = i;
}
// Functional implementation of Levenshtein Distance.
String.levenshteinDistance = function(__this, that, limit) {
var thisLength = __this.length, thatLength = that.length;
if (Math.abs(thisLength - thatLength) > (limit || 32)) return limit || 32;
if (thisLength === 0) return thatLength;
if (thatLength === 0) return thisLength;
// Calculate matrix.
var this_i, that_j, cost, min, t;
for (i = 1; i <= thisLength; ++i) {
this_i = __this[i-1];
for (j = 1; j <= thatLength; ++j) {
// Check the jagged ld total so far
if (i === j && matrix[i][j] > 4) return thisLength;
that_j = that[j-1];
cost = (this_i === that_j) ? 0 : 1; // Chars already match, no ++op to count.
// Calculate the minimum (much faster than Math.min(...)).
min = matrix[i - 1][j ] + 1; // Deletion.
if ((t = matrix[i ][j - 1] + 1 ) < min) min = t; // Insertion.
if ((t = matrix[i - 1][j - 1] + cost) < min) min = t; // Substitution.
matrix[i][j] = min; // Update matrix.
}
}
return matrix[thisLength][thatLength];
};
Distance Damerau-Levenshtein
jsperf.com/damerau-levenshtein-distance
La distance de Damerau-Levenshtein est une petite modification de la distance de Levenshtein pour inclure les transpositions. Il y a très peu à optimiser.
// Damerau transposition.
if (i > 1 && j > 1 && this_i === that[j-2] && this[i-2] === that_j
&& (t = matrix[i-2][j-2]+cost) < matrix[i][j]) matrix[i][j] = t;
Algorithme de tri
La deuxième partie de cette réponse consiste à choisir une fonction de tri appropriée. Je téléchargerai bientôt des fonctions de tri optimisées dans http://jsperf.com/sort .
J'ai implémenté une implémentation très performante du calcul de la distance du levenshtein si vous en avez encore besoin.
function levenshtein(s, t) {
if (s === t) {
return 0;
}
var n = s.length, m = t.length;
if (n === 0 || m === 0) {
return n + m;
}
var x = 0, y, a, b, c, d, g, h, k;
var p = new Array(n);
for (y = 0; y < n;) {
p[y] = ++y;
}
for (; (x + 3) < m; x += 4) {
var e1 = t.charCodeAt(x);
var e2 = t.charCodeAt(x + 1);
var e3 = t.charCodeAt(x + 2);
var e4 = t.charCodeAt(x + 3);
c = x;
b = x + 1;
d = x + 2;
g = x + 3;
h = x + 4;
for (y = 0; y < n; y++) {
k = s.charCodeAt(y);
a = p[y];
if (a < c || b < c) {
c = (a > b ? b + 1 : a + 1);
}
else {
if (e1 !== k) {
c++;
}
}
if (c < b || d < b) {
b = (c > d ? d + 1 : c + 1);
}
else {
if (e2 !== k) {
b++;
}
}
if (b < d || g < d) {
d = (b > g ? g + 1 : b + 1);
}
else {
if (e3 !== k) {
d++;
}
}
if (d < g || h < g) {
g = (d > h ? h + 1 : d + 1);
}
else {
if (e4 !== k) {
g++;
}
}
p[y] = h = g;
g = d;
d = b;
b = c;
c = a;
}
}
for (; x < m;) {
var e = t.charCodeAt(x);
c = x;
d = ++x;
for (y = 0; y < n; y++) {
a = p[y];
if (a < c || d < c) {
d = (a > d ? d + 1 : a + 1);
}
else {
if (e !== s.charCodeAt(y)) {
d = c + 1;
}
else {
d = c;
}
}
p[y] = d;
c = a;
}
h = d;
}
return h;
}
C'était ma réponse à une question similaire SO Implémentation Levenshtein Javascript la plus rapide
Mise à jour
Une version améliorée de ce qui précède est maintenant sur github/npm voir https://github.com/gustf/js-levenshtein
Je suggérerais certainement d'utiliser une meilleure méthode Levenshtein comme celle de la réponse de @James Westgate.
Cela dit, les manipulations DOM sont souvent très coûteuses. Vous pouvez certainement améliorer votre utilisation de jQuery.
Vos boucles sont plutôt petites dans l'exemple ci-dessus, mais concaténer le code HTML généré pour chaque oneResult
en une seule chaîne et en faire une append
à la fin de la boucle sera beaucoup plus efficace.
Vos sélecteurs sont lents. $('.oneResult')
recherchera tous les éléments dans le DOM et testera leur className
dans les anciens IE navigateurs. Vous voudrez peut-être envisager quelque chose comme atRes.find('.oneResult')
pour étendre la recherche.
Dans le cas de l'ajout des gestionnaires click
, nous souhaitons peut-être mieux éviter de définir des gestionnaires sur chaque keyup
. Vous pouvez tirer parti de la délégation d'événements en définissant un seul gestionnaire sur atRest
pour tous les résultats dans le même bloc que vous définissez le gestionnaire keyup
:
atRest.on('click', '.oneResult', function(){
window.location.href = 'http://hashtag.ly/' + $(this).html();
});
Voir http://api.jquery.com/on/ pour plus d'informations.
La façon évidente de le faire est de mapper chaque chaîne à une paire (distance, chaîne), puis de trier cette liste, puis de supprimer à nouveau les distances. De cette façon, vous vous assurez que la distance de Levenstein ne doit être calculée qu'une seule fois. Peut-être aussi fusionner les doublons en premier.
Je viens d'écrire une nouvelle révision: http://jsperf.com/levenshtein-algorithms/16
function levenshtein(a, b) {
if (a === b) return 0;
var aLen = a.length;
var bLen = b.length;
if (0 === aLen) return bLen;
if (0 === bLen) return aLen;
var len = aLen + 1;
var v0 = new Array(len);
var v1 = new Array(len);
var i = 0;
var j = 0;
var c2, min, tmp;
while (i < len) v0[i] = i++;
while (j < bLen) {
c2 = b.charAt(j++);
v1[0] = j;
i = 0;
while (i < aLen) {
min = v0[i] - (a.charAt(i) === c2 ? 1 : 0);
if (v1[i] < min) min = v1[i];
if (v0[++i] < min) min = v0[i];
v1[i] = min + 1;
}
tmp = v0;
v0 = v1;
v1 = tmp;
}
return v0[aLen];
}
Cette révision est plus rapide que les autres. Fonctionne même sur IE =)