Array.prototype.sort()
y que, en muchas ocasiones, se desconoce cómo funciona este método. Hoy vamos a intentar describir brevemente cómo funciona .sort()
y desvelaremos algunos de sus secretos.Advertencia: cambia el Array original
Como ya describimos en su momento, los arrays tienen múltiples patrones y puede ser confuso su manejo. En este caso, debemos tener en cuenta es que este método modifica la matriz ordenándola, devolviendo la misma matriz ordenada, pero no una nueva. Esto es importante tenerlo en cuenta si queremos mantener inmutable la matriz y obtener otra ordenada, lo que tendríamos que hacer una copia de la matriz antes de ordenada.
Comparación de cadenas por su posición en el código Unicode
Por defecto, el método .sort()
está diseñado para ordenar los valores de la matriz como cadenas en base a la posición de cada letra en el código Unicode, así que puede ordenar esta matriz sin problemas:
console.log(["Zaragoza", "Madrid", "Barcelona"].sort()); // [ 'Barcelona', 'Madrid', 'Zaragoza' ]
Esto parece correcto, pero si y algunos de los nombres empieza en minúsculas, entonces la ordenación no parece tan correcta:
console.log(["Zaragoza", "madrid", "Barcelona"].sort()); // [ 'Barcelona', 'Zaragoza', 'madrid' ]
En este caso la ordenación se ha realizado siguiendo la posición de cada letra en la tabla de códigos Unicode, y m
está por detrás de Z
, por eso se ha ordenado de esta forma.
La cosa se complica si queremos ordenar números:
console.log([80, 9, 100].sort()); // [ 100, 80, 9 ]
El resultado parece absurdo, pero tiene sentido, lo que ha ocurrido es que se han convertido los números en cadenas y por lo tanto se ha comparado "100"
, "80"
y "9"
. Como se han comprado letra a letra su posición en el código Unicode, la ordenación es correcta, aunque no sea la que en principio esperábamos.
Estas situaciones producen han provocado que alguna gente abandone el uso .sort()
al tener un comportamiento confuso. Esto es un poco precipitado, ya que con un poco de ayuda este método puede funcionar sin ningún tipo de problemas.
Parámetro del método sort()
El método .sort()
tiene un único parámetro opcional que permite ayudar a este método para realizar la ordenación del contenido. Esta es la clave para que este método se comporte como nos interesa en cada caso.
Esta función recibe dos valores a comparar y como resultado debe:
- devolver un valor positivo (
1
) si e primer valor es superior al segundo - devolver un valor negativo (
-1
) si e primer valor es inferior al segundo - devolver un valor cero (
0
) si los dos valores son iguales o equivalentes para la ordenación.
Esta función es llamada por Javascript en la medida que necesite ordenar los elementos de la matriz, y en ella podemos hacer las comparaciones y ajustes que sean necesarios. Por ejemplo, para comparar números podemos utilizar algo como:
console.log([80, 9, 100].sort((a, b) => a - b)); // [ 9, 80, 100 ]
La función (a, b) => a – b
utiliza un pequeño truco y al restar un valor a otro consigue que se devuelva un valor positivo si a
es mayor que b
, un valor negativo si a
es menor que b
, y 0 si tienen el mismo valor, por lo que se cumple el requisito que nos imponen a la hora de retornar valores en la función de apoyo al método .sort()
.
Si queremos hacer algo parecido para corregir el problema de mayúsculas y minúsculas que vimos antes, podemos estar tentados en utilizar algo de este tipo:
const data = [ "Zaragoza", "madrid", "Barcelona" ]; data.sort ((a, b) => a.toLowerCase () > b.toLowerCase ()); console.log (data); // [ 'Zaragoza', 'madrid', 'Barcelona' ]
El resultado no es correcto porque nuestra precipitada función devuelve true
o false
que es lo que se obtiene de una comparación con un operador mayor (>
) y tenemos que recordar que la función de apoyo a .sort()
espera que devolvamos -1
, 1
o 0
. Para que funcione tendríamos que hacer algo de este tipo:
const data = [ "Zaragoza", "madrid", "Barcelona" ]; data.sort ((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : a.toLowerCase() < b.toLowerCase() ? -1: 0 ); console.log (data); // [ 'Barcelona', 'madrid', 'Zaragoza' ]
El resultado ahora es que podríamos esperar, ya que hemos realizado la comparación independientemente de minúsculas y mayúsculas y hemos devuelto -1
, 1
o 0
, según cada caso.
Realmente no hemos terminado, ya que, si incorporamos alguna letra acentuada, volvemos a tener un resultado no deseado:
const data = [ "Zaragoza", "madrid", "Barcelona", "Ávila" ]; data.sort ((a, b) => a.toLowerCase() > b.toLowerCase() ? 1 : a.toLowerCase() < b.toLowerCase() ? -1: 0 ); console.log (data); // [ 'Barcelona', 'madrid', 'Zaragoza', 'Ávila' ]
Seguimos comparando en base a la posición de cada letra en el código Unicode, que es lo que conseguimos al usar el operador mayor (>
) con cadenas. Una solución bastante sencilla y, en general, bastante desconocida, es utilizar el método String.prototype.localeCompare()
que permite comprarar dos cadenas teniendo en cuenta acentos y otras características específicas de cada idioma para la ordenación. Lo mejor de todo, es que esta función devuelve -1
, 1
o 0
según si es mayor, menor o igual, que es exactamente lo que necesitamos:
const data = ["Zaragoza", "Ávila", "madrid", "Barcelona"]; data.sort((a, b) => a.localeCompare(b)); console.log(data); // [ 'Ávila', 'Barcelona', 'madrid', 'Zaragoza' ]
El uso de .localeCompare()
es fundamental para tener una ordenación correcta y debemos usarla siempre que queramos ordenar cadenas de texto.
Ordenación de matrices con objetos
Por supuesto, si la matriz contiene objetos, podemos utilizar las propiedades de los objetos para hacer la comparación, por ejemplo:
const data = require ('./municipios.json'); data.sort ((a, b) => a.municipio.localeCompare (b.municipio));
Esto mismo podemos hacerlo con fechas y cualquier otro tipo de objeto que tengamos en nuestras estructuras de datos.
Una última nota sobre el rendimiento
Si queremos ordenar matrices muy grandes, debemos tener en cuenta que se llamará bastantes veces a la función de apoyo del método .sort()
y debemos evitar realizar muchas operaciones u operaciones muy pesadas dentro de esta función. Debemos realizar la comparación de la forma más eficiente posible.
Por ejemplo, en matrices muy grandes en vez de utilizar .localeCompare()
se puede utilizar el new Int.Collate().compare
para obtener una función de ordenación más eficiente. El objeto Int
es parte de un estándar llamado International API o ECMA-402 que se centra en funciones de internacionalización, entre los que se encuentra la correcta ordenación en cada idioma. Int
se presenta como un objeto global en los navegadores y en Node, y tiene un soporte muy extendido (incluyendo IE11).
const data = [ "Zaragoza", "Ávila", "madrid", "Barcelona" ]; const compare = new Intl.Collator ().compare; data.sort (compare); console.log (data); // [ 'Ávila', 'Barcelona', 'madrid', 'Zaragoza' ]
Las operaciones de ordenación son complejas y tiene un rendimiento bastante comprometido, por lo que en el caso de tener matrices muy grandes cualquier mejora en la velocidad de la función de apoyo al método sort
tendrá un resultado muy significativo en el rendimiento.
Conclusiones
En general debemos aprovechar la funcionalidad de .sort()
con un una función de apoyo que permita controlar cómo se debe realizar la ordenación, en general bastará con:
- números:
(a, b) => a – b
- cadenas:
(a, b) => a.localeCompare(b)
Utilizar .sort()
sin una función de apoyo tiene muy poco interés, quizás en algunos pocos casos, pero .sort
es una herramienta muy útil si la apoyamos con una sencilla función. Ordenar es una operación básica en muchos casos y no debemos renunciar a realizar esta ordenación en Javascript.
Novedades
HTTP2 para programadores. Enviar mensajes del servidor al cliente con Server Sent Event (sin WebSockets)
En esta charla, organizada por MadridJS, Pablo Almunia nos muestra cómo la mayoría de nosotros cuando oímos hablar por primera vez de HTTP2 nos ilusionamos con las posibilidades que presumiblemente se abrían para el desarrollo de soluciones web avanzadas y cómo muchos nos sentimos defraudados con lo que realmente se podía implementar.
En esta charla podemos ver cómo funciona el HTTP2, que debemos tener en cuenta en el servidor para hace uso de este protocolo y, sobre todo, cómo podemos enviar información desde el servidor al cliente de forma efectiva y fácil. Veremos con detenimiento cómo por medio de los Server-Sent Events (SSE) podemos recibir en el cliente datos enviados desde el servidor sin utilizar websocket, simplificando enormemente la construcción de aplicaciones con comunicación bidireccional.
Observables en Javascript con Proxies
En esta charla, organizada por MadridJS, Pablo Almunia nos habla de la observación reactiva de objetos en Javascript por medio de Proxy. Se describe paso a paso cómo funcionan los Proxies y en que casos pueden ser nuestro mejor aliado. Veremos que no hay que tenerles miedo, son bastante sencillos de utilizar, y nos ofrecen una gran abanico de posibilidades.
Aplicaciones JAMStack, SEO friendly y escalables con NextJS
En esta charla de Madrid JS, Rafael Ventura nos describe las funcionalidades clave de NextJS, nos muestra en vivo cómo desarrollar una completa aplicación JAMStack con Server Side Rendering (SSR) y Static Site Generation (SSG) y termina mostrando como publicar esta aplicación en Vercel.
Stencil JS: mejora el Time To Market de tu producto, por Rubén Aguilera
En esta charla Rubén Aguilera nos cuenta los problemas que tienen muchas empresas a la hora de sacar productos accesibles, vistosos y usables en el Time To Market que requiere Negocio y cómo podemos minimizar este tiempo gracias al DevUI con StencilJS para adecuar una aplicación de Angular a las exigencias del mercado en tiempo record.
Muchas gracias por el contenido, ahora me surge una duda.
En caso de una tabla de posiciones de futbol que se quiera ordenar por puntos, y en caso de empate de pts se resuelva por goles a favor, se púede agregar un segundo parametro para comparar?
Realmente no se pueden pasar más parámetros, pero lo que si puedes es pasar analizar todos los datos que necesites del objeto, por ejemplo: