Máquinas de Turing en C

Una máquina de Turing es un dispositivo de cálculo lógico que utiliza un input en una o varias cintas que se van moviendo en función de la instrucción que tenga el estado de un autómata, para finalmente obtener un output mediante la reescritura de los datos en la misma cinta. La máquina tiene un cabezal de lectura y este lee el dato que se encuentra en la posición de la cinta. Por buscar una similitud lo mas parecido sería un casette de música, que se rebobina o se adelanta según la canción que nos interese, en la cual podremos borrar o grabar otra canción donde queramos. En el caso de una máquina de Turing de una cinta se define dicha máquina como una 7-tupla de la siguiente manera:

M=(Q, \Sigma, \Gamma, s, \_, F, \delta)

donde:

Símbolo Descripción
\Gamma Conjunto finito de símbolos que pueden aparecer en la cinta.
\Sigma Alfabeto de entrada a la máquina un conjunto finito de símbolos (menos el espacio en blanco).
Q Conjunto finito de estados.
s \in Q Estado inicial en el que empieza la máquina.
\_ \in \Gamma Símbolo denominado blanco y es el único símbolo que se puede repetir un número infinito de veces. Es muy habitual que el símbolo para referirse al blanco sea 'b' 'B' o '\#'.
F \subseteq Q Conjunto de estados finales de aceptación.
\delta: Q \times \Gamma \rightarrow Q \times \Gamma \times \{L,R\} Función de transición donde L es un movimiento a la izquierda y R es el movimiento a la derecha.

Cada función de transición se escribe así:

\delta(q_{0},0) = (X, R, q_{1})

Y se interpreta de la siguiente manera:

  1. La máquina se encuentra en el estado q_{0}
  2. Lee un 0
  3. Sobreescribe el 0 con una X
  4. El cabezal se mueve en la cinta una posición a la derecha (Right)
  5. Finalmente se cambia al estado q_{1}

Teniendo claros los conceptos mínimos de los que se compone una máquina de Turing se puede diseñar una para saber si una cadena de entrada cumple una expresión lógico-matemática determinada. A continuación algunos ejemplos.

Cadenas con un numero de 'x' seguidas del mismo número de 'y'

La notación de este Lenguaje Independiente del Contexto (LIC) o expresión regular es:

x^{n}y^{n} | n \geqslant 0

Esta notación representa cualquier cadena compuesta de un número de caracteres x seguido del mismo número de caracteres y, y que el número de veces que aparecen estos caracteres sea mayor o igual que 0. Cualquiera de las siguientes cadenas de entrada sería válida:

(cadena \ vacia)
xy
xxyy
xxxyyy
xxxxyyyy
\vdots

Y las siguientes serían cadenas no válidas o no aceptadas:

x
yx
xyxy
xxxyy
xyyx
\vdots

Donde x e y pueden ser otros símbolos. Para este ejemplo se van a usar los símbolos pertenecientes al conjunto binario, o sea 0 y 1, y n valdrá 3. Así pues, estos son los datos que conforman la máquina de Turing:

\Gamma \rightarrow \{0, 1\}
\Sigma \rightarrow 000111
Q \rightarrow \{q_{0}, q_{1}, q_{2}, q_{3}\}
s \rightarrow q_{0}
F \rightarrow q_{3}

Las funciones de transición serán las siguientes:

\delta(q_{0},0) = (X, R, q_{1})
\delta(q_{0},Y) = (Y, R, q_{3})
\delta(q_{0},\_) = (\_, STOP, q_{4})
\delta(q_{1},0) = (0, R, q_{1})
\delta(q_{1},1) = (Y, L, q_{2})
\delta(q_{1},Y) = (Y, R, q_{1})
\delta(q_{2},0) = (0, L, q_{2})
\delta(q_{2},1) = (1, L, q_{2})
\delta(q_{2},X) = (X, R, q_{0})
\delta(q_{2},Y) = (Y, L, q_{2})
\delta(q_{3},Y) = (Y, R, q_{3})
\delta(q_{3},\_) = (\_, STOP, q_{4})

Esta lista de funciones de transición también se puede representar mediante la siguiente tabla:

Además de la lista de funciones o la tabla anterior, las funciones de transición también se pueden representar mediante el diagrama de un autómata. El ejemplo de máquina de Turing para el lenguaje 0^{n}1^{n} | n \geqslant 0 se representaría con el siguiente autómata determinista.

A continuación un vídeo que he hecho -como he podido- en el que se puede apreciar el progreso de cada una de las funciones sobre la cinta.

Una vez explicado en detalle cómo funciona una máquina de Turing se puede extrapolar dicho funcionamiento a un programa, por ejemplo en lenguaje de programación C. Existen varias maneras de hacer un programa en C que sea capaz de evaluar si una cadena de entrada contiene un número de x seguido del mismo número de y, y desde luego hacerlo con una simulación de una máquina de Turing como la que se explica a continuación no es la mejor manera, pero sirve para comprender "la lógica" sobre su funcionamiento, que es realmente el objetivo de este artículo.

El programa tendrá al inicio una serie de variables y constantes. También la declaración de algunos procedimientos que se invocarán durante varias partes del programa, según el flujo de datos que vaya leyendo la máquina. Son los siguientes:

void estado_q0(char*, int);
void estado_q1(char*, int);
void estado_q2(char*, int);
void estado_q3(char*, int);
void stop(bool);
void addBlankL(char *w);
void addBlankR(char *w);
void salidaTrue();
void salidaFalse();
void printLog(char *w, char *ea, int i, char x, char m, char *e);
void stop(bool resultado);
int linea(int s);
void printTablaMT();

Se trata de procedimientos que contienen la lógica de cada uno de los 4 estados definidos en Q, y además hay un último procedimiento 'void stop(bool)' que servirá para detener la máquina cuando se llegue a un estado de aceptación (en nuestro caso q_{3}) o también cuando se llegue a un estado en el que no existen instrucciones para un símbolo \in \Gamma + \{\_\} leído, como por ejemplo \delta(q_{0},X) o \delta(q_{2},\_).

Procedimiento 'void estado_q0(char *w, int i)'

Representa la lógica del estado q_{0}, el código es el siguiente:

void estado_q0(char *w, int i) {
	estadoActual = "q0";

	if (w[i] == '0') {
		printLog(w, estadoActual, i, 'X', 'R', "q1");
		w[i] = 'X';
		estado_q1(w, i += 1);
	} else if (w[i] == '1' || w[i] == 'X' || w[i] == '_') {
		stop(false);
	} else if (w[i] == 'Y') {
		printLog(w, estadoActual, i, 'Y', 'R', "q3");
		estado_q3(w, i += 1);
	}
}

Ejecuta un condicional que en función del símbolo leído por el cabezal solo continúa si dicho símbolo es 0 o Y. Si lee cualquier otro símbolo, o sea 1, X o un blanco \_, la máquina se detiene invocando el procedimiento 'stop(false);' e imprime por pantalla un mensaje indicando que la cadena de entrada no satisface (false) el lenguaje (LIC) definido para esta máquina de Turing. Tanto este procedimiento como los demás del tipo 'estado_qn' reciben dos argumentos, el primero es el puntero a la cadena o string de entrada y el segundo es la posición del cabezal de lectura. Esta posición del cabezal se incrementa +1 si el cabezal se desplaza hacia la derecha (R) y se decrementa -1 si el cabezal se desplaza a la izquierda (L).

Procedimiento 'void estado_q1(char *w, int i)'

Representa la lógica del estado q_{1}, el código es el siguiente:

void estado_q1(char *w, int i) {
	estadoActual = "q1";

	if (w[i] == '0') {
		printLog(w, estadoActual, i, '0', 'R', "q1");
		estado_q1(w, i += 1);
	} else if (w[i] == '1') {
		printLog(w, estadoActual, i, 'Y', 'L', "q2");
		w[i] = 'Y';
		estado_q2(w, i -= 1);
	} else if (w[i] == 'X' || w[i] == '_') {
		stop(false);
	} else if (w[i] == 'Y') {
		printLog(w, estadoActual, i, 'Y', 'R', "q1");
		estado_q1(w, i += 1);
	}
}

Ejecuta un condicional que hace que el programa continúe solo si el símbolo leído es 0, 1 o Y. Así pues, si el símbolo leído es X o \_ la máquina se detendrá.

Procedimiento 'void estado_q2(char *w, int i)'

Representa la lógica del estado q_{2}, el código es el siguiente:

void estado_q2(char *w, int i) {
	estadoActual = "q2";

	if (w[i] == '0') {
		printLog(w, estadoActual, i, '0', 'L', "q2");
		estado_q2(w, i -= 1);
	} else if (w[i] == '1' || w[i] == '_') {
		stop(false);
	} else if (w[i] == 'X') {
		printLog(w, estadoActual, i, 'X', 'R', "q0");
		estado_q0(w, i += 1);
	} else if (w[i] == 'Y') {
		printLog(w, estadoActual, i, 'Y', 'L', "q2");
		estado_q2(w, i -= 1);
	}
}

Ejecuta un condicional que hace que el programa continúe solo si el símbolo leído es 0, X o Y. Así pues, si el símbolo leído es 1 o \_ la máquina se detendrá.

Procedimiento 'void estado_q3(char *w, int i)'

Representa la lógica del estado q_{3}, el código es el siguiente:

void estado_q3(char *w, int i) {
	estadoActual = "q3";

	if (w[i] == '0' || w[i] == '1' || w[i] == 'X') {
		stop(false);
	} else if (w[i] == 'Y') {
		printLog(w, estadoActual, i, 'Y', 'R', "q3");
		estado_q3(w, i += 1);
	} else if (w[i] == '_') {
		printLog(w, estadoActual, i, '_', 'R', "STOP");
		stop(true);
	}
}

