Arrays asociativos en Javascript (o casi)

Algunas veces en nuestros programas nos interesa usar arrays asociativos para relacionar claramente un campo con su contenido, es más fácil recordar a qué nos referimos si especificamos una entrada como datos_personales['nombre'] que como datos_personales[0]. Algunos lenguajes permiten definir tipos enumerados que internamente se traducen en enteros: podemos crear el enumerado nombre = 0 y acceder al dato con datos_personales[nombre]. Aunque Javascript tampoco dispone de este tipo, es fácil emularlo.
Para definir un array asociativo en Javascript disponemos de las clases Object() y Array(). En algunas referencias se indica que debe usarse Object() puesto que Array() sólo permite índices de tipo escalar pero he probado con ambos y tienen el mismo comportamiento, quizás depende de la versión de Javascript usada. Desde un punto de vista técnico no se pueden crear arrays asociativos en Javascript, en su lugar se añaden propiedades a un objeto usando notación matricial o de puntos. A dichas propiedades nos referimos como índices en éste artículo. Podemos pues definir nuestro array de la siguiente forma:
var tabla = new Array();  // o tabla = new Object();
tabla['indice1'] = 'valor1';
tabla.indice2 = 'valor2';
tabla[2] = 'valor3';
Debemos tener cuidado si mezclamos índices escalares: la sentencia tabla[2] = 'valor3'  funcionará de manera distinta según se haya creado la tabla de tipo Object() o de tipo Array(). De tipo Object() situará el valor a continuación del índice anterior, como se espera, pero con Array() funcionará  situando el valor en la posición del índice - 1 apilándolo en la cima de la tabla, por encima de los índices alfanuméricos. Si ejecutamos el siguiente código para ambos objetos:
for (var indice in tabla)
    document.writeln('[' + indice +']:' + tabla[indice] + ' ');
obtenemos para Object(): [indice1]:valor1 [indice2]:valor2 [2]:valor3
y para Array(): [1]:valor3 [indice1]:valor1 [indice2]:valor2

Si intentamos tabla[4] = 'valor4' lo situará en la posición 3, si intentamos tabla[1] = 'valor1' lo situará en la posición 0 pero si intentásemos tabla[0] = 'valor0' el valor no será guardado. Esto tiene que ver con la forma en que Javascript almacena internamente los array y se comenta más adelante.

Una vez creado nuestro array debemos enfrentar otros dos problemas:
  • los arrays asociativos no disponen de un método que nos devuelva su longitud (length devuelve 0 con Array() y undefined con Object())
  • los arrays asociativos no diponen de un método para ordenarlos, ni por índice ni por valor (sort() ignora la llamada)

Para obtener la longitud del array podemos usar el método getOwnPropertyNames() (Javascript 1.8.5). Dicho método nos devuelve un array escalar con las propiedades (índices) de nuestro objeto, por lo que podemos definir un método longitud para cada clase. Así, para un array definido con Object():
Object.longitud = function(obj)
{
      return Object.getOwnPropertyNames(obj).length;
}
y con Array():
Array.longitud = function(obj)
{
      return Object.getOwnPropertyNames(obj).length - 1;
}
y podemos ejecutar longitud = Object.longitud(tabla). La función Array.longitud requiere restar 1 al resultado dado que estamos accediendo a él como Object() (Array.getOwnPropertyNames no funcionaría) y tenemos acceso a propiedades ocultas de los Array(). Una de estas propiedades es la marca de fin de lista que es donde se almacena la longitud del array (length). Se crea cada vez que instanciamos un Array() pero es totalmente inaccesible a menos que se use la función correspondiente. Al acceder a la tabla como Object() nos aparece en la lista de propiedades y por eso debemos restarlo del resultado final. Si ejecutamos:
var tablaPrueba = new Array();
var indices = Object.getOwnPropertyNames(tablaPrueba);
for (indice in indices)
    document.writeln(indices[indice] + ' ');
obtendremos en la posición 0 el índice (la propiedad) length. Si ahora  intentamos document.writeln(tablaPrueba[0]) nos devolverá undefined, a menos que lo hayamos sobreescrito como se indica en el párrafo anterior. En este caso la propiedad length se desplazará en la tabla colocándose siempre al final de los índices escalares:
var tablaPrueba = new Array();
tablaPrueba[1] = 'valor1';
var indices = Object.getOwnPropertyNames(tablaPrueba);
for (indice in indices)
    document.writeln(indices[indice] + ' ');
nos devolverá 0 length, pero document.writeln(tablaPrueba[1]) no existe. En conclusión, si vas a mezclar índices escalares y alfanuméricos usa Object().
Para solucionar el problema de la ordenación debemos crear dos funciones: una para ordenar por índices y otra para ordenar por valores. El método consiste en leer todos los índices (o valores), crear un array estándar con ellos para poder aplicarles el método sort() y recrear el array original a partir de los datos ordenados:
function ordenarPorIndice(tabla)
{
      var indicesOrdenados = new Array(); 
      var objectOrdenado = {};   // o var objectOrdenado = [];
      for (var indice in tabla)  // extraemos los indices y los ordenamos
      {
          indicesOrdenados.push(indice);
      }
      indicesOrdenados.sort();  // o .sort(funcion)
      for (var indice in indicesOrdenados) // recreamos la tabla según el nuevo orden de los índices
      {
          objectOrdenado[indicesOrdenados[indice]] = arr[indicesOrdenados[indice]];
      }
      return objectOrdenado;
}
function ordenarPorValor(tabla)
{
      var valoresOrdenados = new Array();
      var objectOrdenado = {};  // o var objectOrdenado = [];
      for (var indice in tabla)  // extraemos los valores y los ordenamos
      {
           valoresOrdenados.push(tabla[indice]);
      }
      valoresOrdenados.sort();  // o .sort(funcion)
      for (var indice in tabla)  // recreamos la tabla reasignando los valores de cada índice
      {
          objectOrdenado[indice] = valoresOrdenados.shift();
      }
      return objectOrdenado;
}
Ambas funciones son aplicables indistintamente a instancias de Object() y de Array() pero debe tenerse en cuenta el tipo devuelto para solventar posibles incoherencias (recuérdese que la longitud de un array asociativo creado como Array() es siempre una unidad mayor de la aparente). Además, los métodos push(), pop(), shift() y slice() no son soportados por Object(), sólo push() y pop() por Array() y delete por ambos,

1 comentario: