Seleccionar página
Desde hace unas semanas hemos podido comprobar en diferentes con diferentes equipos que, en general, no se hace un uso habitual de 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

customElements a fondo

Vamos a dar repaso general a customElements y todos sus métodos. Esta es una pieza clave para la creación de Custom Tag, uno de los pilares de los Web Components. Hemos intentado empezar por lo más básico e ir avanzando hasta describir algunos de sus comportamientos más complejos, además de describir algunas de las características más importantes de los Web Components.

Uso de jsRPC en una aplicación de ejemplo

Para poder comprender mejor cómo podemos utilizar la librería jsRPC hemos creado una aplicación de ejemplo en la que hemos utilizado el modelo RPC para que el front invoque funciones del servidor y para que los diferentes microservicios invoquen de forma remota funciones entre ellos.

Un completo sistema RPC en Javascript con sólo 100 líneas

La aparición de gRPC de Google ha hecho que vuelva a ponerse de moda los sistemas de Remote Procedure Calls. La mayoría de las implementaciones de RPC se han ajustado muy poco a las características idiomáticas de Javascript. Con jsRPC mostramos cómo desarrollar un completo sistema RPC utilizando las características de Javascript.

Taller del API Performance Timeline

En este taller práctico descubriremos cómo utilizar el paquete perf_hooks en Node, se explican las principales características del API Performance Timeline y se repasan algunos de los criterios y principios fundamentales que hay que tener en cuenta a la hora de optimizar el rendimiento de nuestros programas.