Ejecuta un condicional que hace que el programa continúe solo si el símbolo leído es Y. Así pues, si el símbolo leído es 0, 1, o X la máquina se detendrá indicando que no satisface el LIC de entrada, pero si el símbolo leído es un blanco \_ la máquina se detendrá indicando que la cadena de entrada si satisface el lenguaje mediante esta máquina de Turing.

Programa completo

A continuación el programa completo con comentarios en el código.

/*
 * turinMachine v0.05
 * Copyleft - 2017  Javier Dominguez Gomez
 * Written by Javier Dominguez Gomez <jdg@member.fsf.org>
 * GnuPG Key: D6648E2B
 * Madrid, Spain
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Compilation: gcc -std=gnu99 -O0 -g3 -Wall -c -o turing.o "turing.c"
 *              gcc -o turing turing.o
 *
 * Usage:       ./turing
 *
 * Case {x^{n}y^{n} | n>=0}
 * +--------------------------------------------------------------------+
 * |        0           1           X           Y           _           |
 * +--------------------------------------------------------------------+
 * | q0     (X,R,q1)    -           -           (Y,R,q3)    (_,STOP,q4) |
 * | q1     (0,R,q1)    (Y,L,q2)    -           (Y,R,q1)    -           |
 * | q2     (0,L,q2)    (1,L,q2)    (X,R,q0)    (Y,L,q2)    -           |
 * | q3     -           -           -           (Y,R,q3)    (_,STOP,q4) |
 * | q4     -           -           -           -           (STOP)      |
 * +--------------------------------------------------------------------+
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdbool.h>

/* Longitud maxima + 1. */
#define MAX_LONG 256

/* Declaracion de variables globales. */
char *estadoActual;
char *funcion = "0^{n}1^{n} | n >= 0";
int i, longitudW;

void estado_q0(char*, int);
void estado_q1(char*, int);
void estado_q2(char*, int);
void estado_q3(char*, int);
void stop(bool);
void addBlankL(char *w);
void addBlankR(char *w);
void salidaTrue();
void salidaFalse();
void printLog(char *w, char *ea, int i, char x, char m, char *e);
void stop(bool resultado);
int linea(int s);
void printTablaMT();

int main(int argc, char *argv[]) {

	/* Se asigna memoria para la cadena w. */
	char *w = malloc(MAX_LONG);

	printf("Introduce una cadena: ");

	/* Guarda la cadena w, con limite de longitud. */
	fgets(w, MAX_LONG, stdin);

	/* Se obtiene la logitud de la cadena de entrada,
	 * sin tener en cuanta el salto de linea. */
	longitudW = strlen(w) - 1;

	/* Elimina salto de linea del final, si existiera. */
	if ((longitudW + 1 > 0) && (w[longitudW] == '\n')) {
		w[longitudW] = '\0';
	}

	/* Primera condicion es que la cadena debe ser par */
	if (strlen(w) % 2 != 0) {
		salidaFalse();
	} else if(strlen(w) == 0){
		/* Acepta la cadena vacia. */
		salidaTrue();
	} else {
		printTablaMT();

		/* Se incorpora un blanco por la izquierda
		 * y otro por la derecha de la cadena w. */
		addBlankL(w);
		addBlankR(w);

		/* Le digo a la maquina de Turing que empiece
		 * por el caracter w[1] en el estado q0 */
		estado_q0(w, 1);
	}

	/* Se libera la memoria reservada para la daena w. */
	free(w);

	return 0;
}

void addBlankL(char *w) {
	/* Recorro el array de derecha a izquierda y desplazo
	 * los caracteres una posicion a la derecha */
	for (i = strlen(w); i > 0; i--) {
		w[i] = w[i - 1];
	}

	/* Concateno la cadena w con un blanco por la izquierda. */
	w[i] = '_';
}

void addBlankR(char *w) {
	/* Concateno la cadena w con un blanco por la derecha. */
	strcat(w, "_");
}

void salidaTrue() {
	printf("\n\nEnhorabuena! se cumple que %s\n\n", funcion);
}

void salidaFalse() {
	printf("\n\nNo se cumple que %s\n\n", funcion);
}

void printLog(char *w, char *ea, int i, char x, char m, char *e) {
	printf(
			"\nw es: \"%s\". Estoy en %s, leo \"%c\", lo cambio por \"%c\", me muevo a la %c y me voy al estado %s.",
			w, ea, w[i], x, m, e);
}

void estado_q0(char *w, int i) {
	estadoActual = "q0";

	// Si leo 0
	if (w[i] == '0') {

		printLog(w, estadoActual, i, 'X', 'R', "q1");

		// lo cambio por X
		w[i] = 'X';

		// me muevo a la derecha (R) y me voy al estado q1.
		estado_q1(w, i += 1);

		// Si leo 1, X o un blanco (_)
	} else if (w[i] == '1' || w[i] == 'X' || w[i] == '_') {

		// se para la maquina de Turing con un resultado false.
		stop(false);

		// Si leo Y
	} else if (w[i] == 'Y') {

		printLog(w, estadoActual, i, 'Y', 'R', "q3");

		// dejo Y sin cambiar, me muevo a la derecha (R) y me voy al estado q3.
		estado_q3(w, i += 1);
	}
}

void estado_q1(char *w, int i) {
	estadoActual = "q1";

	// Si leo 0
	if (w[i] == '0') {

		printLog(w, estadoActual, i, '0', 'R', "q1");

		// dejo 0 sin cambiar, me muevo a la derecha (R) y sigo en el estado q1.
		estado_q1(w, i += 1);

		// Si leo 1
	} else if (w[i] == '1') {

		printLog(w, estadoActual, i, 'Y', 'L', "q2");

		// lo cambio por Y
		w[i] = 'Y';

		// me muevo a la izquierda (L) y me voy al estado q2.
		estado_q2(w, i -= 1);

		// Si leo X o un blanco (_)
	} else if (w[i] == 'X' || w[i] == '_') {

		// se para la maquina de Turing con un resultado false.
		stop(false);

		// Si leo Y
	} else if (w[i] == 'Y') {

		printLog(w, estadoActual, i, 'Y', 'R', "q1");

		// dejo Y sin cambiar, me muevo a la derecha (R) y me voy al estado q1.
		estado_q1(w, i += 1);
	}
}

void estado_q2(char *w, int i) {
	estadoActual = "q2";

	// Si leo 0
	if (w[i] == '0') {

		printLog(w, estadoActual, i, '0', 'L', "q2");

		// dejo 0 sin cambiar, me muevo a la izquierda (L) y sigo en el estado q2.
		estado_q2(w, i -= 1);

		// Si leo 1 o un blanco (_)
	} else if (w[i] == '1' || w[i] == '_') {

		// se para la maquina de Turing con un resultado false.
		stop(false);

		// Si leo X
	} else if (w[i] == 'X') {

		printLog(w, estadoActual, i, 'X', 'R', "q0");

		// dejo X sin cambiar, me muevo a la derecha (R) y me voy al estado q0.
		estado_q0(w, i += 1);

		// Si leo Y
	} else if (w[i] == 'Y') {

		printLog(w, estadoActual, i, 'Y', 'L', "q2");

		// dejo Y sin cambiar, me muevo a la izquierda (L) y me voy al estado q2.
		estado_q2(w, i -= 1);
	}
}

void estado_q3(char *w, int i) {
	estadoActual = "q3";

	// Si leo 0, 1 o X
	if (w[i] == '0' || w[i] == '1' || w[i] == 'X') {

		// se para la maquina de Turing con un resultado false.
		stop(false);

		// Si leo Y
	} else if (w[i] == 'Y') {

		printLog(w, estadoActual, i, 'Y', 'R', "q3");

		// dejo Y sin cambiar, me muevo a la derecha (R) y sigo en el estado q3.
		estado_q3(w, i += 1);

		// Si leo un blanco (_)
	} else if (w[i] == '_') {

		printLog(w, estadoActual, i, '_', 'R', "STOP");

		// se para la maquina de Turing con un resultado true.
		stop(true);
	}
}

void stop(bool resultado) {
	if (resultado) {
		salidaTrue();
	} else {
		salidaFalse();
	}
}

int linea(int s) {
	fprintf(stdout,"+");
	for (int i = 0; i <= s; i++) {
		fprintf(stdout,"-");
	}
	fprintf(stdout,"+\n");
	return 0;
}

void printTablaMT(){
	printf("\nMaquina de Turing para %s\n\n", funcion);
	linea(62);
	printf("|%-5.5s      %-5.5s      %-5.5s      %-5.5s      %-5.5s   %-10.10s |\n", " ", "0", "1", "X", "Y", "    _");
	linea(62);
	printf("|%-5.5s   %-8.8s   %-8.8s   %-8.8s   %-8.8s   %-10.10s |\n", " q0", "(X,R,q1)", "   -", "   -", "(Y,R,q3)", "    _");
	printf("|%-5.5s   %-8.8s   %-8.8s   %-8.8s   %-8.8s   %-10.10s |\n", " q1", "(0,R,q1)", "(Y,L,q2)", "   -", "(Y,R,q1)", "    _");
	printf("|%-5.5s   %-8.8s   %-8.8s   %-8.8s   %-8.8s   %-10.10s |\n", " q2", "(0,L,q2)", "   -", "(X,R,q0)", "(Y,L,q2)", "    _");
	printf("|%-5.5s   %-8.8s   %-8.8s   %-8.8s   %-8.8s   %-10.10s |\n", " q3", "   -", "   -", "   -", "(Y,R,q3)", "(#,R,STOP)");
	linea(62);
}

