Cómo ordenar cadenas en JavaScript

344

Tengo una lista de objetos que deseo ordenar según un campo attrde tipo cadena. Traté de usar-

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

pero descubrió que -no parece funcionar con cadenas en JavaScript. ¿Cómo puedo ordenar una lista de objetos basada en un atributo con tipo cadena?

airportyh
fuente
1
ver JavaScript case insensitive string comparisonen stackoverflow.com/questions/2140627/…
Adrien Be
Para una solución rápida "internacionalizada" (solo en parte, supongo, ya que es posible que esto no cubra todos los acentos en el mundo), es posible que desee simplemente ignorar los acentos, es decir, eliminarlos. Sólo entonces hacer su comparación de cadenas, consulte Javascript : remove accents/diacritics in stringsel stackoverflow.com/questions/990904/...
Adrien Sé
2
Curiosamente, el propio Jeff Atwood escribió una publicación de blog sobre este problema común en 2007, consulte blog.codinghorror.com/sorting-for-humans-natural-sort-order
Adrien Be

Respuestas:

620

Use String.prototype.localeComparea por su ejemplo:

list.sort(function (a, b) {
    return ('' + a.attr).localeCompare(b.attr);
})

Obligamos a a.attr a ser una cadena para evitar excepciones. localeCompareha sido compatible desde Internet Explorer 6 y Firefox 1. También puede ver el siguiente código utilizado que no respeta una configuración regional:

if (item1.attr < item2.attr)
  return -1;
if ( item1.attr > item2.attr)
  return 1;
return 0;
Shog9
fuente
81
Antes de que alguien cometa el mismo error apresurado que yo, es local e Compare, no localCompare.
ento
12
La primera solución considerará que "A" viene después de "z" pero antes de "Z", ya que está haciendo una comparación en el valor ASCII del carácter. localeCompare()no se encuentra con este problema, pero no entiende los números, por lo que obtendrá ["1", "10", "2"] como con las comparaciones de clasificación en la mayoría de los idiomas. si desea ordenar por su interfaz de usuario, busque en el algoritmo de ordenación alfanumérico / natural stackoverflow.com/questions/4340227/… o stackoverflow.com/questions/4321829/…
Dead.Rabit
2
Tenga en cuenta que localeCompare()solo es compatible con los navegadores modernos: IE11 + en el momento de la escritura, consulte developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Adrien Be
3
No, me refiero a la primera línea de la tabla, @Adrien: IE admite localeCompare()volver a muchas versiones, pero no admite especificar la configuración regional hasta la versión 11. Tenga en cuenta también las preguntas a las que Dead.Rabit se vinculó.
Shog9
3
@ Shog9 mi mal, parece que es compatible desde IE6! consulte (método de desplazamiento hacia abajo / buscar en localeCompare ()) en msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx . Una cosa a tener en cuenta, sin embargo, en las implementaciones anteriores en las que no utilizamos los argumentos locales y de opciones (el utilizado antes de IE11), la configuración regional y el orden de clasificación utilizados dependen completamente de la implementación , en otras palabras: Firefox, Safari, Chrome e IE NO ordenar cadenas en el mismo orden. ver code.google.com/p/v8/issues/detail?id=459
Adrien Be
166

Una respuesta actualizada (octubre de 2014)

Estaba realmente molesto por este orden de clasificación natural de cadenas, así que me tomé bastante tiempo para investigar este problema. Espero que esto ayude.

Larga historia corta

localeCompare()el soporte de personajes es rudo, solo úsalo. Como se señaló Shog9, la respuesta a su pregunta es:

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

Se encontraron errores en todas las implementaciones de "orden de clasificación de cadena natural" de javascript personalizado

Hay bastantes implementaciones personalizadas por ahí, que intentan hacer una comparación de cadenas más precisamente llamada "orden de clasificación de cadena natural"

Al "jugar" con estas implementaciones, siempre noté alguna elección extraña de "orden de clasificación natural", o más bien errores (u omisiones en los mejores casos).

Por lo general, los caracteres especiales (espacio, guión, ampersand, corchetes, etc.) no se procesan correctamente.

Luego los encontrará mezclados en diferentes lugares, generalmente eso podría ser:

  • algunos estarán entre las mayúsculas 'Z' y las minúsculas 'a'
  • algunos estarán entre el '9' y la mayúscula 'A'
  • algunos serán después de 'z' en minúsculas

