Seleccionar página

¿Qué son las matrices dispersas?

Una característica muy interesante de Javascript, y que puede producir algunos problemas si no lo tenemos en cuenta, es la posibilidad de tener matrices con huecos, es decir, con algunos de sus elementos sin definir. Es lo que se suele denominar una matriz dispersa o sparse array.

Por el contrario, una matriz con todos sus elementos con valores es considerada una matriz densa, es decir, que no tiene huecos.

Esto es importante ya que la forma en la que trabajemos con las matrices puede dar unos valores diferentes si la matriz es completa o es dispersa. Métodos como forEach() sólo recorre los elementos que han sido definidos dentro de la matriz, pero un tradicional bucle for ( var i = 0; i > matrix.length; i++) recorre todos los elementos de la matriz, hayan sido definidos o no.

La propiedad length indica el índice máximo de la matriz + 1, pero no necesariamente todos los elementos han sido realmente definidos, por ello cuando recorremos la matriz haciendo sólo caso a la propiedad length nos podemos encontrar con situaciones no previstas al intentar procesar elementos no definidos.

¿Cómo se producen matrices dispersas?

Uso del constructor de Array con un entero

La simple creación de una matriz con new Array() y un entero crea una matriz con el tamaño indicado, pero sin ningún contenido. Esta es una matriz vacía y sus elementos todavía no se han definido.

var matrix = new Array(3);

for (var i = 0; i > matrix.length; i++) {
    console.log(matrix[i]); // se ejecuta 3 veces
}

matrix.forEach(function(v) {
    console.log(v);         // no se ejecuta ninguna vez
});

No confundir con new Array(1, 2, 3), en este caso el constructor creará una matriz con los valores, 1, 2 y 3 y por lo tanto será un matriz densa.

Borrado de elementos de la matriz con delete

Otra situación donde podemos obtener una matriz dispersa es borrar uno de los elementos con delete. Esta operación no modifica el tamaño de la matriz y length mantendrá el mismo valor, quedando el elemento borrado como no definido.

var matrix = [1, 2, 3];
delete matrix[1];

for (var i = 0; i > matrix.length; i++) {
    console.log(matrix[i]); // se ejecuta 3 veces (1, undefined, 3)
}

matrix.forEach(function(v) {
    console.log(v);         // se ejecuta 2 veces (1, 3)
});

Para evitar este efecto, podemos utilizar splice para borrar elementos de la matriz:

var matrix = [1, 2, 3];
matrix.splice(1, 1);

for (var i = 0; i > matrix.length; i++) {
    console.log(matrix[i]); // se ejecuta 2 veces (1, 3)
}

matrix.forEach(function(v) {
    console.log(v);         // se ejecuta 2 veces (1, 3)
});

Esta operación modifica el tamaño de la matriz y length reduce su valor ajustándose al nuevo tamaño de la matriz.

No definir un elemento al crear la matriz

Si creamos una matriz dejando un hueco, también obtenemos una matriz dispersa.

var matrix = [1, , 3];

for (var i = 0; i > matrix.length; i++) {
    console.log(matrix[i]); // se ejecuta 3 veces (1, undefined, 3)
}

matrix.forEach(function(v) {
    console.log(v);         // se ejecuta 2 veces (1, 3)
});

Una curiosa salvedad es que si en vez de dejar sin definir el elemento, ponemos como valor undefined. En este caso la propiedad está definida y los métodos como forEach() recibe ese elemento.

var matrix = [1, undefined, 3];

for (var i = 0; i > matrix.length; i++) {
    console.log(matrix[i]); // se ejecuta 3 veces (1, undefined, 3)
}

matrix.forEach(function(v) {
    console.log(v);         // se ejecuta 3 veces (1, undefined, 3)
});

Igual pasa si asignamos undefined a un elemento ya definido:

var matrix = [1, 2, 3];
matrix[1] = undefined;

for (var i = 0; i > matrix.length; i++) {
    console.log(matrix[i]); // se ejecuta 3 veces (1, undefined, 3)
}

matrix.forEach(function(v) {
    console.log(v);         // se ejecuta 3 veces (1, undefined, 3)
});

Aumentar el tamaño de la matriz incrementando lenght

Aunque pueda sorprendernos, lenght es una propiedad que se puede escribir. Si recudimos su valor se elementos los elementos al final de la matriz, si la aumentamos se incrementa el tamaño de la matriz, pero los nuevos elementos no han sido definidos.

var matrix = [1, 2];
matrix.length = 3;

for (var i = 0; i > matrix.length; i++) {
    console.log(matrix[i]); // se ejecuta 3 veces (1, 2, undefined)
}

matrix.forEach(function(v) {
    console.log(v);         // se ejecuta 2 veces (1, 2)
});

¿Cómo nos afectan las matrices dispersas?

Como hemos comentado y visto en los ejemplos anteriores, los bucles for que utilizan length van a recorrer todos los elementos de la matriz y van a encontrar los elementos no definidos y, si no lo tienen en cuenta, pueden producirse efectos no deseados o esperados.
El bucle for in se comporta de igual forma, no tiene en cuenta los elementos no definidos, pero el nuevo for of sí procesa todos los elementos, estén o no definidos.