Por último aclarar que las máquinas de Turing, que no dejan de ser un autómata, actualmente en Ciencias de la Computación se utilizan para crear compiladores de algunos lenguajes de programación. También para desarrollo de inteligencia artificial y redes neuronales. Así pues, no tiene mucho sentido hacer un programa como el de este artículo para otro fin que no sea el de explicar el funcionamiento forense de una máquina de Turing.

Espero que os haya gustado. Como siempre, cualquier pregunta, sugerencia o corrección será bien venida.

Operaciones con matrices en C

En este artículo voy a explicar cómo se realizan algunas de las operaciones mas básicas con matrices y su implementación en un programa C que te calculará automáticamente el determinante, la matriz traspuesta, la matriz adjunta y la matriz invertida (si existiera). Para el ejemplo voy a usar una matriz cuadrada de orden 3. Pero antes refresquemos la memoria recordando qué es cada cosa y poniendo un fragmento de código para ver representada la explicación en C.

Matriz inicial de orden 3

Tenemos una matriz de entrada, por ejemplo:

A=\begin{pmatrix}3 & 5 & 1 \\ 2 & -1 & 0 \\ -1 & 3 & 1\end{pmatrix}

La primera parte del programa comienza con un bucle que nos pide introducir los datos de nuestra matriz cuadrada de orden 3:

Matriz matriz = malloc(sizeof(struct MatrizStruct));

for (i = 0; i < 3; i++) {
	for (j = 0; j < 3; j++) {
		printf("Numero %d,%d: ", i + 1, j + 1);
		scanf("%d", &matriz->matrix_a[i][j]);
	}
}

./Matrices
Numero 1,1: 3
Numero 1,2: 5
Numero 1,3: 1
Numero 2,1: 2
Numero 2,2: -1
Numero 2,3: 0
Numero 3,1: -1
Numero 3,2: 3
Numero 3,3: 1

Los datos se van a ir asignando a las direcciones de memoria correspondientes de cada coordenada ij en un array bidimensional que previamente he declarado en un struct, pensado para almacenar toda la información sobre la matriz.

struct MatrizStruct {
	int matrix_a[3][3];    //Este es el Array inicial
	int matrix_aT[3][3];
	int matrix_aAdj[3][3];
	int matrix_aInv[3][3];
};

Antes de todo esto, en un header de C he declarado Matriz como un tipo de dato (typedef). De este modo podré utilizar y modificar toda la estructura de la que se compone cada matriz.

struct MatrizStruct;
typedef struct MatrizStruct* Matriz;

Una vez que ya tenemos la matriz de entrada ya podemos hacer operaciones con ella.

Determinante

El determinante de una matriz (cuadrada) A lo designaremos por |A| o bien por det A, aunque la primera forma es la mas utilizada. Se trata del número que se le asocia tras realizar el siguiente cálculo:

\begin{vmatrix} A \end{vmatrix} = \begin{vmatrix}a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{vmatrix}

\begin{vmatrix} A \end{vmatrix} = a_{11}\begin{vmatrix}a_{22} & a_{23} \\ a_{32} & a_{32} \end{vmatrix} - a_{12}\begin{vmatrix}a_{21} & a_{23} \\ a_{31} & a_{32} \end{vmatrix} + a_{13}\begin{vmatrix}a_{21} & a_{22} \\ a_{31} & a_{32} \end{vmatrix}

Cuyo desarrollo es idéntico al que nos proporciona la conocida Regla de Sarrus:

\begin{vmatrix} A \end{vmatrix} = a_{11}a_{22}a_{33} + a_{21}a_{32}a_{13} + a_{31}a_{23}a_{12} - a_{13}a_{22}a_{31} - a_{23}a_{32}a_{11} - a_{33}a_{21}a_{12}

Para obtener el determinante de la matriz de entrada he utilizado la siguiente función a la que es necesario pasarle por argumento la estructura Matriz. Esta calcula el determinante y devuelve un dato de tipo int.

int determinante(Matriz m) {
   return (m->matrix_a[0][0] * m->matrix_a[1][1] * m->matrix_a[2][2])
   + (m->matrix_a[2][0] * m->matrix_a[0][1] * m->matrix_a[1][2])
   + (m->matrix_a[0][2] * m->matrix_a[1][0] * m->matrix_a[2][1])
   - (m->matrix_a[2][0] * m->matrix_a[1][1] * m->matrix_a[0][2])
   - (m->matrix_a[0][0] * m->matrix_a[1][2] * m->matrix_a[2][1])
   - (m->matrix_a[0][1] * m->matrix_a[1][0] * m->matrix_a[2][2]);
}

En este ejemplo, el determinante de la matriz de entrada A es el siguiente:

|A|=\begin{vmatrix}3 & 5 & 1 \\ 2 & -1 & 0 \\ -1 & 3 & 1\end{vmatrix}=-8

Matriz traspuesta

La matriz traspuesta de A se designa A^{T} y es una matriz en la que los elementos que formaban una fila en A pasan a ser los elementos de una columna en A^{T}. Véase en la siguiente notación:

A=\begin{pmatrix}a_{11} & a_{12} & a_{13} \\ a_{21} & a_{22} & a_{23} \\ a_{31} & a_{32} & a_{33} \end{pmatrix} \Rightarrow A^{T}=\begin{pmatrix}a_{11} & a_{21} & a_{31} \\ a_{12} & a_{22} & a_{32} \\ a_{13} & a_{23} & a_{33} \end{pmatrix}

Por ejemplo:

A=\begin{pmatrix}3 & 5 & 1 \\ 2 & -1 & 0 \\ -1 & 3 & 1\end{pmatrix}\Rightarrow A^{T}=\begin{pmatrix}3 & 2 & -1 \\ 5 & -1 & 3 \\ 1 & 0 & 1\end{pmatrix}

Para obtener la matriz traspuesta de la matriz de entrada he utilizado la siguiente función a la que también es necesario pasarle por argumento la estructura Matriz. Esta función no solo modifica el array del struct si no que también devuelve un dato de tipo Matriz. Esto nos puede venir muy bien para otro tipo de operaciones que no se tratan en este programa.

Matriz traspuesta(Matriz m) {
	m->matrix_aT[0][0] = m->matrix_a[0][0];
	m->matrix_aT[0][1] = m->matrix_a[1][0];
	m->matrix_aT[0][2] = m->matrix_a[2][0];
	m->matrix_aT[1][0] = m->matrix_a[0][1];
	m->matrix_aT[1][1] = m->matrix_a[1][1];
	m->matrix_aT[1][2] = m->matrix_a[2][1];
	m->matrix_aT[2][0] = m->matrix_a[0][2];
	m->matrix_aT[2][1] = m->matrix_a[1][2];
	m->matrix_aT[2][2] = m->matrix_a[2][2];

	return m;
}

Matriz adjunta

Se denomina matriz adjunta de A o Adj(A) a la matriz \begin{pmatrix}A^{j}_{i}\end{pmatrix} obtenida al sustituir los elementos (a^{i}_{j}) por sus adjuntos A^{j}_{i}, es decir a la matriz formada por los adjuntos de los elementos. Por ejemplo:

A=\begin{pmatrix}3 & 5 & 1 \\ 2 & -1 & 0 \\ -1 & 3 & 1\end{pmatrix}

El cálculo sería el siguiente:

A^{1}_{1}=\begin{vmatrix} -1 & 0 \\ 3 & 1 \end{vmatrix}=-1, \ A^{2}_{1}=-\begin{vmatrix} \ \ 2 & 0 \\ -1 & 1 \end{vmatrix}=-2, \ A^{3}_{1}=\begin{vmatrix} \ \ 2 & -1 \\ -1 & 3 \end{vmatrix}=5

A^{1}_{2}=\begin{vmatrix} 5 & 1 \\ 3 & 1 \end{vmatrix}=-2, \ A^{2}_{2}=-\begin{vmatrix} \ \ 3 & 1 \\ -1 & 1 \end{vmatrix}=4, \ A^{3}_{2}=\begin{vmatrix} \ \ 3 & 5 \\ -1 & \ \ 3 \end{vmatrix}=-14

A^{1}_{3}=\begin{vmatrix} 5 & 1 \\ \ \ -1 & 0 \end{vmatrix}=1, \ A^{2}_{3}=-\begin{vmatrix} 3 & 1 \\ 2 & 0 \end{vmatrix}=2, \ A^{3}_{3}=\begin{vmatrix} 3 & 5 \\ 2 & \ \ -1 \end{vmatrix}=-13

Así pues, la matriz adjunta de A es:

Adj(A)=\begin{pmatrix} A^{1}_{1} & A^{2}_{1} & A^{3}_{1} \\ A^{1}_{2} & A^{2}_{2} & A^{3}_{2} \\ A^{1}_{3} & A^{2}_{3} & A^{3}_{3} \end{pmatrix}=\begin{pmatrix}-1 & -2 & 5 \\ -2 & 4 & -14 \\ 1 & 2 & -13\end{pmatrix}

Al igual que con la función para obtener la matriz traspuesta, la matriz adjunta también se obtiene con una función a a que pasaremos como argumento la estructura Matriz, a continuación se calculará cada elemento del array bidimensional perteneciente al struct y se asignarán los valores para esta nueva matriz. Esta función también tiene un return de tipo Matriz.

