Para muchos estudiantes de programación es difícil ver la utilidad de un arreglo dinámico en un lenguaje de bajo nivel cómo lo es C. ¿Por qué no simplemente usar un arreglo estático para realizar estas operaciones? La respuesta es clara: no siempre conocemos el tamaño de los datos con los cuáles vamos a trabajar. En este artículo vamos a tratar un tema que demuestra este punto. Supongamos que queremos multiplicar dos matrices que no conocemos en el momente de escribir el programa. Vamos a usar dos archivos donde se encuentren descritas las matrices. Dado que no conocemos el tamaño de estas matrices no podemos crear arreglos con un tamaño fijo,  por lo que el programa reservará el espacio necesario para almacenar los datos obtenidos de los archivos, en tiempo de ejecución. Esto significa que al momento de que se ejecute del programa, este creará un arreglo con los datos obtenidos en ese momento. Veamos la lógica general de un programa así.

 Primero será necesario definir las variables necesarias para realizar nuestro cálculo. En este caso nos centraremos en las variables importantes. Son las declaraciones de punteros a estructuras matrix M1, M2 y M. M1 y M2 apuntarán a la memoria que contendrá las matrices que leeremos del archivo, mientras que M será el resultado final de la multiplicación.

    matrix *M1, *M2, *M;

 ¿Pero qué aspecto tiene la estructura matrix?

typedef struct matrix{
    int m, n;
    float **M;
} matrix;

Cada unidad de matrix contiene un doble puntero a elementos float, lo cuál se es equivalente a un arreglo de dos dimensiones de floats, necesario para nuestra matriz. También almacenaremos el tamaño de la misma en las variables enteras m y n. Esto para poder trabajarla y evitar posibles desbordamientos.  

Ahora bien, para crear una matriz dinámica vamos a emplear la famosa función malloc, de la siguiente forma:

matrix 
*matrix_create(int n, int m)
{
    int i;
    matrix *M;
    
    M = malloc(sizeof(matrix));
    if(M == NULL)
    {
        return NULL;
    }

    M->m = m;
    M->n = n;

    M->M = malloc(n * sizeof(float *));
    if(M->M == NULL)
    {
        return NULL;
    }
    for(i = 0; i < n; i++)
    {
        M->M[i] = malloc(m * sizeof(float));
        if(M->M[i] == NULL)
        {
            return NULL;
        }
    }
    
    return M;
}

Si se tiene dudas de cómo funciona esto, puede consultar nuestra entrada sobre creación de arreglos multidimensionales dinámicos. Esta función reserva un espacio en memoria para guardar nuestros datos y regresa el puntero a este espacio. Para poder usarlo de manera eficiente, será necesario escribir también una función de liberación de este espacios. Si no hacemos esto durante la ejecución de un programa, se estarán creando huecos de memoria o langunas que no se podrán usar por otros programas. A la larga ocupará todo el espacio de la RAM. 

#define FREE(p)   do { free(p); (p) = NULL; } while(0)
void matrix_free(matrix *M) { int i; for(i = 0; i < M->n; i++) FREE(M->M[i]); FREE(M->M); FREE(M); }

Ahora que ya tenemos listas nuestras funciones básicas para la creación de matrices y liberación de memoria, vamos a una de las funciones centrales de nuestro programa. Cargar las matrices desde un archivo almacenado en el disco duro. 

matrix 
*matrix_load_file(char *filename, int n, int m)
{
    FILE *fp;
    matrix *M;
    int ni, mj, i, j;
    char line[100000];
    char num[1000];

    M = matrix_create(n, m);
    if(M == NULL)
    {
        return NULL;
    }
    fp = fopen(filename,"r");
    if(fp == NULL)
    {
        printf("Error al abrir archivo");
        return NULL;
    }

    for(ni = 0; ni < n; ni++)
    {
        fgets(line, 100000, fp); 
        i = 0;
        mj = 0;
        while(mj < m)
        {
            j = 0;

            while(line[i] != ' ' && line[i] != '\n')
            {
                num[j] = line[i];
                j++;
                i++;
            }

            i++;
            num[j] = '\0';
            M->M[ni][mj] = atof(num);
            mj++;
            j = 0;
        }
    }
                
    fclose(fp);
    return M;
}

 Ahora que tenemos todo cargado en  memoria, es posible realizar la multiplicación de matrices. 

