Cómo ordenar cadenas en JavaScript
-
09-06-2019 - |
Pregunta
Tengo una lista de objetos que deseo ordenar según un campo attr
de tipo cadena.Intenté usar -
list.sort(function (a, b) {
return a.attr - b.attr
})
pero encontré que -
no parece funcionar con cadenas en JavaScript.¿Cómo puedo ordenar una lista de objetos según un atributo con tipo cadena?
Solución
Usar String.prototype.localeCompare
a según tu ejemplo:
list.sort(function (a, b) {
return ('' + a.attr).localeCompare(b.attr);
})
Forzamos a.attr a ser una cadena para evitar excepciones. localeCompare
ha sido apoyado desde Internet Explorer 6 y Firefox 1.También es posible que veas 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;
Otros consejos
Una respuesta actualizada (octubre de 2014)
Estaba realmente molesto por este orden de clasificación natural de las cadenas, así que me tomé bastante tiempo para investigar este problema.Espero que esto ayude.
Larga historia corta
localeCompare()
El soporte de personajes es genial, solo úsalo.Como lo señala Shog9
, la respuesta a tu pregunta es:
return item1.attr.localeCompare(item2.attr);
Errores encontrados en todas las implementaciones personalizadas de "orden de clasificación de cadenas naturales" de JavaScript
Existen muchas implementaciones personalizadas que intentan realizar una comparación de cadenas con mayor precisión, lo que se denomina "orden de clasificación de cadenas natural".
Al "jugar" con estas implementaciones, siempre noté alguna extraña elección de "orden de clasificación natural", o más bien errores (u omisiones en el mejor de los casos).
Normalmente, los caracteres especiales (espacio, guión, signo comercial, corchetes, etc.) no se procesan correctamente.
Luego los encontrarás mezclados en diferentes lugares, normalmente podrían ser:
- algunos estarán entre la 'Z' mayúscula y la 'a' minúscula
- algunos estarán entre el '9' y la 'A' mayúscula
- algunos estarán después de la 'z' minúscula
Cuando uno hubiera esperado que todos los caracteres especiales estuvieran "agrupados" en un solo lugar, excepto quizás el carácter especial de espacio (que siempre sería el primer carácter).Es decir, todo antes de los números, o todo entre números y letras (las minúsculas y mayúsculas están "juntas" una tras otra), o todo después de las letras.
Mi conclusión es que todos ellos no logran proporcionar un orden consistente cuando empiezo 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:
Natural Compare Lite
https://github.com/litejs/natural-compare-lite :No logra ordenar consistentemente https://github.com/litejs/natural-compare-lite/issues/1 y http://jsbin.com/bevututodavi/1/edit?js,console , clasificación de caracteres latinos básicos http://jsbin.com/bevututodavi/5/edit?js,consoleNatural Sort
https://github.com/javve/natural-sort :No se puede ordenar de forma coherente; consulte el problema https://github.com/javve/natural-sort/issues/7 y ver la clasificación de caracteres latinos básicos http://jsbin.com/cipimosedoqe/3/edit?js,consoleJavascript Natural Sort
https://github.com/overset/javascript-natural-sort :parece bastante descuidado desde febrero de 2012, no logra clasificar de manera consistente, consulte el problema https://github.com/overset/javascript-natural-sort/issues/16Alphanum
http://www.davekoelle.com/files/alphanum.js , Falla al ordenar consistentemente, ver http://jsbin.com/tuminoxifuyo/1/edit?js,console
Implementaciones nativas de "orden de clasificación de cadenas naturales" de los navegadores a través de localeCompare()
localeCompare()
La implementación más antigua (sin los argumentos locales y de opciones) es compatible con IE6+, consulte http://msdn.microsoft.com/en-us/library/ie/s4esdbwz(v=vs.94).aspx (Desplácese hacia abajo hasta el método localeCompare()).el incorporado localeCompare()
El método hace un trabajo mucho mejor al clasificar, incluso caracteres internacionales y especiales.El único problema al utilizar el localeCompare()
El método es ese "La configuración regional y el orden de clasificación utilizados dependen totalmente de la implementación".En otras palabras, cuando se utiliza 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:
- http://jsbin.com/beboroyifomu/1/edit?js,console - comparación de caracteres latinos básicos con localeCompare()http://jsbin.com/viyucavudela/2/ - comparación de caracteres latinos básicos con localeCompare() para pruebas en IE8
- http://jsbin.com/beboroyifomu/2/edit?js,console - caracteres latinos básicos en comparación de cadenas:verificación de coherencia en una cadena frente a cuando un personaje está solo
- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/localeCompare - IE11+ admite los nuevos argumentos locales y opciones
Dificultad del "orden de clasificación natural de cadenas"
Implementar un algoritmo sólido (es decir:consistente pero también cubriendo una amplia gama de personajes) es una tarea muy difícil.UTF8 contiene más de 2000 caracteres & cubre más de 120 guiones (idiomas).Finalmente, existe una especificación para esta tarea, se llama "Algoritmo de intercalación Unicode", que se puede encontrar en http://www.unicode.org/reports/tr10/ .Puedes encontrar más información sobre esto en esta pregunta que publiqué. https://softwareengineering.stackexchange.com/questions/257286/is-there-any-language-agnostic-specification-for-string-natural-sorting-order
Conclusión final
Entonces, considerando el nivel actual de soporte brindado por las implementaciones personalizadas de JavaScript que encontré, probablemente nunca veremos nada que se acerque a admitir todos estos caracteres y scripts (idiomas).Por lo tanto, prefiero utilizar el método localeCompare() nativo de los navegadores.Sí, tiene la desventaja de no ser consistente en todos los navegadores, pero las pruebas básicas muestran que cubre una gama mucho más amplia de caracteres, lo que permite un orden de clasificación sólido y significativo.
Así como lo señala Shog9
, la respuesta a tu pregunta es:
return item1.attr.localeCompare(item2.attr);
Otras lecturas:
- https://softwareengineering.stackexchange.com/questions/257286/is-there-any-language-agnostic-specification-for-string-natural-sorting-order
- ¿Cómo se comparan cadenas en JavaScript?
- JavaScript:tipo natural de cadenas alfanuméricas
- Ordenar matriz de elementos numéricos y alfabéticos (clasificación natural)
- Ordenar matriz alfanumérica mixta
- 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/
Gracias a la agradable respuesta de Shog9, que creo que me puso en la dirección "correcta".
Respuesta (en ECMAScript moderno)
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
Al convertir un valor booleano a un número se obtiene lo siguiente:
true
->1
false
->0
Considere tres patrones posibles:
- 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;
Deberías 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;
});
Me había preocupado esto durante mucho tiempo, así que finalmente investigué esto y les di esta larga razón de por qué las cosas son como son.
Desde el Especificaciones:
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)
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 todo. El operador triple igual aplicado a cadenas devuelve verdadero si los argumentos son exactamente las mismas cadenas (misma longitud y mismos caracteres en las posiciones correspondientes).
Entonces ===
funcionará en los casos en los que intentemos comparar cadenas que podrían haber llegado de diferentes fuentes, pero que sabemos que eventualmente tendrán los mismos valores, un escenario bastante común para cadenas en línea en nuestro código.Por ejemplo, si tenemos una variable llamada connection_state
, y deseamos saber cuál de los siguientes estados ['connecting', 'connected', 'disconnecting', 'disconnected']
¿Está en este momento? Podemos usar directamente el ===
.
Pero hay más.Justo encima 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.
Mmm.¿Ahora que?Las cadenas obtenidas externamente pueden ser unicodey extrañas, y lo más probable es que lo sean, y nuestro amable ===
no les hará justicia.Entra localeCompare
al 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 funciona.
Función de flecha ternaria anidada
(a,b) => (a < b ? -1 : a > b ? 1 : 0)
En tu operación en tu pregunta inicial, estás realizando la siguiente operación:
item1.attr - item2.attr
Entonces, suponiendo que sean números (es decir,item1.attr = "1", item2.attr = "2") Aún puede utilizar el operador "===" (u otros evaluadores estrictos) siempre que garantice el tipo.Lo siguiente debería funcionar:
return parseInt(item1.attr) - parseInt(item2.attr);
Si son alfanuméricos, utilice localCompare().
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
<!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)