Matriz adjunta(Matriz m) {
	m->matrix_aAdj[0][0] = m->matrix_a[1][1] * m->matrix_a[2][2] - m->matrix_a[2][1] * m->matrix_a[1][2];
	m->matrix_aAdj[1][0] = -(m->matrix_a[1][0] * m->matrix_a[2][2] - m->matrix_a[2][0] * m->matrix_a[1][2]);
	m->matrix_aAdj[2][0] = m->matrix_a[1][0] * m->matrix_a[2][1] - m->matrix_a[2][0] * m->matrix_a[1][1];
	m->matrix_aAdj[0][1] = -(m->matrix_a[0][1] * m->matrix_a[2][2] - m->matrix_a[2][1] * m->matrix_a[0][2]);
	m->matrix_aAdj[1][1] = m->matrix_a[0][0] * m->matrix_a[2][2] - m->matrix_a[2][0] * m->matrix_a[0][2];
	m->matrix_aAdj[2][1] = -(m->matrix_a[0][0] * m->matrix_a[2][1] - m->matrix_a[2][0] * m->matrix_a[0][1]);
	m->matrix_aAdj[0][2] = m->matrix_a[0][1] * m->matrix_a[1][2] - m->matrix_a[1][1] * m->matrix_a[0][2];
	m->matrix_aAdj[1][2] = -(m->matrix_a[0][0] * m->matrix_a[1][2] - m->matrix_a[1][0] * m->matrix_a[0][2]);
	m->matrix_aAdj[2][2] = m->matrix_a[0][0] * m->matrix_a[1][1] - m->matrix_a[1][0] * m->matrix_a[0][1];

	return m;
}

Matriz inversa

Solo tienen matriz inversa aquellas matrices que su determinante es distinto de cero, en cuyo caso su matriz inversa A^{-1} es igual a la traspuesta de la matriz adjunta dividida por el determinante de A. Esta sería la notación:

A^{-1}=\frac{1}{\begin{vmatrix} A \end{vmatrix}}\begin{pmatrix} A^{1}_{1} & A^{1}_{2} & ...... & A^{1}_{n} \\ A^{2}_{1} & A^{2}_{2} & ...... & A^{2}_{n} \\ ... & ... & ...... & ... \\ A^{n}_{1} & A^{n}_{2} & ...... & A^{n}_{n}\end{pmatrix}

Una notación mas reducida seria:

A^{-1}=\frac{1}{\begin{vmatrix} A \end{vmatrix}}(Adj(A))^T

Para calcular la matriz inversa también hay una función que recibe la estructura Matriz como argumento. Se asignan los valores correspondientes a la matriz traspuesta de la adjunta (Adj(A))^{T} y finalmente tiene un return de la estructura de tipo Matriz ya modificada. Solo se invocará a esta función en el caso de que el determinante sea distinto de 0.

Matriz inversa(Matriz m) {
	m->matrix_aInv[0][0] = m->matrix_aAdj[0][0];
	m->matrix_aInv[1][0] = m->matrix_aAdj[1][0];
	m->matrix_aInv[2][0] = m->matrix_aAdj[2][0];
	m->matrix_aInv[0][1] = m->matrix_aAdj[0][1];
	m->matrix_aInv[1][1] = m->matrix_aAdj[1][1];
	m->matrix_aInv[2][1] = m->matrix_aAdj[2][1];
	m->matrix_aInv[0][2] = m->matrix_aAdj[0][2];
	m->matrix_aInv[1][2] = m->matrix_aAdj[1][2];
	m->matrix_aInv[2][2] = m->matrix_aAdj[2][2];

	return m;
}

Pero ojo, Esta función solo asigna los valores de la matriz traspuesta de la adjunta. No se generan fracciones por que el programa las ejecutaría y en ocasiones daría como resultado números decimales muy largos y no quedaría presentable, así pues, he decidido que se almacenen como numerador de la fracción que mas adelante se imprimirá por pantalla añadiéndole su denominador, que será el determinante de la matriz inicial |A|.

printf("\nMatriz inversa:\n");
	/* Solo se calculara la inversa si el determinante es != 0 */
	if(determinante(m) != 0){
		inversa(m);

		/* Imprime la matriz inversa. */
		for (i = 0; i < 3; i++) {
			for (j = 0; j < 3; j++) {
				printf("%3d/%d", m->matrix_aInv[i][j], determinante(m));
			}
			printf("\n");
		}
	} else {
		printf("No tiene invertida por que su determinante es 0.\n");
	}

Programa completo

Si queréis probar este modesto programa en vuestra máquina aquí os dejo el contenido de los dos archivos que necesitáis para compilarlo. Se admiten correcciones y mejoras que seguro que tiene un montón.

Archivo matriz.h
/*
 * matriz.h v0.01
 * Copyleft - 2017  Javier Dominguez Gomez
 * Written by Javier Dominguez Gomez <jdg@member.fsf.org>
 * GnuPG Key: 6ECD1616
 * Madrid, Spain
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#ifndef MATRIZ_H_
#define MATRIZ_H_

struct MatrizStruct;
typedef struct MatrizStruct* Matriz;

void printDatos(Matriz m);
int determinante(Matriz m);
Matriz traspuesta(Matriz m);
Matriz adjunta(Matriz m);
Matriz inversa(Matriz m);

#endif /* MATRIZ_H_ */

 

Archivo matriz.c
/*
 * matriz.c v0.01
 * Copyleft - 2017  Javier Dominguez Gomez
 * Written by Javier Dominguez Gomez <jdg@member.fsf.org>
 * GnuPG Key: 6ECD1616
 * Madrid, Spain
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Compilation: gcc -O0 -g3 -Wall -c -MF"matriz.d" -MT"matriz.o" -o "matriz.o" "matriz.c"
 *              gcc  -o "matriz" ./matriz.o
 *
 * Usage:       ./matriz
 */

#include "matriz.h"

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

int i, j;

struct MatrizStruct {
	int matrix_a[3][3];
	int matrix_aT[3][3];
	int matrix_aAdj[3][3];
	int matrix_aInv[3][3];
};

int determinante(Matriz m) {
	return (m->matrix_a[0][0] * m->matrix_a[1][1] * m->matrix_a[2][2])
			+ (m->matrix_a[2][0] * m->matrix_a[0][1] * m->matrix_a[1][2])
			+ (m->matrix_a[0][2] * m->matrix_a[1][0] * m->matrix_a[2][1])
			- (m->matrix_a[2][0] * m->matrix_a[1][1] * m->matrix_a[0][2])
			- (m->matrix_a[0][0] * m->matrix_a[1][2] * m->matrix_a[2][1])
			- (m->matrix_a[0][1] * m->matrix_a[1][0] * m->matrix_a[2][2]);
}

Matriz traspuesta(Matriz m) {
	m->matrix_aT[0][0] = m->matrix_a[0][0];
	m->matrix_aT[0][1] = m->matrix_a[1][0];
	m->matrix_aT[0][2] = m->matrix_a[2][0];
	m->matrix_aT[1][0] = m->matrix_a[0][1];
	m->matrix_aT[1][1] = m->matrix_a[1][1];
	m->matrix_aT[1][2] = m->matrix_a[2][1];
	m->matrix_aT[2][0] = m->matrix_a[0][2];
	m->matrix_aT[2][1] = m->matrix_a[1][2];
	m->matrix_aT[2][2] = m->matrix_a[2][2];

	return m;
}

Matriz adjunta(Matriz m) {
	m->matrix_aAdj[0][0] = m->matrix_a[1][1] * m->matrix_a[2][2] - m->matrix_a[2][1] * m->matrix_a[1][2];
	m->matrix_aAdj[1][0] = -(m->matrix_a[1][0] * m->matrix_a[2][2] - m->matrix_a[2][0] * m->matrix_a[1][2]);
	m->matrix_aAdj[2][0] = m->matrix_a[1][0] * m->matrix_a[2][1] - m->matrix_a[2][0] * m->matrix_a[1][1];
	m->matrix_aAdj[0][1] = -(m->matrix_a[0][1] * m->matrix_a[2][2] - m->matrix_a[2][1] * m->matrix_a[0][2]);
	m->matrix_aAdj[1][1] = m->matrix_a[0][0] * m->matrix_a[2][2] - m->matrix_a[2][0] * m->matrix_a[0][2];
	m->matrix_aAdj[2][1] = -(m->matrix_a[0][0] * m->matrix_a[2][1] - m->matrix_a[2][0] * m->matrix_a[0][1]);
	m->matrix_aAdj[0][2] = m->matrix_a[0][1] * m->matrix_a[1][2] - m->matrix_a[1][1] * m->matrix_a[0][2];
	m->matrix_aAdj[1][2] = -(m->matrix_a[0][0] * m->matrix_a[1][2] - m->matrix_a[1][0] * m->matrix_a[0][2]);
	m->matrix_aAdj[2][2] = m->matrix_a[0][0] * m->matrix_a[1][1] - m->matrix_a[1][0] * m->matrix_a[0][1];

	return m;
}

Matriz inversa(Matriz m) {
	/* Una matriz solo es invertible si su determinante es distinto de 0. */

	/* La matriz inversa es igual a (1/|A|)*(Adj(A))^t */

	/* Construyo solo la matriz traspuesta de la adjunta. Cada
	 * elemento de la matriz sera el numerador de una fraccion que
	 * tendra por denominador el determinite de la matriz de entrada. */
	m->matrix_aInv[0][0] = m->matrix_aAdj[0][0];
	m->matrix_aInv[1][0] = m->matrix_aAdj[1][0];
	m->matrix_aInv[2][0] = m->matrix_aAdj[2][0];
	m->matrix_aInv[0][1] = m->matrix_aAdj[0][1];
	m->matrix_aInv[1][1] = m->matrix_aAdj[1][1];
	m->matrix_aInv[2][1] = m->matrix_aAdj[2][1];
	m->matrix_aInv[0][2] = m->matrix_aAdj[0][2];
	m->matrix_aInv[1][2] = m->matrix_aAdj[1][2];
	m->matrix_aInv[2][2] = m->matrix_aAdj[2][2];

	return m;
}

void printDatos(Matriz m) {

	/* Imprime la matriz de entrada. */
	printf("\nMatriz:\n");
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			printf("%3d", m->matrix_a[i][j]);
		}
		printf("\n");
	}

	/* Imprime su determinante. */
	printf("\nEl determinante es: %d\n", determinante(m));

	/* Imprime la matriz traspuesta. */
	printf("\nMatriz traspuesta:\n");
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			printf("%3d", m->matrix_aT[i][j]);
		}
		printf("\n");
	}

	/* Imprime la matriz adjunta. */
	printf("\nMatriz adjunta:\n");
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			printf("%3d", m->matrix_aAdj[i][j]);
		}
		printf("\n");
	}

	printf("\nMatriz inversa:\n");
	/* Solo se calculara la inversa si el determinante es distinto de cero. */
	if(determinante(m) != 0){
		inversa(m);

		/* Imprime la matriz inversa. */
		for (i = 0; i < 3; i++) {
			for (j = 0; j < 3; j++) {
				printf("%3d/%d", m->matrix_aInv[i][j], determinante(m));
			}
			printf("\n");
		}
	} else {
		printf("No tiene invertida por que su determinante es 0.\n");
	}
}

int main(void) {
	Matriz matriz = malloc(sizeof(struct MatrizStruct));

	/* Pide datos de la matriz 3X3 */
	for (i = 0; i < 3; i++) {
		for (j = 0; j < 3; j++) {
			printf("Numero %d,%d: ", i + 1, j + 1);
			scanf("%d", &matriz->matrix_a[i][j]);
		}
	}

	/* Se calcula todo */
	determinante(matriz);
	traspuesta(matriz);
	adjunta(matriz);

	/* Se muestran todos los datos por pantalla. */
	printDatos(matriz);

	return 0;
}

Para compilarlo solo tenéis que guardar estos dos archivos en una carpeta y ejecutar lo siguiente:

gcc -O0 -g3 -Wall -c -MF"matriz.d" -MT"matriz.o" -o "matriz.o" "matriz.c"
gcc  -o "matriz" ./matriz.o

Una simulación de su ejecución sería la siguiente:

 

Algoritmos de ordenación - método de selección directa

En otro post expliqué cómo podemos ordenar y hacer búsquedas en un array mediante el método de la burbuja, pero este no es el único algoritmo de ordenación que podemos emplear en programación. Esta vez aprenderemos a usar el método de selección directa. Este método es mas sencillo que el anterior, solo tenemos que tomar el elemento n del array, y recorrer uno a uno todos los elementos del array que tiene n a su derecha, incluido él mismo. Si encontramos un elemento a la derecha de n que tiene el menor valor de todos los recorridos (si quisiéramos un orden ascendente) lo intercambiamos, desplazando ese valor menor a la posición de n, y n a la posición de ese valor menor.

A continuación se repite todo el proceso tantas veces como longitud tiene el array, y de este modo obtendríamos un array ordenado. Vemos un ejemplo

#include <stdio.h>

int main(void) {
	/* Declaramos un array de enteros. */
	int vector[4] = { 3, 2, 4, 1 };

	/* Declaramos otras variables que
	 * nos serviran para iterar. */
	int i, j, k, aux;

	/* Ordenamos el array de enteros. */
	for (k = 0; k <= 2; k++) {
		i = k;
		aux = vector[k];
		for (j = k + 1; j <= 3; j++) {
			if (vector[j] < aux) {
				i = j;
				aux = vector[i];
			}
		}
		vector[i] = vector[k];
		vector[k] = aux;
	}

	/* Imprimimos el array de enteros. */
	for (i = 0; i <= 3; i++) {
		printf("%d ", vector[i]);
	}
}

Este ejemplo está hecho en lenguaje C, pero puedes probar a realizarlo en cualquier lenguaje de programación tradicional.

Algoritmos de ordenación - método de la burbuja

Muchas veces cuando estamos haciendo un programa nos puede interesar ordenar una lista de enteros de mayor a menor, o al revés. En todos los lenguajes de programación existen algoritmos de ordenación, y aquí voy a explicar uno de los mas conocidos y sencillo, el método de la burbuja. Tiene este nombre por que (en caso de orden ascendente) hace que el elemento menor de la lista "suba" como una burbuja hasta el principio del array.

El método de la burbuja es muy sencillo, consiste en coger los elementos de un array e ir comparándolos con sus adyacentes y si fuera necesario intercambiarlos, alterando así el orden de los mismos. Si queremos un orden ascendente el intercambio sucede cuando se da el caso en el que el elemento del array en el que nos hemos posicionado es mayor que el que tiene inmediatamente a su derecha. Veámoslo en la siguiente imagen:

En este ejemplo, hecho en C, podemos ver cómo se ordena un array de varios enteros:

#include <stdio.h>

int main(void) {

	/* Declaramos un array de enteros. */
	int vector[4] = { 3, 2, 4, 1 };

	/* Declaramos otras variables que
	 * nos serviran para iterar. */
	int i, j, aux;

	/* Ordenamos el array de enteros. */
	for (i = 0; i <= 2; i++) {
		for (j = 3; j >= i + 1; j--) {
			if (vector[j - 1] > vector[j]) {
				aux = vector[j];
				vector[j] = vector[j - 1];
				vector[j - 1] = aux;
			}
		}
	}

	/* Imprimimos el array de enteros. */
	for (i = 0; i <= 3; i++) {
		printf("%d ", vector[i]);
	}
}

Aunque este ejemplo esta hecho en C, realmente se se puede usar en prácticamente todos los lenguajes de programación tradicionales. ¡Pruébalo!

Quizás te pueda interesar ver este otro post donde explico el método de selección directa.

Leer un fichero en C

Ahora que ya sabemos abrir o crear un fichero y escribir en él voy a explicar como tenemos que hacer para leer su contenido. El mecanismo de lectura es sencillo, cuando abrimos un fichero el puntero se encuentra al inicio de este. Si hacemos una operación de lectura se lee el elemento sobre el que está el puntero, y a continuación el puntero avanza hacia el siguiente elemento, pero hasta que no se haga otra operación de lectura no se lee. A continuación detallo cuales son las principales operaciones o funciones de lectura de ficheros.

fgetc

La función fgetc se utiliza para leer un carácter de un fichero de texto, y devuelve dicho carácter. La sintaxis general es:

fgetc(puntero);

El parámetro puntero es el identificador de la variable puntero a FILE correspondiente al fichero de texto que estamos leyendo. Veamos un ejemplo:

#include <stdio.h>


int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Declaramos la variable caracter de tipo char. */
	char caracter;

	/* Abrimos "fichero1.txt" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("fichero1.txt", "rt");

	if (fichero == NULL) {
		printf("Error: No se ha podido crear el fichero fichero1.txt");
	} else {
		/* Se obtiene el primer caracter del fichero
		 * y se almacena en la variable caracter. */
		caracter = fgetc(fichero);

		printf("El primer caracter leído es: \"%c\"\n", caracter);

		/* Cerramos "fichero1.txt". */
		fclose(fichero);
	}
}

Como podemos ver, tras leer el primer carácter del archivo "fichero1.txt" este se almacena en la variable caracter, y a continuación imprimimos un mensaje indicando cuál es ese primer caracter.

feof

La función feof nos permite detectar el final de un fichero (end-of-file) y así poder averiguar todos los caracteres que existen dentro del mismo. La sintaxis general es:

feof(puntero);

Donde puntero es el identificador de la variable puntero al fichero. Esta llamada a la función feof nos devolverá un 0 si no se ha llegado al final del fichero, y por el contrario devolverá un valor distinto de 0 si se llega al final. Veámoslo con un ejemplo:

#include <stdio.h>

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Declaramos la variable caracter de tipo char. */
	char caracter;

	/* Abrimos "fichero1.txt" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("fichero1.txt", "rt");

	if (fichero == NULL) {
		printf("Error: No se ha podido crear el fichero fichero1.txt");
	} else {
		/* Se obtiene el primer caracter del fichero
		 * y se almacena en la variable caracter. */
		caracter = fgetc(fichero);

		while (feof(fichero) == 0) {
			printf("%c\n", caracter);
			caracter = fgetc(fichero);
		}

		/* Cerramos "fichero1.txt". */
		fclose(fichero);
	}
}

Este ejemplo lo que hace es leer un carácter, a continuación comprueba si hemos llegado al final del fichero, y si no se ha llegado al final lo imprime. Este proceso se repite por cada uno de los caracteres escritos en el fichero (incluidos saltos de línea).

fgets

Con la función fgets podemos leer una cadena de caracteres, su sintaxis general es:

fgets(cadena, tamaño, puntero);

Donde cadena es la variable que va a almacenar la cadena de caracteres que se lea del fichero, tamaño es la cantidad máxima de caracteres que vamos a leer del fichero (y almacenar en cadena), y puntero es el identificador de la variable puntero al fichero. Decimos que tamaño es la cantidad máxima de caracteres que vamos a leer del fichero por que la lectura se podría detener antes si encuentra un salto de línea. La función fgets devuelve la cadena de caracteres leída o NULL si hubiera un error o se llegase al final del archivo. El mismo ejemplo de antes utilizando la función fgets sería así:

