Comment trier les chaînes en JavaScript

344

J'ai une liste d'objets que je souhaite trier en fonction d'un champ attrde type chaîne. J'ai essayé d'utiliser-

list.sort(function (a, b) {
    return a.attr - b.attr
})

mais a constaté que -cela ne semble pas fonctionner avec les chaînes en JavaScript. Comment puis-je trier une liste d'objets en fonction d'un attribut avec une chaîne de type?

airportyh
la source
1
voir JavaScript case insensitive string comparisonsur stackoverflow.com/questions/2140627/…
Adrien Be
Pour une solution "internationalisée" rapide (seulement partiellement, je suppose que cela peut ne pas couvrir tous les accents dans le monde), vous pouvez simplement ignorer les accents, c'est-à-dire les supprimer. Ensuite, ne faites que votre comparaison de chaînes, voir Javascript : remove accents/diacritics in stringssur stackoverflow.com/questions/990904/…
Adrien Be
2
Assez drôle Jeff Atwood lui-même a écrit un blog sur ce problème courant en 2007, voir blog.codinghorror.com/sorting-for-humans-natural-sort-order
Adrien Be

Réponses:

622

Utilisez String.prototype.localeCompareun par 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. localeCompareest pris en charge 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;
Shog9
la source
81
Avant que quiconque ne fasse la même erreur précipitée que moi, c'est local e Compare, pas localCompare.
ento
12
La première solution considérera "A" après "z" mais avant "Z" car il fait une comparaison sur la valeur ASCII du caractère. localeCompare()ne rencontre pas ce problème mais ne comprend pas les chiffres, vous obtiendrez donc ["1", "10", "2"] comme pour les comparaisons de tri dans la plupart des langues. si vous voulez trier votre interface utilisateur, consultez l'algorithme de tri alphanum / naturel stackoverflow.com/questions/4340227/… ou stackoverflow.com/questions/4321829/…
Dead.Rabit
2
Notez que cela localeCompare()n'est pris en charge que dans les navigateurs modernes: IE11 + au moment de la rédaction, voir developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Adrien Be
3
Non, je veux dire la première ligne du tableau, @Adrien - IE prend localeCompare()en charge le retour de nombreuses versions, mais ne prend pas en charge la spécification des paramètres régionaux jusqu'à la version 11. Notez également les questions liées à Dead.Rabit.
Shog9
3
@ Shog9 ma mauvaise, on dirait que c'est supporté depuis IE6! voir (défilement / recherche jusqu'à la méthode localeCompare ()) sur msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx . Une chose à noter cependant, dans les anciennes implémentations où nous n'utilisons pas les arguments de paramètres régionaux et d'options (celui utilisé avant IE11), les paramètres régionaux et l'ordre de tri utilisés dépendent entièrement de l'implémentation , en d'autres termes: Firefox, Safari, Chrome et IE font PAS trier les chaînes dans le même ordre. voir code.google.com/p/v8/issues/detail?id=459
Adrien Be
166

Une réponse mise à jour (octobre 2014)

J'étais vraiment ennuyé par cet ordre de tri naturel des chaînes, j'ai donc pris un certain temps pour enquêter sur ce problème. J'espère que ça aide.

Longue histoire courte

localeCompare()le support des personnages est dur à cuire, utilisez-le. Comme indiqué par Shog9, la réponse à votre question est:

return item1.attr.localeCompare(item2.attr);

Bogues trouvés dans toutes les implémentations javascript personnalisées "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 des chaînes naturelles"

En "jouant" avec ces implémentations, j'ai toujours remarqué un choix étrange "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 ensuite apparaissant mélangés à différents endroits, généralement cela pourrait être:

  • certains seront entre le «Z» majuscule et le «a» minuscule
  • certains seront entre le «9» et le «A» majuscule
  • certains seront après «z» en minuscules

Quand 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 tous avant les nombres, soit tous entre les chiffres et les lettres (les minuscules et les majuscules étant «ensemble» l'un après l'autre), ou tous 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'est-à-dire des caractères avec des signes diacritiques ou des caractères tels qu'un tiret, un point d'exclamation, etc.).

Recherche sur les implémentations personnalisées:

Implémentations natives de "l'ordre de tri des chaînes naturelles" des navigateurs via localeCompare()

localeCompare()la plus ancienne implémentation (sans les paramètres régionaux et les arguments d'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'à localeCompare ( ) méthode). La localeCompare()méthode intégrée fait un bien meilleur travail de tri, même les caractères internationaux et spéciaux. Le seul problème avec la localeCompare()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, lorsque vous utilisez 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:

Difficulté de "l'ordre de tri naturel des chaînes"