Cuando uno hubiera esperado que los caracteres especiales se "agruparan" en un solo lugar, excepto por el carácter especial de espacio (que siempre sería el primer carácter). Es decir, todos antes de los números, o todos entre números y letras (minúsculas y mayúsculas están "juntas" una tras otra), o todas después de las letras.

Mi conclusión es que todos no pueden proporcionar un orden consistente cuando comienzo a agregar caracteres apenas inusuales (es decir, caracteres con signos diacríticos o caracteres como guión, signo de exclamación, etc.).

Investigación sobre las implementaciones personalizadas:

Implementaciones nativas de "orden de cadena natural" de los navegadores a través de localeCompare()

localeCompare()IE6 + admite la implementación más antigua (sin los argumentos locales y de opciones), consulte http://msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx (desplácese hacia abajo para ubicaleCompare ( ) método). El localeCompare()método incorporado hace un trabajo mucho mejor en la clasificación, incluso caracteres internacionales y especiales. El único problema con el localeCompare()método es que "la configuración regional y el orden de clasificación utilizados dependen completamente de la implementación". En otras palabras, cuando se usa localeCompare como stringOne.localeCompare (stringTwo): Firefox, Safari, Chrome e IE tienen un orden de clasificación diferente para las cadenas.

Investigación sobre las implementaciones nativas del navegador:

Dificultad del "orden de clasificación natural de la cuerda"

Implementar un algoritmo sólido (es decir: consistente pero que también cubre una amplia gama de caracteres) es una tarea muy difícil. UTF8 contiene más de 2000 caracteres y cubre más de 120 scripts (idiomas) . Finalmente, hay algunas especificaciones para estas tareas, se llama "Algoritmo de clasificación Unicode", que se puede encontrar en http://www.unicode.org/reports/tr10/ . Puede encontrar más información sobre esto en esta pregunta que publiqué /software/257286/is-there-any-language-agnostic-specification-for-string-natural-sorting-order

Conclusión final

Por lo tanto, teniendo en cuenta el nivel actual de soporte proporcionado por las implementaciones personalizadas de JavaScript que encontré, probablemente nunca veremos nada que se acerque a todos estos caracteres y scripts (idiomas). Por lo tanto, preferiría usar el método nativo localeCompare () de los navegadores. Sí, tiene el inconveniente de no ser coherente en todos los navegadores, pero las pruebas básicas muestran que cubre una gama mucho más amplia de caracteres, lo que permite órdenes de clasificación sólidas y significativas.

Entonces, como se señaló Shog9, la respuesta a su pregunta es:

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

Otras lecturas:

Gracias a la buena respuesta de Shog9, que me puso en la dirección "correcta", creo

Adrien Be
fuente
38

Respuesta (en Modern ECMAScript)

list.sort((a, b) => (a.attr > b.attr) - (a.attr < b.attr))

O

list.sort((a, b) => +(a.attr > b.attr) || -(a.attr < b.attr))

Descripción

Lanzar un valor booleano a un número produce lo siguiente:

  • true -> 1
  • false -> 0

Considere tres posibles patrones:

  • x es mayor que y: (x > y) - (y < x)-> 1 - 0->1
  • x es igual a y: (x > y) - (y < x)-> 0 - 0->0
  • x es menor que y: (x > y) - (y < x)-> 0 - 1->-1

(Alternativa)

  • x es mayor que y: +(x > y) || -(x < y)-> 1 || 0->1
  • x es igual a y: +(x > y) || -(x < y)-> 0 || 0->0
  • x es menor que y: +(x > y) || -(x < y)-> 0 || -1->-1

Entonces, estas lógicas son equivalentes a las funciones típicas de comparación de clasificación.