#include <stdio.h>

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Declaramos la variable cadena de tipo array char. */
	char cadena[256];

	/* Declaramos la variable reslutado como puntero. */
	char *resultado;

	/* Abrimos "fichero1.txt" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("fichero1.txt", "rt");

	if (fichero == NULL) {
		printf("Error: No se ha podido crear el fichero fichero1.txt");
	} else {
		/* Se obtiene la cadena de caracteres de
		 * tamaño 256 dentro de fichero1.txt. */
		resultado = fgets(cadena, 256, fichero);

		while (resultado != NULL) {
			printf("%s", cadena);
			resultado = fgets(cadena, 256, fichero);
		}

		/* Cerramos "fichero1.txt". */
		fclose(fichero);
	}
}

fread

La función fread ofrece la misma potencia que la función fread, pero para leer, y nos permite leer datos de diferentes tipos. La sintaxis es la misma:

fread(direccion_dato, tamaño, veces, puntero);

Donde direccion_dato es la dirección de la variable o elemento donde queremos almacenar los datos leídos del fichero indicado por la variable puntero. En tamaño debemos indicar el tamaño en bytes que queremos leer del fichero, y en veces indicamos cuántos elementos de tamaño tamaño vamos a leer. Se nos podría plantear la duda de qué valores indicamos en tamaño y veces, pero, como lo normal es que el fichero que vayamos a leer también lo hayamos creado nosotros, conoceremos estos valores, ya que los hemos usado previamente con la función frwrite.

Anteriormente hemos creado un fichero binario, llamado empleados.dat, en el que hemos almacenado estructuras con  la siguiente definición:

struct datos_empleado {
	char nombre[30];
	int edad;
};

Si queremos leer ese fichero binario y  mostrar todas las estructuras almacenadas en él podríamos hacerlo con un programa como este de ejemplo:

#include <stdio.h>

struct datos_empleado {
	char nombre[30];
	int edad;
};

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	struct datos_empleado empleado;

	/* Abrimos "empleados.dat" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("empleados.dat", "rb");

	if (fichero == NULL) {
		printf("Error: No se ha podido crear el fichero empleados.dat");
	} else {
		/* Leemos la primera estructura empleado
		 * del fichero "empleados.dat". */
		fread(&empleado, sizeof(empleado), 1, fichero);

		while (feof(fichero) == 0) {
			printf("\n\nNombre: %s", empleado.nombre);
			printf("\nEdad: %d", empleado.edad);

			/* Si aun no hemos llegado al final del fichero
			 * "empleados.dat" seguimos leyendo estructuras. */
			fread(&empleado, sizeof(empleado), 1, fichero);
		}

		/* Cerramos "empleados.dat". */
		fclose(fichero);
	}
}

De este modo, podremos comprobar que imprime cada una de las estructuras hasta llegar al final del fichero. Bien, ¿y si lo que queremos es que el programa imprima solo una estructura que cumpla un requisito? Por ejemplo, solo los datos de un empleado que tenga una edad determinada. En ese caso podemos hacer un programa que solicite al usuario introducir una edad y pedirle al programa que solo imprima los nombres de los empleados que cumplan con esa condición. Este sería un ejemplo:

#include <stdio.h>

struct datos_empleado {
	char nombre[30];
	int edad;
};

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	struct datos_empleado empleado;
	int filtroEdad;

	/* Creamos un flag con valor inicial 0, solo
	 * cambiara su valor a 1 cuando se encuentre
	 * un empleado con la edad buscada. */
	int encontrado = 0;

	/* Pedimos al usuario una edad a buscar. */
	printf("Edad del empleado: ");
	scanf("%d", &filtroEdad);

	/* Abrimos "empleados.dat" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("empleados.dat", "rb");

	if (fichero == NULL) {
		printf("Error: No se ha podido crear el fichero empleados.dat");
	} else {
		/* Leemos la primera estructura empleado
		 * del fichero "empleados.dat". */
		fread(&empleado, sizeof(empleado), 1, fichero);

		while (feof(fichero) == 0) {

			if(empleado.edad == filtroEdad){
				printf("\n\nNombre: %s", empleado.nombre);
				printf("\nEdad: %d", empleado.edad);

				/* Activamos el flag a true (1), para indicar
				 * que hemos encontrado al algo. */
				encontrado = 1;
			}

			/* Si aun no hemos llegado al final del fichero
			 * "empleados.dat" seguimos leyendo estructuras. */
			fread(&empleado, sizeof(empleado), 1, fichero);
		}

		/* Cerramos "empleados.dat". */
		fclose(fichero);

		if(encontrado == 0){
			printf("\n\nNo existe ningun empleado con edad %d.", filtroEdad);
		}
	}
}

Este es un método muy básico para realizar búsquedas dentro de una fichero, y cumple con el propósito de imprimir solo los datos de los empleados que tienen determinada característica.

fseek

Por último tenemos la función fseek, que nos permitirá acceder directamente a un registro de nuestro archivo. Esto nos puede interesar para leer o para modificar ese registro en concreto. Lo que hace la función fseek es situar el puntero del fichero  en una posición determinada de este. Su sintaxis general es:

fseek(puntero, tamaño_salto, desde);

Donde puntero es la variable puntero del fichero, tamaño_salto es el tamaño en bytes del salto realizado desde la posición desde. El argumento desde tiene tres posibles valores constantes, que son:

Constante Valor Descripción
SEEK_SET 0 Salta desde el principio del fichero.
SEEK_CUR 1 Salta desde la posición actual.
SEEK_END 2 Salta desde el final del fichero.

Siguiendo con el ejemplo anterior, veamos un ejemplo que imprimirá la estructura de datos del empleado que se encuentra en tercer lugar dentro del archivo binario empleados.dat.

#include <stdio.h>

struct datos_empleado {
	char nombre[30];
	int edad;
};

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	struct datos_empleado empleado;

	/* Abrimos "empleados.dat" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("empleados.dat", "rb");

	if (fichero == NULL) {
		printf("Error: No se ha podido crear el fichero empleados.dat");
	} else {

		/* Nos posicionamos en la tercera estructura empleado
		 * desde el inicio del fichero "empleados.dat". */
		fseek(fichero, 2 * sizeof(empleado), SEEK_SET);

		/* Leemos esa estructura. */
		fread(&empleado, sizeof(empleado), 1, fichero);

		/* La imprimimos. */
		printf("\n\nNombre: %s", empleado.nombre);
		printf("\nEdad: %d", empleado.edad);

		/* Cerramos "empleados.dat". */
		fclose(fichero);
	}
}

Voy a explicar la línea:

fseek(fichero, 2 * sizeof(empleado), SEEK_SET);

El parámetro fichero, tal y como ya se ha explicado en varias ocasiones en ejemplos anteriores, indica el puntero del archivo que vamos a leer. Lo tenemos que tener abierto mediante la función fopen previamente. Antes de explicar el segundo parámetro voy a hablar sobre el tercer parámetro, SEEK_SET, que sirve para decirle a la función que vamos a mover el puntero del archivo hasta una determinada distancia desde el inicio del fichero. La cuestión ahora es ¿a qué distancia voy a mover el puntero desde el inicio del fichero? En el ejemplo anterior hemos dicho que nos interesa leer el tercer registro del fichero, bien, pues necesitaremos dar un salto desde el inicio hasta los dos primeros registros, y para ello deberemos indicar esa distancia o tamaño en bytes. Como son dos estructuras las que tenemos que saltar debemos multiplicar por dos el tamaño de una estructura, y para averiguar dinámicamente el tamaño de nuestra estructura empleado podemos utilizar la función sizeof(empleado). Así pues, 2*sizeof(empleado) nos permite saltar dos registros desde el inicio (SEEK_SET) del fichero (fichero), con lo que la llamada a la función fseek hace que el puntero se encuentre al principio del tercer registro. Por último se llama a la función fread que lee el tercer registro y luego lo imprime.

Otro ejemplo de uso de la función fseek es utilizando la constante SEEK_CUR, que realiza un salto desde la posición actual, o lo que es lo mismo, salta un registro. Esto nos puede servir si lo que queremos es imprimir registros uno sí , uno no, por ejemplo los impares, veamos un ejemplo:

#include <stdio.h>

struct datos_empleado {
	char nombre[30];
	int edad;
};

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	struct datos_empleado empleado;

	/* Abrimos "empleados.dat" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("empleados.dat", "rb");

	if (fichero == NULL) {
		printf("Error: No se ha podido crear el fichero empleados.dat");
	} else {

		/* Leemos el primer registro. */
		fread(&empleado, sizeof(empleado), 1, fichero);

		while (!feof(fichero)) {
			/* Imprimimos los datos del registro. */
			printf("\n\nNombre: %s", empleado.nombre);
			printf("\nEdad: %d", empleado.edad);

			/* saltamos un registro desde la posicion actual. */
			fseek(fichero, 1 * sizeof(empleado), SEEK_CUR);

			/* Leemos el siguiente registro. */
			fread(&empleado, sizeof(empleado), 1, fichero);
		}

		/* Cerramos "empleados.dat". */
		fclose(fichero);
	}
}

Poder saltar con el puntero a una posición concreta del fichero nos permite realizar búsquedas mas precisas en menos tiempo.

Escribir dentro de un fichero en C

