Comment trier les chaînes en JavaScript
-
09-06-2019 - |
Question
J'ai une liste d'objets que je souhaite trier en fonction d'un champ attr
de type chaîne.J'ai essayé d'utiliser -
list.sort(function (a, b) {
return a.attr - b.attr
})
mais j'ai trouvé que -
ne semble pas fonctionner avec des chaînes en JavaScript.Comment puis-je trier une liste d'objets en fonction d'un attribut de type chaîne ?
La solution
Utiliser String.prototype.localeCompare
un selon votre exemple :
list.sort(function (a, b) {
return ('' + a.attr).localeCompare(b.attr);
})
Nous forçons a.attr à être une chaîne pour éviter les exceptions. localeCompare
a été soutenu depuis Internet Explorer 6 et Firefox 1.Vous pouvez également voir le code suivant utilisé qui ne respecte pas les paramètres régionaux :
if (item1.attr < item2.attr)
return -1;
if ( item1.attr > item2.attr)
return 1;
return 0;
Autres conseils
Une réponse mise à jour (octobre 2014)
J'étais vraiment ennuyé par cet ordre de tri naturel des chaînes, j'ai donc pris pas mal de temps pour enquêter sur ce problème.J'espère que ça aide.
Longue histoire courte
localeCompare()
la prise en charge des personnages est géniale, utilisez-la simplement.Comme l'a souligné Shog9
, la réponse à votre question est :
return item1.attr.localeCompare(item2.attr);
Bogues trouvés dans toutes les implémentations javascript personnalisées de « ordre de tri des chaînes naturelles »
Il existe de nombreuses implémentations personnalisées, essayant de faire une comparaison de chaînes plus précisément appelée "ordre de tri naturel des chaînes".
En "jouant" avec ces implémentations, j'ai toujours remarqué un étrange choix "d'ordre de tri naturel", ou plutôt des erreurs (ou des omissions dans le meilleur des cas).
En règle générale, les caractères spéciaux (espace, tiret, esperluette, crochets, etc.) ne sont pas traités correctement.
Vous les trouverez alors mélangés à différents endroits, généralement :
- certains seront entre le « Z » majuscule et le « a » minuscule
- certains seront entre le « 9 » et le « A » majuscule
- certains seront après le « z » minuscule
Alors qu'on aurait pu s'attendre à ce que tous les caractères spéciaux soient "regroupés" en un seul endroit, à l'exception peut-être du caractère spécial espace (qui serait toujours le premier caractère).C'est-à-dire soit tout avant les chiffres, soit tout entre les chiffres et les lettres (les minuscules et les majuscules étant "ensemble" l'une après l'autre), ou tout après les lettres.
Ma conclusion est qu'ils ne parviennent pas tous à fournir un ordre cohérent lorsque je commence à ajouter des caractères à peine inhabituels (c.-à-d.caractères avec des signes diacritiques ou des caractères tels que tiret, point d'exclamation, etc.).
Recherche sur les implémentations personnalisées :
Natural Compare Lite
https://github.com/litejs/natural-compare-lite :Échec du tri cohérent https://github.com/litejs/natural-compare-lite/issues/1 et http://jsbin.com/bevututodavi/1/edit?js,console , tri des caractères latins de base http://jsbin.com/bevututodavi/5/edit?js,consoleNatural Sort
https://github.com/javve/natural-sort :Échec du tri cohérent, voir le problème https://github.com/javve/natural-sort/issues/7 et voir le tri des caractères latins de base http://jsbin.com/cipimosedoqe/3/edit?js,consoleJavascript Natural Sort
https://github.com/overset/javascript-natural-sort :semble plutôt négligé depuis février 2012, ne parvient pas à trier de manière cohérente, voir problème https://github.com/overset/javascript-natural-sort/issues/16Alphanum
http://www.davekoelle.com/files/alphanum.js , Échec du tri cohérent, voir http://jsbin.com/tuminoxifuyo/1/edit?js,console
Implémentations natives de « l'ordre de tri des chaînes naturelles » des navigateurs via localeCompare()
localeCompare()
l'implémentation la plus ancienne (sans les arguments locales et options) est prise en charge par IE6+, voir http://msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx (faites défiler jusqu'à la méthode localeCompare()).L'intégré localeCompare()
La méthode fait un bien meilleur travail de tri, même des caractères internationaux et spéciaux.Le seul problème lors de l'utilisation du localeCompare()
la méthode est que "les paramètres régionaux et l'ordre de tri utilisés dépendent entièrement de l'implémentation".En d’autres termes, lors de l’utilisation de localeCompare tel que stringOne.localeCompare(stringTwo) :Firefox, Safari, Chrome et IE ont un ordre de tri différent pour les chaînes.
Recherche sur les implémentations natives du navigateur :
- http://jsbin.com/beboroyifomu/1/edit?js,console - comparaison des caractères latins de base avec localeCompare()http://jsbin.com/viyucavudela/2/ - comparaison de base des caractères latins avec localeCompare() pour les tests sur IE8
- http://jsbin.com/beboroyifomu/2/edit?js,console - caractères latins de base dans la comparaison de chaînes :contrôle de cohérence dans la chaîne vs quand un personnage est seul
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare - IE11+ prend en charge les nouveaux arguments locales et options
Difficulté de "l'ordre de tri naturel des chaînes"
Implémenter un algorithme solide (c'est-à-dire :cohérent mais couvrant également un large éventail de personnages) est une tâche très difficile.UTF8 contient plus de 2000 caractères & couvre plus de 120 scripts (langues).Enfin, il existe quelques spécifications pour ces tâches, elles sont appelées « Algorithme de classement Unicode », qui peuvent être trouvées sur http://www.unicode.org/reports/tr10/ .Vous pouvez trouver plus d'informations à ce sujet sur cette question que j'ai postée https://softwareengineering.stackexchange.com/questions/257286/is-there-any-langue-agnostic-spécification-for-string-natural-sorting-order
Conclusion finale
Donc, compte tenu du niveau actuel de support fourni par les implémentations personnalisées javascript que j'ai rencontrées, nous ne verrons probablement jamais rien de comparable à la prise en charge de tous ces caractères et scripts (langues).Par conséquent, je préférerais utiliser la méthode localeCompare() native des navigateurs.Oui, il présente l'inconvénient de ne pas être cohérent entre les navigateurs, mais des tests de base montrent qu'il couvre une gamme beaucoup plus large de caractères, permettant des ordres de tri solides et significatifs.
Ainsi, comme l'a souligné Shog9
, la réponse à votre question est :
return item1.attr.localeCompare(item2.attr);
Lectures complémentaires :
- https://softwareengineering.stackexchange.com/questions/257286/is-there-any-langue-agnostic-spécification-for-string-natural-sorting-order
- Comment effectuer une comparaison de chaînes en JavaScript ?
- Javascript :sorte naturelle de chaînes alphanumériques
- Trier un tableau d'éléments numériques et alphabétiques (tri naturel)
- Trier un tableau mixte alphanumérique
- https://web.archive.org/web/20130929122019/http://my.opera.com/GreyWyvern/blog/show.dml/1671288
- https://web.archive.org/web/20131005224909/http://www.davekoelle.com/alphanum.html
- http://snipplr.com/view/36012/javascript-natural-sort/
- http://blog.codinghorror.com/sorting-for-humans-natural-sort-order/
Merci à la belle réponse de Shog9, qui m'a mis dans la "bonne" direction je crois
Réponse (en ECMAScript moderne)
list.sort((a, b) => (a.attr > b.attr) - (a.attr < b.attr))
Ou
list.sort((a, b) => +(a.attr > b.attr) || -(a.attr < b.attr))
Description
Convertir une valeur booléenne en un nombre donne ce qui suit :
true
->1
false
->0
Considérez trois modèles possibles :
- x est plus grand que y :
(x > y) - (y < x)
->1 - 0
->1
- x est égal à y :
(x > y) - (y < x)
->0 - 0
->0
- x est plus petit que y :
(x > y) - (y < x)
->0 - 1
->-1
(Alternative)
- x est plus grand que y :
+(x > y) || -(x < y)
->1 || 0
->1
- x est égal à y :
+(x > y) || -(x < y)
->0 || 0
->0
- x est plus petit que y :
+(x > y) || -(x < y)
->0 || -1
->-1
Ces logiques sont donc équivalentes aux fonctions typiques du comparateur de tri.
if (x == y) {
return 0;
}
return x > y ? 1 : -1;
Vous devez utiliser > ou < et == ici.La solution serait donc :
list.sort(function(item1, item2) {
var val1 = item1.attr,
val2 = item2.attr;
if (val1 == val2) return 0;
if (val1 > val2) return 1;
if (val1 < val2) return -1;
});
Cela me dérangeait depuis longtemps, alors j'ai finalement fait des recherches à ce sujet et je vous ai donné cette longue raison expliquant pourquoi les choses sont telles qu'elles sont.
Du spécification:
Section 11.9.4 The Strict Equals Operator ( === )
The production EqualityExpression : EqualityExpression === RelationalExpression
is evaluated as follows:
- Let lref be the result of evaluating EqualityExpression.
- Let lval be GetValue(lref).
- Let rref be the result of evaluating RelationalExpression.
- Let rval be GetValue(rref).
- Return the result of performing the strict equality comparison
rval === lval. (See 11.9.6)
Alors maintenant nous passons à 11.9.6
11.9.6 The Strict Equality Comparison Algorithm
The comparison x === y, where x and y are values, produces true or false.
Such a comparison is performed as follows:
- If Type(x) is different from Type(y), return false.
- If Type(x) is Undefined, return true.
- If Type(x) is Null, return true.
- If Type(x) is Number, then
...
- If Type(x) is String, then return true if x and y are exactly the
same sequence of characters (same length and same characters in
corresponding positions); otherwise, return false.
C'est ça. L'opérateur triple égal appliqué aux chaînes renvoie vrai si les arguments sont exactement les mêmes chaînes (même longueur et mêmes caractères aux positions correspondantes).
Donc ===
fonctionnera dans les cas où nous essayons de comparer des chaînes qui pourraient provenir de différentes sources, mais dont nous savons qu'elles finiront par avoir les mêmes valeurs - un scénario assez courant pour les chaînes en ligne dans notre code.Par exemple, si nous avons une variable nommée connection_state
, et nous souhaitons savoir lequel des états suivants ['connecting', 'connected', 'disconnecting', 'disconnected']
est-ce en ce moment, on peut directement utiliser le ===
.
Mais il y a plus.Juste au-dessus de la version 11.9.4, il y a une courte note :
NOTE 4
Comparison of Strings uses a simple equality test on sequences of code
unit values. There is no attempt to use the more complex, semantically oriented
definitions of character or string equality and collating order defined in the
Unicode specification. Therefore Strings values that are canonically equal
according to the Unicode standard could test as unequal. In effect this
algorithm assumes that both Strings are already in normalized form.
Hmm.Et maintenant?Les chaînes obtenues de l'extérieur peuvent, et seront très probablement, être d'étranges Unicodey, et notre doux ===
ne leur rendra pas justice.Arrive localeCompare
à la rescousse:
15.5.4.9 String.prototype.localeCompare (that)
...
The actual return values are implementation-defined to permit implementers
to encode additional information in the value, but the function is required
to define a total ordering on all Strings and to return 0 when comparing
Strings that are considered canonically equivalent by the Unicode standard.
Nous pouvons rentrer à la maison maintenant.
tl;dr;
Pour comparer des chaînes en javascript, utilisez localeCompare
;si vous savez que les chaînes n'ont pas de composants non-ASCII car ce sont, par exemple, des constantes internes du programme, alors ===
fonctionne aussi.
Fonction de flèche ternaire imbriquée
(a,b) => (a < b ? -1 : a > b ? 1 : 0)
Dans votre opération dans votre question initiale, vous effectuez l'opération suivante :
item1.attr - item2.attr
Donc, en supposant que ce soient des chiffres (c'est-à-direitem1.attr = "1", item2.attr = "2") Vous pouvez toujours utiliser l'opérateur "===" (ou d'autres évaluateurs stricts) à condition de garantir le type.Les éléments suivants devraient fonctionner :
return parseInt(item1.attr) - parseInt(item2.attr);
S'ils sont alphanumériques, utilisez localCompare().
list.sort(function(item1, item2){
return +(item1.attr > item2.attr) || +(item1.attr === item2.attr) - 1;
})
Comment ils fonctionnent des échantillons :
+('aaa'>'bbb')||+('aaa'==='bbb')-1
+(false)||+(false)-1
0||0-1
-1
+('bbb'>'aaa')||+('bbb'==='aaa')-1
+(true)||+(false)-1
1||0-1
1
+('aaa'>'aaa')||+('aaa'==='aaa')-1
+(false)||+(true)-1
0||1-1
0
<!doctype html>
<html>
<body>
<p id = "myString">zyxtspqnmdba</p>
<p id = "orderedString"></p>
<script>
var myString = document.getElementById("myString").innerHTML;
orderString(myString);
function orderString(str) {
var i = 0;
var myArray = str.split("");
while (i < str.length){
var j = i + 1;
while (j < str.length) {
if (myArray[j] < myArray[i]){
var temp = myArray[i];
myArray[i] = myArray[j];
myArray[j] = temp;
}
j++;
}
i++;
}
var newString = myArray.join("");
document.getElementById("orderedString").innerHTML = newString;
}
</script>
</body>
</html>
var str = ['v','a','da','c','k','l']
var b = str.join('').split('').sort().reverse().join('')
console.log(b)