====== Matrices ====== Módulo perteneciente al curso [[informatica:programacion:cursos:programacion_avanzada_javascript|Programación avanzada con JavaScript y ECMAScript]]. ===== Introducción ===== Como ya sabrás, **una matriz o array** es un **conjunto de elementos colocados de forma adyacente en la memoria**, de manera que nos podemos referir a ellos c**on un sólo nombre común** mientras que, por otro lado, no se pierde la independencia de los mismos. Es decir, es **un modo de agrupar datos** para que se guarden ordenadamente y sean más cómodos de manejar y gestionar. Nos permite almacenar en una sola variable muchos elementos distintos, accediendo a ellos de manera individual. Todo elemento que pertenece a una matriz lleva asociado un índice de forma unívoca: siempre se puede acceder a un elemento determinado de cualquier matriz si se conoce su índice. En este módulo vamos a dar un repaso a las matrices en JavaScript y su funcionamiento. Las dos primeras lecciones son básicas, pero pueden venirte bien para repasar algunos conceptos, aunque puedes saltártelas o pasarlas rápido si es algo que ya dominas. Las demás lecciones comentan cuestiones prácticas interesantes sobre el uso de matrices y, aunque ya las domines, algunas de ellas contendrán pequeñas consideraciones relevantes sobre el trabajo con matrices que todo programador JavaScript debiera conocer. ===== Definición y uso básico de matrices ===== var coches = new Array(); Con la matriz definida, podemos usarla añadiendo información en cada elemento identificado por un índice (posición): coches[0] = "Porsche"; coches[1] = "Audi"; coches[2] = "Volkswagen"; Como en otros lenguajes, en JavaScript las matrices se empiezan a numerar desde el 0 Podemos indicar de antemano el tamaño que tendrá la matriz. Ponemos el número de elementos que contendrá como argumento en la creación: var coches = new Array(3); Esto es útil si queremos optimizar el código ya que JavaScript no tendrá que redimensionar la matriz. Otra forma de definir matrices, más breve y habitual: var coches = []; // es lo mismo que poner 'new Array()' Para saber el tamaño de una matriz, utilizamos la propiedad ''lenght'': alert(coches.length); La propiedad ''length'' también la podemos usar para redimensionar la matriz: coches.length = 7; ''lenght'' también es útil para recorrer una matriz: for (var i = 0; i < coches.length; i++) { alert(coches[i]); } Hay una variante del bucle ''for'' pensada para recorrer matrices y colecciones for (var coche in coches) { alert(coches[coche]); } ===== Inicialización de matrices y matrices multidimensionales ===== Muchas veces sabemos de antemano los elementos que querremos meter en una matriz, así que podemos inicializar una matriz de la siguiente manera: var coches = new Array("Porsche", "Audi", "Vokswagen"); alert(coches[1]); // "Audi" Pero se suele usar esta otra forma: var coches = ["Porsche", "Audi", "Vokswagen"]; alert(coches[1]); // "Audi" Una matriz puede contener elementos de distinto tipo: var miMatriz = ["cadena", 5, true]; alert(miMatriz[0]); // 'cadena' Podemos también definir matrices que contienen matrices, es decir, matrices **multidimensionales**: var dimension1 = ["00", "01", "02"]; var dimension2 = ["10", "11"]; var matriz2D = [dimension1, dimension2]; // Las matrices pueden tener diferentes dimensiones, y se les llamaría // matrices escalonadas alert(matriz2D[0]); // '00,01,02' alert(matriz2D[0][0]); // '00' alert(matriz2D[1][1]); // '11' ===== Genera un alfabeto ===== Crearemos una función que generará, dentro de una matriz, las letras del alfabeto que el usuario le diga. El alfabeto anglosajón tiene 26 letras. Le pediremos al usuario cuántas letras quiere y las meteremos en posiciones distintas de una misma matriz. function generaAlfabeto() { var n; // Mostrará un pop-up en el navegador para solicitar // información al usuario n = prompt("¿Cuántas letras del alfabeto deseas introducir en la matriz (1-26)?", "26"); // Convertimos la cadena en número n = parseInt(n); // Verificamos que nos hayan introducido un número if (isNaN(n) == true) return "¡Cancelado!"; // Comprobamos que el número esté dentro del rango esperado if (n < 1) { n = 1; } if (n > 26) { n = 26; } var alfabeto = []; for (var i = 0; i < n; i++) { alfabeto[i] = String.fromCharCode(65+i); // 65 es la 'A' } return alfabeto; } alert(generaAlfabeto()); El método [[https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/fromCharCode|fromCharCode]] devuelve la cadena correspondiente al código Unicode. **ASCII**: //American Standard Code for Information Interchange//, es código muy antiguo que "mapeaba" las diferentes letras del abecedario estadounidense, así como algunos otros símbolos de uso común, a diferentes números que los representaban. Hoy en día está muy superado pero las nuevas codificaciones, como Unicode, mucho más amplias, mantienen la codificación original de los caracteres. **Unicode**: representación de caracteres mucho más amplia que ASCII, capaz de abarcar todos los idiomas del planeta (incluyendo los que usan ideogramas, cirílico, árabe, etyc...) y con espacio para todavía muchos más caracteres y símbolos. ===== Matrices asociativas ===== Las matrices asociativas permiten relacionar sus elementos mediante parejas de clave-valor. En lugar de usar un índice posicional (0, 1, 2...), nos permite utilizar índices basados en claves únicas para poder acceder a un elemento. var coches = []; coches["Alemanes"] = ["Audi", "Volkswagen", "Porsche"]; coches["Franceses"] = ["Renault", "Citroën"]; coches["Italianos"] = ["Fiat", "Alfa Romeo", "Ferrari"]; Sin embargo, en **JavaScript no existen las matrices asociativas**, pero las podemos simular. Si fuera una matriz asociativa: alert(coches.length); // -> 0 alert(coches[0]); // -> undefined JavaScript lo que permite es **definir propiedades del objeto**: // Lo que hacemos realmente es definir la propiedad "Alemanes" // en el objeto "coches" y darle un valor coches["Alemanes"] = ["Audi", "Volkswagen", "Porsche"]; alert(coches.Alemanes); -> "Audi", "Volkswagen", "Porsche" ===== Trabajo práctico con matrices ===== ==== Manipulación de elementos de matrices ==== **Unión de elementos** de una matriz con el método ''join()'': var mEjemplo = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]; // Obtener cadena de texto con todos los elementos de la matriz // unidos por cierto separador mEjemplo.join(); // -> 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 mEjemplo.join("--"); // -> 0--1--2--3--4--5--6--7--8--9 **Unión de matrices** con ''concat()'': var matriz1 = [1, 2, 3]; var matriz2 = [4, 5, 6]; var siete = 7; var ocho = "ocho"; // Combinamos todo en una matriz var nuevaMatriz = matriz1.concat(matriz2, siete, ocho); alert(nuevaMatriz); --> 1,2,3,5,6,7,ocho **Extraer trozos de una matriz** con el método ''slice()'': var matriz1 = [1, 2, 3]; var matriz2 = [4, 5, 6]; var siete = 7; var ocho = "ocho"; var nuevaMatriz = matriz1.concat(matriz2, siete, ocho); // A partir del elemento 0, extraeremos hasta el elemento de la posición 6. var subconjunto = nuevaMatriz.slice(0, 6); alert(subconjunto); // -> 1,2,3,4,5 // Coger del índice 3 hasta el final var subconjunto = nuevaMatriz.slice(3); // -> 4,5,6,7,ocho Aprovechemos y comentemos el siguiente caso: var miMatriz = [1,2,3,4]; var otra = miMatriz; otra[1] = 5; alert(miMatriz[1]); // -> 5 Las matrices por defecto son objetos y se tratan por referencia, así que en el ejemplo anterior tenemos dos variables apuntando a la misma matriz. Para evitar este comportamiento, tendríamos que hacer una copia, y para esto también es útil el método ''slice()'', sin parámetros: var miMatriz = [1,2,3,4]; var otra = miMatriz.slice(); otra[1] = 5; alert(miMatriz[1]); // -> 2 **Quitar elementos de una matriz** con el método ''splice'': var matriz = [1,2,3,4,5,6,7,8]; // Desde el elemento 3, quitaremos 2 elementos var res = matriz.splice(3, 2); alert(matriz); // -> 1,2,3,6,7,8 ==== Pilas y colas ==== Una **pila** LIFO (//Last In, First Out//) es una colección donde el primer elemento que se mete, es el primero que se quita. Como si hablásemos de una pila de libros o de platos. JavaScript tiene los métodos ''push'' y ''pop'' que permiten añadir y quitar elementos de una pila. var matriz = [1,2,3,4]; // Añadimos al final, que es el comportamiento de una pila matriz.push(5); alert(matriz); // -> 1,2,3,4,5 // Para quitarlo: matriz.pop(); // -> 5 alert(matriz); // -> 1,2,3,4 Una **cola** FIFO (//First In, First Out//) es una colección donde el primer elemento que se mete es el último que se quita. Igual que una cola de personas. JavaScript tiene los métodos ''unshift'' y ''shift'' que permiten añadir y quitar elementos de una cola. var matriz = ["a", "b", "c", "d"]; // Metemos un elemento al principio matriz.unshift("x", "z"); alert(matriz); // x,z,a,b,c,d // Quitar desde el principio de la matriz matriz.shift(); // -> x alert(matriz); // -> z,a,b,c,d * [[https://en.wikipedia.org/wiki/Stack_(abstract_data_type)|Stack (abstract data type)]] (Wikipedia) * [[https://en.wikipedia.org/wiki/Queue_(abstract_data_type)|Queue (abstract data type)]] (Wikipedia) ==== Ordenación ==== JavaScript ofrece varios métodos para la ordenación de los elementos de una matriz. ''reverse'' **invierte el orden** de los elementos de la matriz: var matriz = [1, 4, 6, 3, 10]; matriz.reverse(); alert(matriz); // -> 10,3,6,4,1 Para ordenar los elementos de una matriz, de menor a mayor, utilizamos ''sort'': var matriz = [1, 4, 6, 3, 10]; matriz.sort(); alert(matriz); // -> 1,10,3,4,6 El método transforma los elementos a cadenas de texto y la ordenación se hace por esas cadenas. El código UTF-16 del 10 es menor que el 3, por eso va primero. Si queremos ordenar por longitud de las cadenas: var frutas = ["Naranja", "Pera", "Melocotón", "Fruta de la pasión", "Uva"]; function compararFruta(f1, f2) { if (f1.length > f2.length) { return 1; } else if (f1.length < f2.length) { return -1 } else { return 0; } } fruta.sort(compararFruta); // -> Uva, Pera, Naranja, Melocotón, Fruta de la pasión ==== Búsqueda ==== Para **localizar un elemento** en una matriz, podemos usar el método ''indexOf'' que nos devolverá, si lo encuentra, la posición que ocupa el elemento a buscar: var matriz = [8, 99, "ab", "cinco", 10, 41, 1]; alert(matriz.indexOf(1)); // -> 6 // Si buscamos algo que no está, devolverá -1 alert(matriz.indexOf("hola"); // -> -1 Para hacer una búsqueda desde el final de la matriz, usaremos el método ''lastIndexOf'': var matriz = [8, 99, "ab", "cinco", 10, 41, 1, 0, 1]; alert(matriz.lastIndexOf(1)); // -> 0 Podemos indicar, como segundo argumento, dónde debe empezar a buscar, es decir, a partir de qué elemento empezaría a buscar: var matriz = [8, 99, "ab", "cinco", 1, 10, 41, 1, 0, 1]; alert(matriz.indexOf(1,5)); // -> 7 Podemos escribir una función que encuentre todos los elementos de un determinado tipo que haya en una matriz: function encontrarTodos(elemento, matriz) { var encontrados = []; var pos = matriz.indexOf(elemento); // Si el elemento no está, devolverá -1. // Si está, nos devolverá la posición while (pos != -1) { encontrados.push(pos); // Tenemos que seguir buscando tras la primera ocurriencia pos = matriz.indexOf(elemento, ++pos); } return encontrados; } Para probarla: var matriz = [8, 99, "ab", "cinco", 10, 41, 1, 0, 1]; encontrarTodos(1, matriz); // -> 6,8 (posiciones en las que hay un 1 ==== Procesamiento de elementos ==== El método ''filter'' permite filtrar una matriz y extraer de ella los elementos que cumplan una determinada condición: var matriz = [1,2,3,4,5,6,7,8,9,10]; function esPar(n) { if ((n % 2) == 0) { return true; } else { return false; } } var numerosPares = matriz.filter(esPar()); alert(numerosPares); // -> 2,4,6,8,10 ''filter'' no modifica la matriz original. Solo extraer los elementos. El método ''some'' sirve para indicar si algún elemento de la matriz cumple lo que la función que le pasemos. var matriz = [1,2,3,4,5,6,7,8,9,10]; function esPar(n) { if ((n % 2) == 0) { return true; } else { return false; } } alert(matriz.some(esPar); // -> true Parecido a ''some'' está ''every'', pero indica si todos y cada uno de los elementos de la matriz cumplen la función que le pasemos: var matriz = [1,2,3,4,5,6,7,8,9,10]; function esPar(n) { if ((n % 2) == 0) { return true; } else { return false; } } alert(matriz.every(esPar); // -> false Otra función interesante de procesamiento es ''map'', que asocia elementos de la matriz a la función que le indiquemos. var matriz = [1,2,3,4,5,6,7,8,9,10]; // Queremos hacer el doble de cada elemento function elDoble(n) { return n * 2; } var res = matriz.map(elDoble); alert(res); // -> 2,4,6,8,10,12,14,18,20 ''map'' no modifica la matriz original. * [[https://es.wikipedia.org/wiki/MapReduce|MapReduce]] (Wikipedia) Finalmente, está el método ''forEach'', que se parece mucho a ''map'', pero no devuelve nada. var matriz = [1,2,3,5]; function mostrar(e, indice) { alert("El elemento [" + indice + "] tiene el valor: " + e); } matriz.forEach(mostrar); // -> El elemento [0] tiene el valor: 1, etc ===== Prácticas propuestas para este módulo ===== En este módulo hemos estudiado las matrices y sus principales métodos de trabajo. Para asentar los conocimientos te sugerimos los siguientes ejercicios prácticos: * Crea una función que tome como parámetro una matriz y calcule el máximo de los elementos que ésta contiene, desechando los que NO son números. Lo mismo para el mínimo. ¿Has tenido en cuenta todas las posibles circunstancias (por ejemplo que no haya ningún número, que todos los números sean iguales...? ¿Funcionaría tu función en estos casos? * Crea una función que permita multiplicar una matriz lineal (de una sola dimensión) por cualquier número. * Crea una función para multiplicar entre sí dos matrices cualesquiera de "n" filas por "m" columnas. Debes tener cuidado porque no es posible multiplicar entre sí matrices de cualquier dimensión, así que habrá que comprobarlo. Además deberás comprobar que todos los elementos son números. Si necesitas ayuda para aprender a multiplicar matrices, [[https://es.wikipedia.org/wiki/Multiplicaci%C3%B3n_de_matrices|este enlace]] te puede resultar útil. Y para verlo más gráficamente y paso a paso, [[http://matrixmultiplication.xyz/|esta herramienta]] es sensacional. * ¿De qué manera podrías hacer que una matriz compuesta de números exclusivamente, se pudiera ordenar considerando sus valores, es decir, sin considerar que son cadenas como hace ''sort'' por defecto? Si tienes que escribir una función de ordenación, ¿cuál sería la función más simple que podrías escribir? (pista, tendría únicamente una línea). * Crea una función que permita filtrar cualquier matriz con ''filter'' y devuelva únicamente los elementos numéricos que ésta contenga, desechando los que no sean de este tipo de datos. Las prácticas sugeridas al final de cada módulo son propuestas para que, si tienes tiempo, puedas reforzar lo aprendido con prácticas específicamente diseñadas para el contenido del módulo. No obstante, en este curso como el tiempo es algo justo **puedes saltártelas o dejarlas para más adelante**, especialmente estas del principio, que son cuestiones más básicas. En cualquier caso **no es necesario que las envíes** al tutor y **no cuentan para la nota**: son para que practiques por tu cuenta y refuerces conocimientos. Si las haces y te atascas con alguna o tienes alguna duda el tutor está para ayudarte a través de mensajería interna (recuerda: las cosas relacionadas con las prácticas, mejor que no las preguntes en los foros públicos: lo demás sí). ===== Recursos ===== * [[https://en.wikipedia.org/wiki/Stack_(abstract_data_type)|Pila LIFO o stack]] * [[https://en.wikipedia.org/wiki/Queue_(abstract_data_type)|Pila FIFO o cola]] * [[https://es.wikipedia.org/wiki/MapReduce|MapReduce]]