En el artículo anterior explicaba cómo abrir, crear y cerrar un fichero en C, pero en este artículo os contaré cómo podemos escribir dentro de un fichero. Podremos hacerlo empleando las diferentes funciones que nos ofrece la librería stdio de C, como fputc, fputs y fwrite.

fputc

La función fputc nos permite escribir en un fichero caracteres, pero lo podremos hacer de uno en uno. La sintaxis es:

fputc(caracter, puntero);

Donde caracter es el carácter que queremos escribir y puntero es la dirección donde está el fichero en el que queremos escribir.

fputs

También podemos escribir cadenas de caracteres en un fichero, para ello utilizamos la función fputs. La sintaxis es:

fputs(cadena, puntero);

Donde cadena es la cadena de caracteres que queremos escribir en el archivo, al igual que antes, es la dirección de nuestro fichero.

fwrite

Esta función es diferente, la función fwrite nos permite trabajar tanto con fichero de texto como binarios y escribir cualquier tipo de datos. Al ser una función mas completa voy a desarrollar un poco mas su explicación con un ejemplo mas detallado. Su sintaxis general es:

fwrite(direccion_dato, tamaño, veces, puntero);

Aquí tenemos varios argumentos, por ejemplo direccion_dato es la dirección de la variable o elemento que queremos escribir en el fichero indicado por la variable puntero. En tamaño debemos indicar el tamaño en bytes de la variable o elemento  escribir en el fichero. Para obtener el tamaño de una variable podemos usar el operador sizeof y para obtener el tamaño de una cadena la función strlen. En veces indicamos cuántos elementos de tamaño tamaño vamos a escribir. ¡Cuidado!, veces no es el número de veces que queremos que se escriba repetidamente el dato indicado en direccion_dato. Veamos unos ejemplos:

#include <stdio.h>

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Declaramos la variable caracter con un caracter ASCII. */
	char caracter = 'A';

	/* Abrimos "fichero1.txt" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("fichero1.txt", "wt");

	if(fichero == NULL) {
		printf("Error: No se ha podido crear el fichero archivo1.txt");
	} else {
		/* Escribimos dentro de "fichero1.txt". */
		fwrite(&caracter, sizeof(caracter), 1, fichero);

		/* Cerramos "fichero1.txt". */
		fclose(fichero);
	}

	fflush(stdin);
	printf("\n\nPulsa Intro para finalizar...");
	getchar();
}

En la línea

fwrite(&caracter, sizeof(caracter), 1, fichero);

hemos escrito en el fichero "fichero1.txt" el contenido de la variable caracter,  que es 'A'. Para ello indicamos en el primer parámetro la dirección de memoria de la variable caracter, cuyo contenido queremos escribir en el fichero. En el segundo parámetro escribimos el tamaño en bytes de la variable caracter. Utilizamos el operador sizeof  para que lo calcule. El tercer parámetro indica cuántos elementos de tamaño sizeof(caracter) hay en la variable caracter, en este caso 1. Este parámetro generalmente será 1, aunque al escribir arrays en los ficheros puede variar. El cuarto parámetro es la variable puntero al fichero abierto.

Si en lugar de un caracter quisiéramos escribir una cadena de caracteres podemos hacerlo de la siguiente manera:

char nombre[30];
fwrite(&nombre, strlen(nombre), 1, fichero);

Y si lo que queremos es almacenar en un archivo binario el contenido de una variable de tipo struct llamada empleado debemos hacerlo de la siguiente manera:

fwrite(&empleado, sizeof(empleado), 1, fichero);

En el siguiente programa se escribe en el fichero "archivo1.txt" una frase que se le pide introducir al usuario:

#include <stdio.h>
#include <string.h>

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Declaramos la variable frase de tipo array. */
	char frase[100];

	/* Abrimos "fichero1.txt" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("fichero1.txt", "wt");

	if(fichero == NULL) {
		printf("Error: No se ha podido crear el fichero archivo1.txt");
	} else {

		/* Se pide al usuario que introduzca una frase. */
		printf("Escriba una frase: ");
		scanf("%s", frase);

		/* Escribimos dentro de "fichero1.txt". */
		fwrite(&frase, strlen(frase), 1, fichero);

		/* Cerramos "fichero1.txt". */
		fclose(fichero);
	}
}

Y por último veamos un ejemplo en el que queremos crear un fichero binario, llamado "empleados.dat", en el que almacenamos tres estructuras con la siguiente definición:

#include <stdio.h>
#include <string.h>

struct datos_empleado {
	char nombre[30];
	int edad;
};

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	struct datos_empleado empleado;

	/* Abrimos "empleados.dat" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("empleados.dat", "wb");

	if (fichero == NULL) {
		printf("Error: No se ha podido crear el fichero empleados.dat");
	} else {
		int n;

		/* Se pide al usuario que introduzca
		 * los datos de 3 empleados. */
		for(n=0;n<=2;n++){
			fflush (stdin);
			printf("\nNombre: ");
			scanf("%s", empleado.nombre);
			printf("Edad: ");
			scanf("%d", &empleado.edad);
			fwrite(&empleado, sizeof(empleado), 1, fichero);
		}

		/* Cerramos "empleados.dat". */
		fclose(fichero);
	}
}

En el próximo artículo explicaré cómo leer el contenido de un archivo en C.

Abrir, crear y cerrar un fichero en C

En este artículo voy a explicar cómo se abre un fichero desde un programa en C. En artículos posteriores explicaré como leer el contenido de un fichero y cómo escribirlo.

Así pues, te hago un pregunta: ¿Sabes lo que es un un fichero? Seguramente dirás que si, pero ¿y si te piden que des una explicación de lo que es? Ahí... parece que se complica la cosa. En los siguientes párrafos voy a intentar explicar cómo funciona esto de los ficheros en un sistema. Un fichero es un conjunto de datos que se almacenan en un soporte como pudiera ser un disco duro o un soporte externo como un USB, CD-ROM, etc. A estos ficheros se le ponen nombres para que los podamos identificar, por ejemplo, telefonos.txt o empleados.dat. Y ¿qué podemos almacenar en un fichero? Podemos almacenar datos de muchos tipos primitivos como int, char, float, pero también cadenas de caracteres o estructuras. Al final, a mas bajo nivel lo que estamos almacenando de un modo u otro son ceros y unos (011010100110...)

Para explicar todo el proceso con ejemplos vamos a utilizar el lenguaje de programación C, y algunas de sus funciones para el caso. Todas las funciones que necesitaremos para poder abrir, crear, acceder a su contenido, modificar y cerrar, pertenecen a la biblioteca stdio de C, así que podremos usarlas indicando la siguiente directiva al inicio de nuestros programas.

#include <stdio.h>

Comencemos pues. Imagina lo que le puede costar a una computadora encontrar un fichero en concreto de entre centenares de miles de ficheros que hay almacenados. Quizás podríamos pensar que solo tiene que recorrer uno a uno hasta dar con el que quiere, pero claro, este puede estar en cualquier lugar, e incluso el último de una lista enorme de ficheros. Para ello, las computadoras tienen una tabla donde existe una relación entre el nombre de un fichero y la dirección o lugar en la que se encuentra dentro del disco duro. Así pues, cuando abramos un fichero desde un programa C, en realidad lo que hacemos es obtener esa dirección y utilizarla para realizar diferentes operaciones. Aquí podemos ver un ejemplo:

Nombre del fichero Dirección del puntero
archivo1.txt 0x7fff7ad1f070
archivo2.txt 0x7fff7ad1f108
archivo3.txt 0x7fff7ad1f1a0
\vdots \vdots

fopen

Ahora que ya sabemos lo que es un fichero y cómo se almacena podemos ver qué es lo que hay que hacer para crear un fichero nuevo o abrir uno existente. Para ello utilizaremos la función fopen, que nos devolverá la dirección del fichero, un puntero a FILE. La sintaxis general es:

fopen(nombre, modo);

Donde nombre puede ser una cadena con el nombre del fichero o la ruta absoluta (por ejemplo: /var/tmp/archivo1.txt). Y el parámetro modo es una cadena que contiene una serie de caracteres que indican cómo queremos que se abra o se cree el fichero. En la siguiente tabla se indican las distintas posibilidades y su significado:

Modo Descripción
r (read) Si el fichero existe se abre en modo solo lectura y, por defecto, en modo texto.Si el fichero no existe la función devuelve NULL. Se utiliza para leer los datos de un fichero en modo texto.
w (write) Si el fichero no existe lo crea para escribir en él y lo deja abierto, por defecto, en modo texto, y si ya existe lo sobrescribe. Todas las operaciones de escritura con el modo 'w' se realizan al final del fichero.
a (add) Si el fichero no existe lo crea para escribir en él y lo deja abierto, por defecto, en modo texto, y si ya existe permite añadir mas datos al final de este respetando sus datos anteriores.
r+ Este modo es igual que el modo 'r' pero  también permite escribir en cualquier punto del fichero. Se utiliza para leer y modificar los datos de un fichero en modo texto.
w+ Igual que 'w' pero también permite leer del fichero. Se utiliza para crear un fichero y poder realizar operaciones de lectura.
a+ Igual que 'a' pero pero también permite leer del fichero. Se utiliza para añadir datos a un fichero en modo texto y poder realizar operaciones de lectura.
rt Igual que 'r'. la 't' es de texto.
wt Igual que 'w'. la 't' es de texto.
at Igual que 'a'. la 't' es de texto.
rt+ Igual que 'r+'.
wt+ Igual que 'w+'.
at+ Igual que 'a+'.
rb Igual que 'r' pero en modo binario, en vez de en modo texto. La 'b' es de binario.
wb Igual que 'w' pero en modo binario, en vez de en modo texto. La 'b' es de binario.
ab Igual que 'a' pero en modo binario, en vez de en modo texto. La 'b' es de binario.
rb+ Igual que 'r+' pero en modo binario, en vez de en modo texto. La 'b' es de binario.
wb+ Igual que 'w+' pero en modo binario, en vez de en modo texto. La 'b' es de binario.
ab+ Igual que 'a+' pero en modo binario, en vez de en modo texto. La 'b' es de binario.