matrix
*matrix_mult(matrix *M1, matrix *M2)
{
    int n,m,i;
    matrix *M;
    float fsum;
    
    if(M1 == NULL)
    {
        return NULL;
    }
    if(M2 == NULL)
    {
        return NULL;
    }

    if(M1->m != M2->n)
    {
        printf("Las matrices deben ser M1 = N2");
        return NULL;
    }

    M = matrix_create(M1->m, M2->n);
    if(M == NULL)
    {
        return NULL;
    }

    M->m = M1->m;
    M->n = M2->n;

    for(n = 0; n < M->n; n++)
    {
        for(m = 0; m < M->m; m++)
        {
            fsum = 0;
            for(i=0; i<M->m; i++)
            {
                fsum = fsum + M1->M[n][i] * M2->M[i][m];
            }
            M->M[n][m] = fsum;
        }
    }
    
    return M;
}

¿Pero qué formato deben tener nuestros archivos de entrada? Aquí un breve ejemplo:

2 3 1 0 3 4 1 4
2 1 4 5 0 0 1 0
45 3 2.34 5.1 34 1 1 
2 3 4 2.3 4.1 3 4
2 3 4 2.3 4.1 3 4
2 3 4 2.3 4.1 3 4

La multiplicación revisa que las condiciones para una multiplicación se cumplan por parte de las dos matrices introducidas, para posteriormente crear  una nueva matriz de las dimenciones rultantes de una multiplicación de matrices. Se reserva esta memoria y se trabaja como matriz M. Posteriormente se itera sobre cada elemento de la nueva matriz y se cualcula su valor. La nueva matriz se regresa de la función. Para poder ver si nuestros resultados son los deseados, vamos a escrubir además una función que imprima en pantalla las matrices. 

void    
matrix_print(matrix *M)
{
    int n,m;
    for(n=0; n < M->n; n++)
    {
        for(m=0; m < M->m; m++)
        {
            printf("%.2f\t", M->M[n][m]);
        }
        printf("\n");
    }
}

Para poner todo junto, agrego los archivos fuente multm.c . El primero es el que contiene el main y junta todo lo antes discutido:  

#include <stdio.h>
#include <stdlib.h>
#include "matrix.h"

void
formato(void)
{
    printf("Formato de entrada:\n");
    printf("   multm [n] [m] archivo1.txt [n] [m] archivo2.txt\n");
    printf("\nDonde n y m son las dimenciones de cada matriz\n");
    printf("y archivo1.txt y archivo2.txt contienen los valores\n");
    printf("de las matrices.\n");
}; 


int
main(int argc, char **argv)
{
    int n1, n2, m1, m2, print;
    char *f1, *f2;
    matrix *M1, *M2, *M;

    // Revisamos si hay suficientes entradas desde terminal
    if (argc != 7)
    {
        formato();
        return 0;
    }
    
    // ¿Queremos impresión en pantalla?
    print = 1;

    // Asignamos valores de la entrada a variables
    n1 = atoi(argv[1]);
    m1 = atoi(argv[2]);
    f1 = argv[3];
    n2 = atoi(argv[4]);
    m2 = atoi(argv[5]);
    f2 = argv[6];

    // Se crea la primera matriz
    M1 = matrix_load_file(f1 ,n1, m1);
    if (M1 == NULL)
    {
        printf("Error al crear matríz\n");
        return 1;
    }

    if(print)
    {
        matrix_print(M1);
        printf("*\n");
    }

    //Se crea la segunda matriz
    M2 = matrix_load_file(f2 ,n2, m2);
    if (M2 == NULL)
    {
        printf("Error al crear matríz\n");
        matrix_free(M1);
        return 1;
    }

    if(print)
    {
        matrix_print(M2);
    }
    
    // Realizamos la multiplicación
    M = matrix_mult(M1, M2);
    if(M == NULL)
    {
        matrix_free(M1);
        matrix_free(M2);
        return 1;
    }
    if(print)
    {
        printf("----------------------\n");
        matrix_print(M);
    }

    // Liberamos memoria
    matrix_free(M1);
    matrix_free(M2);
    matrix_free(M);
    return 0;
}