if (x == y) {
    return 0;
}
return x > y ? 1 : -1;
mpyw
fuente
1
Como comenté en la respuesta anterior que usó este truco, las respuestas de solo código se pueden hacer más útiles al explicar cómo funcionan.
Dan Dascalescu
Descripción agregada
mpyw
¿Puedes comentar si esto es mejor o peor que localeCompare?
Ran Lottem
3
@RanLottem localeComparey la comparación estándar producen resultados diferentes. Cual esperas ["A", "b", "C", "d"].sort((a, b) => a.localeCompare(b))las clases en el orden alfabético de mayúsculas y minúsculas, mientras que ["A", "b", "C", "d"].sort((a, b) => (a > b) - (a < b))lo hace con el fin de punto de código
mpyw
Ya veo, ese parece ser el principal problema. ¿Alguna idea sobre las diferencias de rendimiento?
Ran Lottem
13

Debe usar> o <y == aquí. Entonces la solución sería:

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
fuente
1
En una nota al margen, esto no manejará las comparaciones entre cadenas y números. Por ejemplo: 'Z' <9 (falso), 'Z'> 9 (también falso ??), 'Z' == 9 (¡también falso!). Silly NaN en JavaScript ...
Kato
7

Función de flecha ternaria anidada

(a,b) => (a < b ? -1 : a > b ? 1 : 0)
gecos
fuente
7

Como las cadenas se pueden comparar directamente en JavaScript, esto hará el trabajo

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

la resta en una función de clasificación se usa solo cuando se desea una clasificación no alfabética (numérica) y, por supuesto, no funciona con cadenas

Alejadro Xalabarder
fuente
6

Me había preocupado por esto durante mucho tiempo, así que finalmente investigué esto y le di esta razón larga y sin aliento de por qué las cosas son como son.

De la especificación :

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)

Así que ahora vamos a 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.

Eso es. El operador triple igual aplicado a las cadenas devuelve verdadero si los argumentos son exactamente las mismas cadenas (la misma longitud y los mismos caracteres en las posiciones correspondientes).

Por ===lo tanto , funcionará en los casos en que intentemos comparar cadenas que podrían haber llegado de diferentes fuentes, pero que sabemos que eventualmente tendrán los mismos valores, un escenario lo suficientemente común para cadenas en línea en nuestro código. Por ejemplo, si tenemos una variable llamada connection_statey deseamos saber en cuál de los siguientes estados se ['connecting', 'connected', 'disconnecting', 'disconnected']encuentra en este momento, podemos usar directamente ===.

Pero hay más. Justo arriba de 11.9.4, hay una breve nota:

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 ¿Ahora que? Las cadenas obtenidas externamente pueden, y muy probablemente serán, unicodey extrañas, y nuestro gentil ===no les hará justicia. En viene localeCompareal rescate:

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. 

Podemos irnos a casa ahora.

tl; dr;

Para comparar cadenas en javascript, use localeCompare; si sabe que las cadenas no tienen componentes que no sean ASCII porque son, por ejemplo, constantes internas del programa, entonces ===también funcionan.

Manav
fuente
0

En su operación en su pregunta inicial, está realizando la siguiente operación:

item1.attr - item2.attr

Por lo tanto, suponiendo que se trata de números (es decir, item1.attr = "1", item2.attr = "2") Aún puede utilizar el operador "===" (u otros evaluadores estrictos) siempre que se asegure el tipo. Lo siguiente debería funcionar:

return parseInt(item1.attr) - parseInt(item2.attr);

Si son alphaNumeric, entonces use localCompare ().

Eggmatters
fuente
0
list.sort(function(item1, item2){
    return +(item1.attr > item2.attr) || +(item1.attr === item2.attr) - 1;
}) 

Cómo funcionan las muestras:

+('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
fuente
3
Las respuestas de solo código se pueden hacer más útiles al explicar cómo funcionan.
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 Muñoz
fuente
1
Agregue información sobre cómo esto resolverá la pregunta a su respuesta. Las respuestas de solo código no son bienvenidas. Gracias.
wayneOS
aquí desea ordenar los caracteres dentro de una cadena, que no es lo que se solicita. Puede lograr esta clasificación simplemente usando "Array.sort", por ejemplo, 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
fuente
Si bien este código puede resolver la pregunta, incluir una explicación de cómo y por qué esto resuelve el problema realmente ayudaría a mejorar la calidad de su publicación, y probablemente resultaría en más votos positivos. Recuerde que está respondiendo la pregunta para los lectores en el futuro, no solo la persona que pregunta ahora. Por favor, editar su respuesta para agregar explicación y dar una indicación de lo que se aplican limitaciones y supuestos.
Dave