Por ejemplo, si queremos abrir un archivo archivo1.txt y ver su contenido, debemos empezar por escribir lo siguiente:

fopen("archivo1.txt", "rt");

Antes he explicado que esto nos devuelve la dirección del fichero en un formato hexadecimal (0x7fff7ad1f070), así pues, dicha dirección la almacenaremos en una variable puntero a FILE, por ejemplo la llamaremos fichero.

#include <stdio.h>

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Abrimos fichero1.txt en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("fichero1.txt", "rt");

	/* Imprimimos la direccion para este ejemplo. */
	printf("%p\n",fichero);
}

Con este pequeño fragmento de código, cualquier operación que hubiésemos realizado con la variable fichero la estaríamos realizando con el archivo fichero1.txt.

Otro ejemplo, si queremos crear un nuevo fichero binario llamado empleados.dat escribimos el siguiente código:

#include <stdio.h>

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Creamos empleados.dat en modo binario y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("empleados.dat", "wb");

	/* Imprimimos la direccion para este ejemplo. */
	printf("%p\n",fichero);
}

fclose

Hasta ahora solo hemos abierto ficheros nuevos o existentes, y no hemos hecho ninguna operación con ellos. Es muy importante cerrar un fichero una vez hemos terminado de trabajar con él, para ello existe la función fclose, que tiene la siguiente sintaxis:

fclose(puntero);

Donde puntero es el identificador de la variable puntero a FILE que almacena la dirección del fichero abierto. Vamos a ver el ejemplo anterior cerrando el fichero:

#include <stdio.h>

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Abrimos "fichero1.txt" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("fichero1.txt", "rt");

	/* Imprimimos la direccion para este ejemplo. */
	printf("%p\n",fichero);

	/* Cerramos "fichero1.txt". */
	fclose(fichero);
}

Si el archivo fichero1.txt no existiera, la función fopen devolverá un valor NULL, y no podremos cerrarlo, puesto que no existe. Así que vamos a añadir un control  que detecte si el fichero existe o no, de modo que solo lo cerraremos si el valor del puntero es distinto de NULL.

#include <stdio.h>

int main(void) {

	/* Declaramos la variable fichero como puntero a FILE. */
	FILE *fichero;

	/* Abrimos "fichero1.txt" en modo texto y
	 * guardamos su direccion en el puntero. */
	fichero = fopen("fichero1.txt", "rt");

	if(fichero == NULL) {
		/* Imprimimos un mensaje para indicar que no existe. */
		printf("El fichero no se ha podido abrir, no existe.");
	} else {
		/* Imprimimos mesaje de exito y la direccion para este ejemplo. */
		printf("El fichero existe y esta en la direccion: %p\n",fichero);

		/* Cerramos "fichero1.txt". */
		fclose(fichero);
	}
}

En el próximo artículo explicaré cómo escribir dentro de un archivo en C.

Un millón de dígitos de Pi

En la siguiente imagen que he generado se pueden apreciar el primer millón de dígitos de Pi. Se trata de una foto MUY grande (22.8MB) a la que podéis acceder y descargar:

http://ingenieriainversa.org/1millon_de_digitos_de_Pi.png

Para obtener este primer millón de dígitos de Pi he utilizado el programa C que emplea el Algoritmo de Chudnovsky del que ya hablé en este otro post.

Integridad de archivos en sistemas GNU/Linux y Unix

En este artículo voy a explicar cómo podemos saber si un fichero ha sido alterado, o comprobar si se trata del mismo archivo que esperamos que sea. Casi todos los sistemas operativos GNU ya vienen de serie con software para manejar algoritmos de reducción criptográfica como MD5. Podremos debatir sobre si se trata del mejor algoritmo o no, pero no es el objetivo de este post. Así pues, aprovechemos la ocasión ya que probablemente no tengamos que instalar nada en nuestra máquina.

Para este ejemplo, lo primero que haremos es crear un archivo de texto plano con una frase cualquiera.

cat archivo.txt
Este archivo es de prueba.

Ahora ejecutamos el comando md5sum al que le pasaremos como argumento el nombre del archivo. Esto nos devolverá un valor alfanumérico único.

md5sum archivo.txt
ae2e5a6e2d2067069248e928cc8dddb1  archivo.txt

Si estuviéramos en un sistema Unix como Solaris tendríamos que ejecutar el comando digest con la opcion -a para indicar el tipo de algoritmo, en este caso MD5:

digest -a md5 archivo.txt
ae2e5a6e2d2067069248e928cc8dddb1

Como podemos observar, la cadena resultante es la misma en un sistema GNU/Linux que en Solaris. Bien, ahora vamos a modificar el contenido del archivo y veamos que pasa. Vamos a cambiar el punto del final por una exclamación.

cat archivo.txt
Este archivo es de prueba!

Repetimos el proceso anterior para ver su cadena md5 y... voilà.

Linux

md5sum archivo.txt
398d5ec7a57375dce2657ff0fd8fd53e  archivo.txt

Solaris

digest -a md5 archivo.txt
398d5ec7a57375dce2657ff0fd8fd53e

La cadena alfanumérica MD5 ha cambiado.

- "¿Y para que me sirve todo esto?"

Supongamos que tienes un archivo que quieres compartir y lo subes a internet para que la gente lo descargue. Esta cadena alfanumérica sería algo así como el DNI de ese archivo, la matrícula, el certificado que verifica que el archivo original es este y solamente este. Cualquier modificación en el interior del archivo, sea cual sea, un punto, una coma, un espacio, hará que cambie esta cadena MD5.

Aunque con esto ya sería suficiente para comprobar que un archivo ha sido manipulado de algún modo, es posible pasar esta cadena resultante por otro algoritmo de cifrado como base64, por ejemplo con openssl. Esta sería el método en cualquier sistema operativo GNU/Linux y Unix:

Linux

md5sum archivo.txt | awk '{print $1}' | openssl enc -base64
Mzk4ZDVlYzdhNTczNzVkY2UyNjU3ZmYwZmQ4ZmQ1M2UK

Solaris

digest -a md5 archivo.txt | openssl enc -base64
Mzk4ZDVlYzdhNTczNzVkY2UyNjU3ZmYwZmQ4ZmQ1M2UK

Notese que en el caso de GNU/Linux he tenido que meter un awk '{print $1}' para quedarme solo con la parte de la cadena MD5, cosa que en Solaris no es necesario.

Encriptar un archivo con clave GPG

A continuación explico de forma muy breve cómo se encripta un archivo cualquiera con una clave GPG. *Debes tener instalado en tu sistema GnuPG.

Para este ejemplo primero crearemos un archivo prueba.txt que contendrá la cadena de texto "Hola".

echo "Hola" > prueba.txt
cat prueba.txt 
Hola

Ahora ejecutamos el siguiente comando sobre el archivo prueba.txt.

gpg --passphrase abcd1234 -o prueba.gpg -c prueba.txt

Al argumento --passphrase se le puede pasar la cadena de texto que queramos. Eso si, debemos recordarla para luego desencriptar nuestro archivo. El argumento -o es para indicar el archivo de salida ya encriptado, y el argumento -c es para indicar que se va a realizar un cifrado simétrico (por defecto AES128). Si se quisiera cambiar el tipo de cifrado se puede sustituir la opción -c por --cipher-algo y a continuación especificar el tipo de cifrado, por ejemplo:

gpg --passphrase abcd1234 -o prueba.gpg --cipher-algo AES256 prueba.txt

Una vez hecho esto, se puede listar el contenido del directorio actual para ver lo que se ha generado.

ls -lrt
-rw-rw-r-- 1 xe26505 xe26505    5 nov 03 19:43 prueba.txt
-rw-rw-r-- 1 xe26505 xe26505   54 nov 03 19:43 prueba.gpg

Si queremos ver qué contiene el archivo prueba.gpg generado veremos que está encriptado:

cat prueba.gpg 
??K0pF`?%<??Z?8??>??Tgh???_u???O?
????8a?

Ahora ya podemos guardar para nosotros mismos o hacer llegar el archivo a una persona que conozca la passphrase para descifrarlo, de un modo seguro y fiable.

Para desencriptar el archivo bastaría con ejecutar el siguiente comando:

gpg --passphrase abcd1234 -d prueba.gpg 
gpg: CAST5 encrypted data
gpg: encrypted with 1 passphrase
Hola

Donde al argumento --passphrase se le ha de pasar la misma cadena que se utilizó para encriptar, y el argumento -d es el archivo encriptado.

Si lo que se quiere encriptar es un conjunto de archivos y directorios bastaría con empaquetarlos y/o comprimirlos en un archivo, por ejemplo .tar, .zip o .gz y repetir el proceso de este mini tutorial.

- "¿Y para qué necesito yo hacer todo esto?"

Si necesitas guardar algo de gran valor, por ejemplo una semilla de un monedero Bitcoin, un archivo de passwords, o necesitas enviar por mail datos con información sensible (informe médico, cuenta del banco, passwords, etc.) es altamente recomendable hacerlo de forma que nadie mas que tú y quien tú quieras lo pueda leer. Aquí puedes leer cómo usar GnuPG para tu correo electrónico.