Ahora el archivo de cabecera matrix.h que contiene todas las definiciones de nuestras funciones y tipos de datos:

#ifndef MATRIX_H
#define MATRIX_H

typedef struct matrix{
    int m, n;
    float **M;
} matrix;

matrix *matrix_create(int, int);
matrix *matrix_load_file(char *, int, int);
void    matrix_print(matrix *);
matrix *matrix_mult(matrix *, matrix *);
void    matrix_free(matrix *);

#endif

El archivo donde se definen nuestras funciones matrix.c :

#include <stdio.h>
#include <stdlib.h>
#include "matrix.h"
#define FREE(p)   do { free(p); (p) = NULL; } while(0)

matrix 
*matrix_create(int n, int m)
{
    int i;
    matrix *M;
    
    M = malloc(sizeof(matrix));
    if(M == NULL)
    {
        return NULL;
    }

    M->m = m;
    M->n = n;

    M->M = malloc(n * sizeof(float *));
    if(M->M == NULL)
    {
        return NULL;
    }
    for(i = 0; i < n; i++)
    {
        M->M[i] = malloc(m * sizeof(float));
        if(M->M[i] == NULL)
        {
            return NULL;
        }
    }
    
    return M;
}

matrix 
*matrix_load_file(char *filename, int n, int m)
{
    FILE *fp;
    matrix *M;
    int ni, mj, i, j;
    char line[100000];
    char num[1000];

    M = matrix_create(n, m);
    if(M == NULL)
    {
        return NULL;
    }
    fp = fopen(filename,"r");
    if(fp == NULL)
    {
        printf("Error al abrir archivo");
        return NULL;
    }

    for(ni = 0; ni < n; ni++)
    {
        fgets(line, 100000, fp); 
        i = 0;
        mj = 0;
        while(mj < m)
        {
            j = 0;

            while(line[i] != ' ' && line[i] != '\n')
            {
                num[j] = line[i];
                j++;
                i++;
            }

            i++;
            num[j] = '\0';
            M->M[ni][mj] = atof(num);
            mj++;
            j = 0;
        }
    }
                
    fclose(fp);
    return M;
}


void    
matrix_print(matrix *M)
{
    int n,m;
    for(n=0; n < M->n; n++)
    {
        for(m=0; m < M->m; m++)
        {
            printf("%.2f\t", M->M[n][m]);
        }
        printf("\n");
    }
}

matrix
*matrix_mult(matrix *M1, matrix *M2)
{
    int n,m,i;
    matrix *M;
    float fsum;
    
    if(M1 == NULL)
    {
        return NULL;
    }
    if(M2 == NULL)
    {
        return NULL;
    }

    if(M1->m != M2->n)
    {
        printf("Las matrices deben ser M1 = N2");
        return NULL;
    }

    M = matrix_create(M1->m, M2->n);
    if(M == NULL)
    {
        return NULL;
    }

    M->m = M1->m;
    M->n = M2->n;

    for(n = 0; n < M->n; n++)
    {
        for(m = 0; m < M->m; m++)
        {
            fsum = 0;
            for(i=0; i<M->m; i++)
            {
                fsum = fsum + M1->M[n][i] * M2->M[i][m];
            }
            M->M[n][m] = fsum;
        }
    }
    
    return M;
}
    
void    
matrix_free(matrix *M)
{
    int i;
    for(i = 0; i < M->n; i++)
        FREE(M->M[i]);
    FREE(M->M);
    FREE(M);
} 

El Makefile para que no  batallen con la compilación:

all:
	gcc matrix.c multm.c -o multm -Wall -ggdb
clean:
	rm multm

Los archivos de prueba m1.txt :

1 1 2 3 5 1 4
3 4 1 2 67 7 3
2.3 45 3 4 56 1
0 3 4 2 54 2 0

Y el archvio de prueba m2.txt:

2 3 1 0 3 4 1 4
2 1 4 5 0 0 1 0
45 3 2.34 5.1 34 1 1 
2 3 4 2.3 4.1 3 4
2 3 4 2.3 4.1 3 4
2 3 4 2.3 4.1 3 4

Espero este tutorial les ha servido. 

 

Share This