La mise en œuvre d'un algorithme solide (ce qui signifie: cohérent mais couvrant également un large éventail de caractères) est une tâche très difficile. UTF8 contient plus de 2000 caractères et couvre plus de 120 scripts (langues) . Enfin, il existe quelques spécifications pour ces tâches, il est appelé "Algorithme de Collation Unicode", qui peut être trouvé à http://www.unicode.org/reports/tr10/ . Vous pouvez trouver plus d'informations à ce sujet sur cette question que j'ai publiée /software/257286/is-there-any-language-agnostic-specification-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 se rapprocher de la prise en charge de tous ces caractères et scripts (langues). Par conséquent, je préfère utiliser la méthode localeCompare () native des navigateurs. Oui, il a l'inconvénient d'être non cohérent entre les navigateurs, mais les tests de base montrent qu'il couvre une gamme beaucoup plus large de caractères, permettant des ordres de tri solides et significatifs.

Donc, comme l'a souligné par Shog9, la réponse à votre question est:

return item1.attr.localeCompare(item2.attr);

Lectures complémentaires:

Merci à la gentille réponse de Shog9, qui m'a mis dans la "bonne" direction je crois

Adrien Be
la source
38

Réponse (dans 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))

La description

La conversion d'une valeur booléenne en un nombre donne les résultats suivants:

  • 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 de comparateur de tri.

if (x == y) {
    return 0;
}
return x > y ? 1 : -1;
mpyw
la source
1
Comme je l'ai commenté sur la réponse précédente qui a utilisé cette astuce, les réponses uniquement en code peuvent être rendues plus utiles en expliquant comment elles fonctionnent.
Dan Dascalescu
Description ajoutée
mpyw
Pouvez-vous dire si c'est mieux ou pire que localeCompare?
Ran Lottem
3
@RanLottem localeCompareet la comparaison standard donnent des résultats différents. À quoi vous attendez-vous? ["A", "b", "C", "d"].sort((a, b) => a.localeCompare(b))trie dans l'ordre alphabétique insensible à la casse tandis que le ["A", "b", "C", "d"].sort((a, b) => (a > b) - (a < b))fait dans l'ordre des
codets
Je vois, cela semble être le principal point de blocage. Une idée des différences de performances?
Ran Lottem
13

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;
});
airportyh
la source
1
Sur une note latérale, cela ne gérera pas les comparaisons de chaînes vs nombres. Par exemple: 'Z' <9 (faux), 'Z'> 9 (également faux ??), 'Z' == 9 (également faux !!). Silly NaN en JavaScript ...
Kato
7

Fonction flèche ternaire imbriquée

(a,b) => (a < b ? -1 : a > b ? 1 : 0)
geckos
la source
7

puisque les chaînes peuvent être comparées directement en javascript, cela fera le travail

list.sort(function (a, b) {
    return a.attr > b.attr ? 1: -1;
})

la soustraction dans une fonction de tri n'est utilisée que lorsque le tri non alphabétique (numérique) est souhaité et bien sûr cela ne fonctionne pas avec les chaînes

Alejadro Xalabarder
la source
6

J'avais été gêné à ce sujet depuis longtemps, alors j'ai finalement fait des recherches et vous donner cette raison de longue haleine pour laquelle les choses sont comme elles sont.

De la 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 tout. 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).

Cela ===fonctionnera donc dans les cas où nous essayons de comparer des chaînes qui pourraient provenir de différentes sources, mais qui, nous le savons, 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_stateet que nous souhaitons savoir dans lequel des états suivants se ['connecting', 'connected', 'disconnecting', 'disconnected']trouve actuellement, nous pouvons utiliser directement le=== .

Mais il y a plus. Juste au-dessus de 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 cordes obtenues de l'extérieur peuvent, et très probablement, être unicodey étranges, et nos doux ===ne leur rendront pas justice. En vient 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. 

On peut rentrer 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 de programme internes, alors cela ===fonctionne aussi.

Manav
la source
0

Dans votre opération dans votre question initiale, vous effectuez l'opération suivante:

item1.attr - item2.attr

Donc, en supposant que ce sont des nombres (ie item1.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 alphaNumeric, alors utilisez localCompare ().

oeufmatters
la source
0
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
Petr Varyagin
la source
3
Les réponses uniquement codées peuvent être rendues plus utiles en expliquant comment elles fonctionnent.
Dan Dascalescu
-2
<!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>
Julio Munoz
la source
1
Veuillez ajouter quelques informations sur la façon dont cela va résoudre la question à votre réponse. Les réponses uniquement codées ne sont pas les bienvenues. Je vous remercie.
wayneOS
ici, vous voulez ordonner les caractères dans une chaîne, ce qui n'est pas ce qui est demandé. Vous pouvez effectuer ce tri en utilisant simplement "Array.sort", par exemple str.split (""). Sort () .join ("")
Alejadro Xalabarder
-2
var str = ['v','a','da','c','k','l']
var b = str.join('').split('').sort().reverse().join('')
console.log(b)
Abdul
la source
Bien que ce code puisse résoudre la question, y compris une explication de comment et pourquoi cela résout le problème aiderait vraiment à améliorer la qualité de votre message, et entraînerait probablement plus de votes positifs. N'oubliez pas que vous répondrez à la question aux lecteurs à l'avenir, pas seulement à la personne qui pose la question maintenant. Veuillez modifier votre réponse pour ajouter des explications et donner une indication des limitations et hypothèses applicables.
Dave