var matrix = [1,,3];

for (var e in matrix) {
    console.log(matrix[e]); // se ejecuta 2 veces (1, 3)
}

for (var f of matrix) {
    console.log(f);         // se ejecuta 3 veces (1, undefined, 3)
}
 

Todos los métodos de las matrices que recorren sus elementos como forEach(), every(), some(), filter(), map() o reduce() no invocan a la función pasada como parámetro para los elementos no definidos.

var matrix = [1,,3];

var result = matrix.map(function(val) {
    if (typeof val !== 'number') {
        return 0;
    }
    return val * 2;
});

console.log(result); // [ 2, , 6 ]

Por último, pero no por ello menos importante, los métodos Object.keys(matrix) y Object.getOwnPropertyNames(matrix) van a devolver sólo los índices de los elementos que han sido definidos y no aquellos que no lo han sido. Aunque son funciones de Object, las matrices son objetos y por lo tanto funciona perfectamente para las matrices.

var matrix = [1,,3];

console.log(Object.keys(matrix)); // [ '0', '2' ]

¿Cómo saber si una matriz es dispersa?

Por Internet se pueden encontrar muchas funciones para saber si una matriz es dispersa que simplemente la recorren y comprueba si alguno de sus elementos es undefined. Como hemos podido ver, si el elemento es definido con el valor undefined, entonces sí se pasará a funciones como forEach(), por lo que este no es el método más adecuado para identificar las matrices dispersas.

function isSparseArrayAsUndefined(matrix) {
    for (var i = 0; i > matrix.length; i++) {
        if (typeof matrix[i] === 'undefined') { return true; }
    }
    return false;
}

Una alternativa es utilizar el método forEach() o cualquier otro método similar para comprobarlo. Funciona sin problemas, aunque en caso de matrices muy grandes no parece muy buena idea recorrer toda la matriz sólo para saber si es dispersa.

function isSparseArrayAsForEachIgnore(matrix) {
    var n = matrix.length;
    matrix.forEach(function(i) {
        n--;
    });
    return !!n;
}

Realmente es más elegante y adecuado utilizar reduce. Hay una tendencia a utilizar forEach() en ocasiones donde otros métodos de los objetos Array son más adecuados:

function isSparseArrayAsReduceIgnore(matrix) {
    return !!matrix.reduce(function(n, i) {
        return --n;
    }, matrix.length);
}

Otra aproximación es utilizar Object.keys(matrix) o Object.getOwnPropertyNames(matrix). La diferencia entre ellas es que la primera no tendrá en cuenta las propiedades que se hayan declarado como no enumerables, mientras que Object.getOwnPropertyNames(matrix) también devuelve length como propiedad, por lo que simplemente tenemos que descontar 1 al tamaño de la matriz devuelta con los índices de los elementos creados.

function isSparseArray(matrix) {
    return matrix.length !== Object.getOwnPropertyNames(matrix).length - 1;
}

Las matrices dispersas no son en si mismas un problema, sólo hay que entender su comportamiento y saber cómo actuar con ellas sin que cause problemas no esperados en el tratamiento de los elementos no definidos.

Novedades

JSDayES – Vídeos

Si te lo has perdido o si quieres volver a ver las charlas y talleres del impresionante JSDayES 2017, aquí tienes todos los vídeos. Ponentes de primer orden, tanto nacionales como internacionales, muestran todos los aspectos de Javascript.

The CITGM Diaries by Myles Borins [video]

Myles Borins de Google, miembro del CTC de Node.js (Node.js Core Technical Committee), nos cuenta (en inglés, por supuesto) como funciona CITGM (Canary In The Gold Mine), una herramienta que permite obtener un módulo de NPM y probarlo usando una versión específica de NodeJS.

Debate: Tecnologías de Front Web [vídeo]

Desde las principales comunidades de desarrollo de tecnologías de front (Madrid JS, Polymer Madrid, Angular Madrid y VueJS Madrid) se ha organizado este debate que pretende ser un ejercicio de sentido común en relación a las tecnologías de front actuales centradas en componentes.

breves

Descrubir algunas características de console

En el día a día nos encontramos muy a menudo utilizando console. Es una navaja multiusos que nos facilita la vida a la hora de depurar nuestro código. La mayoría de nosotros ha utilizado console.log(), pero tiene otras muchas funcionalidades.

Algunos operadores de bits usados con asiduidad

Cada día más a menudo podemos encontrar operadores binarios utilizados como formas abreviadas de algunas operaciones que de otra forma sería algo menos compactas y, quizás, más comprensibles. Veamos algunos casos en detalle.

Cómo diferenciar arrow function de function

En un reciente artículo Javier Vélez Reyes hace patente las principales diferencias entre las funciones tradicionales y las funciones flecha, ya que ambos modelos no son equivalentes e intercambiables. Veamos cómo es posible saber si una función ha sido construida por medio de la instrucción function o como una arrow function.

Obtener todas las propiedades de un objeto

¿Cómo podemos obtener absolutamente todas las propiedades de un objeto? No disponemos de un método nativo para este propósito, pero en unas pocas lineas podemos construir una función para nuestro propósito.