En JavaScript las matrices son simplemente vectores de vectores, es decir son vectores donde cada elemento es a su vez un vector (una fila).
Por lo tanto una matriz se crea igual que un vector, es decir de manera formal con new Array o de manera abreviada (la forma recomendada) escribiendo las filas, separadas con comas, entre corchetes. Por ejemplo, la siguiente matriz:
| \[ a = \begin{bmatrix} 1 & 2 & 3 & 4 & 5\\ 2 & 3 & 4 & 5 & 6\\ 3 & 4 & 5 & 6 & 7\\ 4 & 5 & 6 & 7 & 8 \end{bmatrix} \] |
Puede ser creada de la siguiente forma:
Como se puede ver, por defecto, la matriz se muestra en una sola línea. Para ver cada fila en una línea diferente, se emplea el método show:
Con la forma estándar (que no será empleada en la práctica) la matriz se crea con:
Como se mencionó al estudiar vectores, los métodos add, sub, mul, div y neg operan con vectores, matrices, números reales y complejos, por lo que son más generales que los operadores +, -, *, / y - (cuando es empleado como negación). Por ejemplo, dadas las siguientes matrices:
| \[ a = \begin{bmatrix} 2 & 8 & 2 & 3\\ 1 & 6 & -1 & 4\\ 2 & -1 & 2 & 5\\ 4 & 2 & 3 & 5 \end{bmatrix} \hspace{1cm} b = \begin{bmatrix} 1 & 4 & -2 & 3\\ 2 & 2 & 0 & 4\\ 3 & 0 & -1 & 2\\ 1 & 2 & 2 & -3 \end{bmatrix} \] |
Puenden ser sumadas, restadas, multiplicadas y divididas:
Al ser las matrices vectores de vectores, se pueden emplear todas las propiedades y métodos disponibles para vectores. Adicionalmente, en la calculadora Javascript, se cuenta con los siguientes métodos (librería array):
| función | Descripción |
|---|---|
| identity(n,m) | Crea una matriz identidad con "n" filas y "m" columnas ("n" filas y columnas si sólo se manda "n"), o con las dimensiones de la matriz o vector desde el cual es llamado. |
| diagonal(k) | Crea una matriz con los elementos del vector desde el cual es llamado, en la diagonal principal o en la diagonal "k". |
| appendRows(b) | Añade al final de la matriz o vector desde el cual es llamado, las filas de la matriz o vector "b". |
| appendCols(b) | Añade al final de la matriz o vector desde el cual es llamado, las columnas de la matriz o vector "b" |
| insertRows(b,k) | Inserta al principio (o en la fila "k") de la matriz o vector desde el cual es llamado, la fila o filas de la matriz o vector "b". |
| insertCols(b,k) | Inserta al principio (o en la columna "k") de la matriz o vector desde el cual es llamado, la columna o columnas de la matriz o vector "b". |
| removeRows(k,l) | Quita la fila "k" o las filas "k" a "l" de la matriz o vector desde el cual es llamado, devolviendo la fila o filas removidas. |
| removeCols(k,l) | Quita la columna "k" o las columnas "k" a "l" de la matriz o vector desde el cual es llamado, devolviendo la columna o columnas removidas. |
| getRows(k,l) | Devuelve un vector o matriz con la fila "k", o las filas desde "k" hasta "l", de la matriz o vector desde el cual es llamado. |
| getCols(k,l) | Devuelve un vector o matriz con la columna "k", o las columnas desde "k" hasta "l", de la matriz o vector desde el cual es llamado. |
| getDiagonal(k) | Devuelve un vector con los elementos de la diagonal principal, o de la diagonal "k", de la matriz desde la cual es llamado. |
| replaceRows(b,k) | Reemplaza la fila "k", o la fila "k" y posteriores, de la matriz desde la cual es llamdo, con las filas de la matriz o vector "b". |
| replaceCols(b,k) | Reemplaza la columna "k", o la columna "k" y posteriores, de la matriz desde la cual es llamado, con las columnas de la matriz o vector "b". |
| rowSwap(i,j) | Intercambia las filas "i" y "j" de la matriz o vector desde el cual es llamado. |
| colSwap(i,j) | Intercambia las columnas "i" y "j" de la matriz o vector desde el cual es llamado. |
| det() | Devuelve el determinante de la matriz desde la cual es llamado. |
| inv() | Devuelve la inversa de la matriz desde la cual es llamado. |
| trace() | Devuelve el trazo de la matriz desde la cual es llamado. |
| transpose() | Devuelve una nueva matriz, con la transpuesta de la matriz desde la cual es llamado. |
Con estos métodos se pueden llevar a cabo la mayoría de las operaciones requeridas en el trabajo con matrices, además, como se dijo, se pueden emplear también todos los métodos estudiados en vectores.
De estos métodos los que se utilizan con mayor frecuencia en la resolución de sistemas de ecuaciones lineales son inv, transpose y det. De los métodos estudiados previamente, son de mayor utilidad en el trabajo con matrices: rand, const y zeros, además de los métodos de carácter general: copy, round, precision, add, sub, etc.
Se recuerda que, el método rand, genera por defecto un vector o matriz con números realmente aleatorios, es decir con números que se generan en una secuencia no predecible, sin embargo, se pueden generar números pseudoaleatorios (secuencias que pueden volver a ser generadas) estableciendo un valor inicial o semilla (randseed).
Mostrando las matrices con show:
Genere y guarde en "a" una matriz con 3 filas y 4 columnas de números enteros pseudoaleatorios comprendidos entre 1 y 9 (randseed=1):
Genere y guarde en "b" una matriz con 4 filas y 4 columnas de números enteros pseudoaleatorios comprendidos entre 1 y 9 (randseed=0):
Genere y guarde en la variable "c" una matriz con 4 filas y 5 columnas de números 6:
Obtenga la transpuesta de la matriz "a":
Calcule la inversa de la matriz b, redondeando los resultados al segundo dígito después del punto:
Calcule la matriz resultante de multiplicar la matriz "a" por la matriz "b":
Divida la matriz "c" entre el número 2:
Genere una matriz con 4 filas y 6 columnas de ceros:
Mostrando las matrices con show y tomando en cuenta las siguientes matrices:
| \[ a = \begin{bmatrix} 1 & 2 & 2\\ 4 & 1 & 2\\ 3 & 5& 1 \end{bmatrix}\hspace{7mm} b = \begin{bmatrix} 2 & 3 & 1\\ 4 & 5 & 3\\ 3 & 7 & 2 \end{bmatrix} \] |
Guárdelas en "a" y "b":
Calcule su suma y guarde el resultado en "c":
Obtenga la transpuesta de la matriz "c":
Calcule la inversa de la matriz "c" mostrando el resultado con cuatro dígito después del punto:
Calcule el trazo de la matriz "c":
Calcule el valor absoluto de la matriz "c":
Calcule el determinante de la matriz "c":
Copie en "d" la matriz "a" y añada a la copia la primera fila de la matriz "b":
Que puede ser resuelta, igualmente, con slice:
Copie en "e" la matriz "b" y añada a la copia la segunda columna de la matriz "a":
Que, igualmente, puede ser resuelta con slice:
Mostrando las matrices con show:
Genere la matriz "a" con 3 filas y 4 columnas de números enteros pseudoaleatorios comprendidos entre 10 y 20 (randseed=1):
Genere el vector "v" con 4 números enteros pseudoaleatorios comprendidos entre 11 y 19 (randseed=1):
Cree la matriz "a1" con los elementos del vector "v" en su diagonal principal:
Cree la matriz "a2", con los elementos del vector "v", en la segunda diagonal a la derecha de la principal:
Cree la matriz "a3", con los elementos del vector "v", en la primera diagonal a la izquierda de la principal:
Inserte el vector "v" en la segunda fila de la matriz "a":
Inserte el vector "v" en la primera columna de la matriz "a":
Quite la tercera fila de la matriz "a":
Quite la segunda columna de la matriz "b":
Mostrando las matrices con
Cree el vector "v1" con los números impares que existen entre 1 y 30:
Cree el vector "v2" con 25 elementos igualmente espaciados comprendidos entre 1 y 10:
Cree la matriz "a1" con 4 filas y 5 columnas de ceros:
Cree la matriz "a2" con 5 filas y 3 columnas de cincos:
Cree la matriz identidad "a3" de 6 filas por 6 columnas:
Cree el vector "v3" con 5 números enteros pseudoaleatorios comprendidos entre 1 y 9 (randseed=0):
Reemplace la segunda columna de la matriz "a2" con los elementos del vector "v3":
Reemplace la tercera fila de la matriz "a1" con los elementos del vector "v3":
Mostrando las matrices con show:
Cree el vector "v1" con 5 elementos iguales a 7:
Cree la matriz "a1" con 4 filas y 5 columnas de números enteros pseudoaleatorios (randseed = 2) comprendidos entre 10 y 30:
Intercambia la segunda y tercera filas de la matriz "a1":
Intercambie la primera y quinta columnas de la matriz "a1":
Cree el vector "v2" con los elementos de la segunda fila de la matriz "a1":
Cree el vector "v3" con los elementos de la cuarta columna de la matriz "a1":
Cree el vector "v4" con los elementos de la diagonal principal de la matriz "a1":
Cree el vector "v5" con los elementos de la primera diagonal a la derecha de la diagonal principal de la matriz "a1":
Cree el vector "v6" con los elementos de la segunda diagonal a la izquierda de la diagonal principal de la matriz "a1":
Al igual que ocurre con vectores, al trabajar con matrices, se llevan a cabo operaciones con todos o un subconjunto de sus elementos. La estructura más adecuada para ese fin (al igual que en vectores) es la estructura for, sólo que con las matrices generalmente se requieren dos estructuras for anidadas (porque tienen dos dimensiones).
Las expresiones matemáticas que involucran sumatorias (\(\sum\)) o productorias (\(\prod\)), pueden ser traducidas directamente a sus estructuras for equivalentes.
Para calcular la sumatoria de los elementos de una matriz, se aplica la siguiente expresión matemática:
| \[ S = \sum_{i=1}^{n}\sum_{j=1}^{m}a_{i,j} \] |
Recordando que las matrices en JavaScript comienzan en 0, estas sumatorias pueden ser traducidas directamente en dos ciclos for: El primero (contador "i") desde 0 hasta n-1 y el segundo (contador "j") desde 0 hasta m-1. El código respectivo, implementado como un método getter de la clase "SM", que extiende a (hereda de) la clase Array, es:
El número de filas "n" es el número de elementos del array (a.length), porque en una matriz cada fila es un elemento. El número de columnas es el número de elementos en la primera fila (a[0].length, por supuesto asumiendo que es una matriz regular).
Llamando al método con las siguientes matrices:
| \[ a = \begin{bmatrix} 2 & 8 & 2 & 3\\ 1 & 6 & -1 & 4\\ 2 & -1 & 2 & 5\\ 4 & 2 & 3 & 5 \end{bmatrix} \hspace{1cm} b = \begin{bmatrix} 1 & 4 & -2 & 3\\ 2 & 2 & 0 & 4\\ 3 & 0 & -1 & 2\\ 1 & 2 & 2 & -3 \end{bmatrix} \] |
Se obtiene:
Observe que para crear una matriz con la clase "SM", se manda (al constructor heredado de Array) las filas de las matrices, porque una matriz (en JavaScript) simplemente es un vector, donde cada uno de sus elementos es a su vez otro vector.
Para determinar si una matriz es regular (es decir determinar si todas las filas tienen el mismo número de elementos) se puede llamar al método isMatrixr, que devuelve true (verdadero) si la matriz es regular y false (falso) en caso contrario.
En lugar de la estructura for, el problema puede ser resuelto también empleando la estructura for-of:
En este caso, en el ciclo externo se obtiene en "f", una por una las filas de la matriz "a", mientras que en el interno se obtienen y suman, uno por uno, los elementos "e" de cada fila "f".
Igualmente, el problema puede ser resulto con el método forEach:
Donde, al igual que en la estructura for-of, en la llamada externa se obtiene en "f", una por una, las filas de la matriz "a", mientras que en la llamada interna se obtienen y suman, uno por uno, los elementos "e" de cada fila "f".
Igualmente, el problema puede ser resulto con el método map:
Donde, al igual que en el método anterior, en la llamada externa se obtiene en "f", una por una las filas de la matriz "a", mientras que en la llamada interna se obtienen y suman, uno por uno, los elementos "e" de cada fila "f", sin embargo, como ya se hizo notar en el capítulo anterior, el uso de map, para resolver este tipo de problemas, no es eficiente, porque map genera una nueva matriz (con los resultados intermedios de la sumatoria) y no es ni lógico, ni eficiente, crear una matriz que al final no es empleada.
Empleando el método reduce, el problema se resuelve de la siguiente manera:
En este caso, el primer reduce itera para cada una de las filas "f" de la matriz, mientras que el segundo (que es llamado desde cada una de las filas "f") itera para cada uno de los elementos "e" de cada fila. Para que el primer reduce itere desde la primera fila de la matriz (y no directamente desde la segunda) se le manda un valor inicial "0" (que es guardado en el acumulador "s"). Para cada una de las iteraciones del reduce externo, el interno devuelve la sumatoria de los elementos "e" de la fila respectiva y ese resultado es añadido a la sumatoria de las filas de la matriz.
Es importante tomar en cuanta que, aunque se ha dado el mismo nombre ("s") tanto al acumulador interno, como al externo, son dos acumuladores diferentes (el interno devuelve la sumatoria de los elementos "e" de la fila "f", mientras que el externo devuelve la sumatoria de las filas "f" de la matriz "a").
El problema, resuelto con el método every, es:
La lógica es la misma que con forEach, sólo que para que every itere para todas las filas y para todos los elementos de cada fila, las funciones tanto interna, como externa, devuelven 1, que es uno de los valores equivalentes a true (por supuesto se puede devolver directamente true en lugar de 1).
De manera similar se pueden emplear los métodos filter, some, find, findLast, findIndex, findLastIndex, sin embargo, como ya se ha recalcado en el capítulo anterior, estos métodos no son adecuados para resolver este tipo de problemas. Se emplean en la asignatura, únicamente con el propósito de ayudar a comprender su lógica.
La siguiente expresión matemática, permite calcular la matriz "c" que resulta de multiplicar las matrices "a" y "b":
| \[ c_{i,j} = \sum_{k=1}^{nca}a_{i,k}b_{k,j} \hspace{0.5cm} \begin{cases} i = 1 \rightarrow nfa\\ j = 1 \rightarrow ncb \end{cases} \] |
Donde "nfa" es el número de filas de la matriz "a", "ncb" es el número de columnas de la matriz "b" y "nca" es el número de columnas de la matriz "a". La condición para que dos matrices puedan ser multiplicadas es que el número de columnas de la matriz "a", sea igual al número de filas de la matriz "b".
Esta expresión, puede ser traducida y codificada directamente en tres ciclos for: El primero (contador "i") que va desde 0 hasta nfa-1, el segundo (contador "j") que va desde 0 hasta ncb-1 y el tercero (contador "k") que va desde 0 hasta nca-1. El código respectivo, programado como un método estándar de la clase "MM", que hereda de la clase Array, es:
Observe que la matriz "c" se declara como constante, aunque en el proceso se generan y calculan los elementos de cada una de sus filas. Se procede así porque, como se recordará, las variables que almacenan un vector (y en general cualquier objeto) sólo guardan la dirección de memoria donde se encuentra el vector (u objeto), no los valores en si y esa dirección no cambia en todo el proceso (es constante).
Creando dos matrices de tipo MM:
Y llamando a "mulMat" desde la matria "a", se obtiene:
El algoritmo puede ser codificado también empleando el método push (en lugar de una sentencia de asignación):
En lugar de iniciar la matriz resultante "c", como una matriz vacía y añadir las filas y elementos en cada ciclo, puede ser iniciada como una matriz de ceros, con el número de filas de la matriz "a" y el número de columnas de la matriz "b". El código respectivo, programado como un método estándar de la clase "MM", es:
Aunque no es la estructura más adecuada para este caso, el problema puede ser resuelto, también, con la estructura for-of.
Para resolver el problema con otras estructuras iterativas, es importante recordar que al multiplicar dos matrices, se multiplica cada una de las filas de la matriz "a" por cada una de las columnas de la matriz "b". Con ese propósito, el primer ciclo (el más externo) recorre cada una de las filas de la matriz "a", el segundo recorre cada una de las columnas de la matriz "b" y el tercero (el más interno) simplemente multiplica los elementos de la fila "a" por los elementos de la columa "b".
Por ejemplo, para obtener el tercer elemento de la segunda fila (el elemento c[1][2]), se multiplica la segunda fila de la matriz "a" ([2, 3, 4l) por la tercera columna de la matriz "b" ([3, 4, 5]), es decir:
Siendo esas las operaciones que se llevan a cabo en el tercer ciclo.:
Entonces, si "fa" es la fila de la matriz "a" y "cb" es la columna de la matriz "b", el elemento resultante, puede ser calculado en un ciclo:
En consecuencia, para obtener la primera fila de la matriz resultante, se debe multiplicar la primera fila de la matriz "a", por cada una de las columnas de la matriz "b", luego, para la segunda fila, se multiplica la segunda fila de la matriz "a" por cada una de las columnas de la matriz "b" y así sucesivamente.
Como al multiplicar las matrices se trabaja con las columnas de la matriz "b", pero los métodos iteran a través de las filas (no las columnas), para resolver el problema, empleando los métodos iterativos, se debe trabajar con la transpuesta de la matriz "b".
Una vez comprendido el procedimiento, se puede resolver el problema con la estructura for-of:
En esta solución, en el primer ciclo (el ciclo externo) se itera para cada una de las filas "fa" de la matriz "a". En el segundo ciclo (el primer ciclo anidado) se itera para cada una de las columnas "cb" de la matriz "b" (porque se está trabajando con la matriz transpuesta) y en el tercer ciclo (el ciclo más interno) se itera para cada uno de los elementos "ea" de cada una de las filas "fa" de la matriz "a".
Antes del primer ciclo se crea la matriz (vacía) resultante "c", en el priner ciclo se crea (en cada iteración) cada una de las filas (vacías) "fc" de la matriz resultante. En el segundo ciclo, se inicia en 0 cada uno de los elementos "ec" de la fila "fc" y se inicia en 0 el contador "k" de la columna "cb". En el tercer ciclo se calcula el valor del elemento "ec" (multiplicando los elementos "ea" de la fila "fa" por los elementos cb[k] de la columna "cb"). En este ciclo, se emplea el operador ++, para incrementar el valor del contador, inmediatamente después de recuperar el valor del elemento "b[k]".
Se pueden lograr soluciones más compactas, si en el ciclo más interno, se emplea otra estructura iterativa. Por ejemplo, con "reduce" se tiene la siguiente solución:
En este caso no es necesario incrementar el contador "k" (pues la estructura "reduce" lo hace automáticamente).
Siguiendo un razonamiento similar, el problema puede ser resuelto, igualmente, con los métodos iterativos disponibles para arrays. Así, empleando el método forEach, el problema se resuelve con:
En este caso, como la función forEach puede recibir también el contador (el índice de cada elemento) no es necesario declarar, iniciar, incrementar ni reiniciar los contadores.
De manera similar, se pueden emplear los otros métodos, por ejemplo, con el método some, el código, es:
Donde las funciones de "some" devuelven un valor falso (en este caso 0) para garantizar que se itere con todos los elementos del array. Además, para evitar escribir el comando return, las instrucciones de las funciones se escriben entre paréntesis y se separan con comas, de manera que sean ejecutadas como si se tratara de una sola instrucción (tal como se explicó en el capítulo previo).
En este ejemplo se elabora un método que rota, hacia la derecha, las columnas de la matriz desde la cual es llamado.
Para rotar las columnas de una matriz hacia la derecha, dado que las matrices están conformadas por vectores fila (no vectores columna) se deben recorrer los elementos de cada fila (desde la primera hasta la última fila) una posición hacia la derecha. Para ello simplemente el último elemento toma el valor del penúltimo, el penúltimo del antepenúltimo y así sucesivamente hasta que el segundo elemento toma el valor del primero.
Sin embargo, en este proceso se pierde el valor del último elemento (porque es reemplazado por el penúltimo). Por esta razón, es necesario guardar ese elemento en una variable auxiliar y una vez rotados los elementos, asignar ese valor al primer elemento.
El código respectivo, implementado como un método getter de la clase "RC", que hereda de (extiende a) la clase Array:
Que puede ser probada, por ejemplo, rotando 2 veces una matriz pseudoaleatoria (semilla = 1) de 5 filas y 7 columnas de números enteros comprendidos entre 10 y 30:
El problema puede ser resuelto, igualmente, empleando la estructura for-of
En este caso, el ciclo externo itera para cada una de las filas "f" de la matriz "a". Dentro del ciclo se guarda (en "aux") el último elemento de la fila ("f[m]") y en "k" el índice del último elemento (m). En el ciclo interno, se itera para cada uno de los elementos "_" de la fila "f", los elementos "_" en realidad no son utilizados en la función (siendo esa la razón por la se les da ese nombre). La estructura "for-of" se emplea únicamente para iterar un número de veces igual al número de elementos de cada fila (razón por la cual no es la estructura más adecuada para resolver este problema).
En cada iteración del ciclo interno, se recorre un elmeento hacia la derecha, hasta que se llega al primer elemento (k===0), donde se le asigna el valor del último elemento.
Con un razonamiento similar, se pueden emplear también otros métodos iterativos. Por ejemplo con el método every, el código que resuelve el problema, es:
Donde, al igual que con some y por la misma razón, las funciones se escriben entre paréntesis, sólo que ahora se devuelve un valor equivalente a true (1).
En el ciclo interno, como el contador "k" inicia en 0 y se requiere al último elemento (no al primero), los índices se calculan con m-k, para que en la primera iteración sea el último elemento: m-0 = m, en la siguiente el penúltimo: m-1 y así sucesivamente. Pero, cuando se llega al primer elemento (cuando "k" es igual a "m": m-m = 0), se asigna el valor del último elemento (guardado en la variable auxiliar), por eso en la función se pregunta si "m" es igual a "k" y de ser así, se le asigna el valor de "aux".
Cuando lo que se quiere se rotar las filas de la matriz, no las columnas, solo es neceario un ciclo (no dos) porque en una matriz, las filas son simplemente elementos de la matriz, en consecuencia, el problema se reduce a rotar esos elementos.