En esta ocasión vamos a ver un ejercicio en ANSI C que trata sobre código Morse y persistencia de datos en un archivo. Se trata de algunas opciones que son:
- Cargar las traducciones en memoria usando una pila, leyendo el archivo de texto
- Leer una cadena y mostrar su traducción en Morse
- Leer un archivo de texto y escribir su contenido en otro archivo, pero traducido a Morse
- Leer un archivo codificado y escribir su contenido en un archivo, pero traducido a texto plano
Tiene algunos requisitos que son:
- Cada código Morse va separado por 3 espacios
- Los espacios entre palabras se codifican como 5 espacios
Veremos paso por paso, lo que más vamos a practicar será el parseo de cadenas así como el trabajo con cadenas, que son un verdadero dolor de cabeza en C pero que al aprender a usarlas son relativamente sencillas.
La pila
Uno de los puntos importantes es leer el archivo de traducciones y colocarlo en memoria. Veamos primero el archivo de traducciones:
A .-*
B -...*
C -.-.*
D -..*
E .*
F ..-.*
G --.*
H ....*
I ..*
J .---*
K -.-*
L .-..*
M --*
N -.*
O ---*
P .--.*
Q --.-*
R .-.*
S ...*
T -*
U ..-*
V ...-*
W .--*
X -..-*
Y -.--*
Z --..*
0 -----*
1 .----*
2 ..---*
3 ...--*
4 ....-*
5 .....*
6 -....*
7 --...*
8 ---..*
9 ----.*
Para cargarlo a memoria he utilizado una pila de structs. Cada struct
guarda la traducción y el original:
// Nodo que tiene el carácter y el código morse
struct nodo {
char caracter[MAXIMA_LONGITUD_CARACTER];
char morse[MAXIMA_LONGITUD_EQUIVALENTE_MORSE];
struct nodo *siguiente;
};
// La parte superior de la pila; es decir, por donde comenzamos
struct nodo *superior = NULL;
De este modo podemos recorrer toda la pila y buscar ya sea el texto plano o el texto en Morse.
Cargar traducción
Tengo una función que le añade un struct a la pila y que se usará al cargar la traducción. Es la siguiente:
// Agrega una nueva traducción
void agregar(char *caracter, char *morse) {
// El que se agregará; reservamos memoria
struct nodo *nuevoNodo = malloc(sizeof(struct nodo));
// Le ponemos el dato
strcpy(nuevoNodo->morse, morse);
strcpy(nuevoNodo->caracter, caracter);
nuevoNodo->siguiente = superior;
superior = nuevoNodo;
}
Por favor notar el uso de strcpy
, esta función es muy importante para asignar la cadena al struct
. Sin ella, no se podría continuar.
Ahora que tenemos la función es momento de cargar el archivo de traducciones:
void cargarDiccionarioEnMemoria() {
FILE *archivoTraducciones;
int tamanioBuffer = 255;
char linea[tamanioBuffer];
char delimitador[] = " ";
archivoTraducciones = fopen(nombreArchivoDiccionario, "r");
if (archivoTraducciones == NULL) {
printf("Imposible leer el archivo %s", nombreArchivoDiccionario);
exit(EXIT_FAILURE);
}
while (fgets(linea, tamanioBuffer, archivoTraducciones)) {
size_t longitudLinea = strlen(linea);
// Checar si la línea no es un salto de línea vacío
if (longitudLinea > 1) {
// De la línea, removemos el asterisco * y el salto de línea
strtok(linea, "\n*");
// Al hacer un split con el espacio, lo primero es el carácter
char *simbolo = strtok(linea, delimitador);
// Y lo segundo es el equivalente
char *equivalente = strtok(NULL, delimitador);
agregar(simbolo, equivalente);
}
}
fclose(archivoTraducciones);
}
Esto fue un poco complejo porque tenía que quitar el salto de línea y el asterisco de la línea. Además, tenía que separar por espacios para obtener el texto plano y la traducción, pero al final funcionó.
Buscar texto plano o Morse en traducción
Debo definir dos funciones. Una de ellas recibirá el carácter en texto plano y devolverá su traducción en Morse. La otra recibirá el código Morse y devolverá el texto plano.
Ambas funciones recorren la pila buscando y comparando cadenas con strcmp:
void buscarMorseDeCaracter(char *cadena, char destino[MAXIMA_LONGITUD_EQUIVALENTE_MORSE]) {
// Salida rápida. Si es espacio, devolvemos vacío
if (strcmp(cadena, " ") == 0) {
strcpy(destino, "");
return;
}
struct nodo *temporal = superior;
while (temporal != NULL) {
if (strcmp(cadena, temporal->caracter) == 0) {
strcpy(destino, temporal->morse);
return;
}
temporal = temporal->siguiente;
}
}
void buscarCaracterDeMorse(char *morse, char destino[MAXIMA_LONGITUD_EQUIVALENTE_MORSE]) {
struct nodo *temporal = superior;
if (strcmp(morse, ESPACIO_PERSONALIZADO) == 0) {
strcpy(destino, " ");
return;
}
while (temporal != NULL) {
if (strcmp(morse, temporal->morse) == 0) {
strcpy(destino, temporal->caracter);
return;
}
temporal = temporal->siguiente;
}
}
Gracias a estos métodos vamos a poder buscar la traducción de cualquier texto plano o Morse.
Codificar una palabra
Este es el requisito más sencillo. Se trata de leer una palabra introducida por el usuario (de una longitud definida) y traducirla a Morse.
void codificarPalabra(const char palabra[MAXIMA_LONGITUD_PALABRA]) {
int i = 0;
char equivalencia[MAXIMA_LONGITUD_EQUIVALENTE_MORSE];
char cadenaTemporal[2] = "\0";
while (palabra[i] != 0) {
cadenaTemporal[0] = palabra[i];
char separador[20] = "";
if (strcmp(cadenaTemporal, " ") == 0) {
strcpy(separador, " ");
} else {
strcpy(separador, " ");
}
buscarMorseDeCaracter(cadenaTemporal, equivalencia);
printf("%s%s", equivalencia, separador);
i++;
}
}
Si la probamos todo va bien:

Una cosa muy importante es respetar los espacios entre símbolos.
Codificar archivo
Ahora veamos cómo codificar un archivo y escribir el código Morse en otro archivo. Para ello definimos el archivo original:
ME LLAMO LUIS
SOY UN PROGRAMADOR
5 Y 5 SON DIEZ
HOLA MUNDO
PROGRAMANDO EN C
Se debe leer línea por línea y escribir el contenido en otro archivo, pero en código Morse. La función que lo realiza es la siguiente:
void codificarArchivo() {
FILE *archivoOriginal, *archivoCodificado;
int tamanioBuffer = 255;
char linea[tamanioBuffer];
archivoOriginal = fopen(nombreArchivoOriginal, "r");
if (archivoOriginal == NULL) {
printf("El archivo %s no existe", nombreArchivoOriginal);
return;
}
archivoCodificado = fopen(nombreArchivoCodificado, "w");
if (archivoCodificado == NULL) {
printf("Imposible abrir el archivo %s", nombreArchivoCodificado);
return;
}
while (fgets(linea, tamanioBuffer, archivoOriginal)) {
// De la línea, removemos el salto de línea
strtok(linea, "\n");
// La recorremos
int i = 0;
char equivalencia[MAXIMA_LONGITUD_EQUIVALENTE_MORSE];
char cadenaTemporal[2] = "\0";
while (linea[i] != 0) {
cadenaTemporal[0] = linea[i];
char separador[20] = "";
if (strcmp(cadenaTemporal, " ") == 0) {
strcpy(separador, " ");
} else {
strcpy(separador, " ");
}
buscarMorseDeCaracter(cadenaTemporal, equivalencia);
// Tenemos la equivalencia, la escribimos
fprintf(archivoCodificado, "%s%s", equivalencia, separador);
i++;
}
fprintf(archivoCodificado, "\n");
}
fclose(archivoOriginal);
fclose(archivoCodificado);
printf("Archivo codificado\n");
}
Mira cómo es que estamos usando las funciones anteriormente mencionadas. Además, estamos tomando en cuenta la cantidad de espacios. Al seleccionar la opción se habrá creado un archivo codificado cuyo contenido es:
-- . .-.. .-.. .- -- --- .-.. ..- .. ...
... --- -.-- ..- -. .--. .-. --- --. .-. .- -- .- -.. --- .-.
..... -.-- ..... ... --- -. -.. .. . --..
.... --- .-.. .- -- ..- -. -.. ---
.--. .-. --- --. .-. .- -- .- -. -.. --- . -. -.-.
Presta atención al archivo porque después lo vamos a decodificar.
Decodificar archivo
Veamos el proceso inverso, ahora vamos a leer el código Morse de un archivo y lo vamos a traducir a texto plano de manera que quede en otro archivo de texto.
La función que lo hace es la siguiente:
void decodificarArchivo() {
FILE *archivoCodificado, *archivoDecodificado;
int tamanioBuffer = 900;
char linea[tamanioBuffer];
archivoCodificado = fopen(nombreArchivoCodificado, "r");
if (archivoCodificado == NULL) {
printf("Imposible abrir el archivo %s", nombreArchivoCodificado);
return;
}
archivoDecodificado = fopen(nombreArchivoDecodificado, "w");
if (archivoDecodificado == NULL) {
printf("Imposible abrir el archivo %s", nombreArchivoCodificado);
return;
}
while (fgets(linea, tamanioBuffer, archivoCodificado)) {
// Remover salto de línea
strtok(linea, "\n");
int indice = 0;
int cantidadDeEspacios = 0;
char palabra[5000] = "";
while (linea[indice] != 0) {
char actual = linea[indice];
if (actual == '-' || actual == '.') {
// Cadena para convertir char a string
char temporal[2] = "\0";
temporal[0] = actual;
if (cantidadDeEspacios == 0) {
strcat(palabra, temporal);
} else if (cantidadDeEspacios == 3) {
strcat(palabra, " ");
strcat(palabra, temporal);
cantidadDeEspacios = 0;
} else if (cantidadDeEspacios == 5) {
strcat(palabra, " ");
strcat(palabra, ESPACIO_PERSONALIZADO);
strcat(palabra, " ");
strcat(palabra, temporal);
cantidadDeEspacios = 0;
}
} else {
cantidadDeEspacios++;
}
indice++;
}
// Llegados a este punto ya tenemos una línea codificada, pero sin espacios que estorban
char *token = strtok(palabra, " ");
if (token != NULL) {
while (token != NULL) {
char equivalencia[MAXIMA_LONGITUD_EQUIVALENTE_MORSE];
buscarCaracterDeMorse(token, equivalencia);
fprintf(archivoDecodificado, "%s", equivalencia);
token = strtok(NULL, " ");
}
}
fprintf(archivoDecodificado, "\n");
}
printf("\n");
fclose(archivoCodificado);
fclose(archivoDecodificado);
printf("Archivo descodificado\n");
}
Y el contenido debe ser el mismo que el del archivo original. Todo funciona de maravilla.
Menú de opciones
El ejercicio requería que se dieran las opciones mostradas aquí en un menú. Ese menú debería mostrarse de manera infinita. El código del menú en C es:
while (1) {
printf("\n1 - Codificar palabra\n");
printf("2 - Codificar archivo\n");
printf("3 - Descodificar archivo\n");
printf("0 - Salir\n");
printf("Elige:\n");
char escanearInt[20];
fgets(escanearInt, 20, stdin);
opcion = strtol(escanearInt, NULL, 0);
if (opcion == 0) {
break;
} else if (opcion == 1) {
printf("\nEscribe la palabra. Max 15 caracteres:\n");
fgets(palabra, 5000, stdin);
strtok(palabra, "\n");// Remover salto de línea
if (strlen(palabra) > MAXIMA_LONGITUD_PALABRA_SIN_NULL) {
printf("Longitud fuera de rango.\n");
continue;
}
printf("Resultado:\n");
codificarPalabra(palabra);
} else if (opcion == 2) {
codificarArchivo();
} else if (opcion == 3) {
decodificarArchivo();
}
}
Hacemos un ciclo infinito que se detendrá solo si se selecciona una opción determinada.
Poniendo todo junto
El código completo de este ejercicio queda como se ve a continuación:
/*
Programado por Parzibyte
____ _____ _ _ _
| _ \ | __ \ (_) | | |
| |_) |_ _ | |__) |_ _ _ __ _____| |__ _ _| |_ ___
| _ <| | | | | ___/ _` | '__|_ / | '_ \| | | | __/ _ \
| |_) | |_| | | | | (_| | | / /| | |_) | |_| | || __/
|____/ \__, | |_| \__,_|_| /___|_|_.__/ \__, |\__\___|
__/ | __/ |
|___/ |___/
Blog: https://parzibyte.me/blog/
Ayuda: https://parzibyte.me/#contacto
Contacto: https://parzibyte.me/#contacto
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#define MAXIMA_LONGITUD_PALABRA_SIN_NULL 15
#define MAXIMA_LONGITUD_PALABRA MAXIMA_LONGITUD_PALABRA_SIN_NULL + 1
#define MAXIMA_LONGITUD_CARACTER 2 //
#define MAXIMA_LONGITUD_EQUIVALENTE_MORSE 20
#define ESPACIO_PERSONALIZADO "#"
char *nombreArchivoOriginal = "original.txt";
char *nombreArchivoCodificado = "codificado.txt";
char *nombreArchivoDecodificado = "decodificado.txt";
char *nombreArchivoDiccionario = "morse.txt";
// Nodo que tiene el carácter y el código morse
struct nodo {
char caracter[MAXIMA_LONGITUD_CARACTER];
char morse[MAXIMA_LONGITUD_EQUIVALENTE_MORSE];
struct nodo *siguiente;
};
// La parte superior de la pila; es decir, por donde comenzamos
struct nodo *superior = NULL;
// Agrega una nueva traducción
void agregar(char *caracter, char *morse) {
// El que se agregará; reservamos memoria
struct nodo *nuevoNodo = malloc(sizeof(struct nodo));
// Le ponemos el dato
strcpy(nuevoNodo->morse, morse);
strcpy(nuevoNodo->caracter, caracter);
nuevoNodo->siguiente = superior;
superior = nuevoNodo;
}
void cargarDiccionarioEnMemoria() {
FILE *archivoTraducciones;
int tamanioBuffer = 255;
char linea[tamanioBuffer];
char delimitador[] = " ";
archivoTraducciones = fopen(nombreArchivoDiccionario, "r");
if (archivoTraducciones == NULL) {
printf("Imposible leer el archivo %s", nombreArchivoDiccionario);
exit(EXIT_FAILURE);
}
while (fgets(linea, tamanioBuffer, archivoTraducciones)) {
size_t longitudLinea = strlen(linea);
// Checar si la línea no es un salto de línea vacío
if (longitudLinea > 1) {
// De la línea, removemos el asterisco * y el salto de línea
strtok(linea, "\n*");
// Al hacer un split con el espacio, lo primero es el carácter
char *simbolo = strtok(linea, delimitador);
// Y lo segundo es el equivalente
char *equivalente = strtok(NULL, delimitador);
agregar(simbolo, equivalente);
}
}
fclose(archivoTraducciones);
}
void buscarMorseDeCaracter(char *cadena, char destino[MAXIMA_LONGITUD_EQUIVALENTE_MORSE]) {
// Salida rápida. Si es espacio, devolvemos vacío
if (strcmp(cadena, " ") == 0) {
strcpy(destino, "");
return;
}
struct nodo *temporal = superior;
while (temporal != NULL) {
if (strcmp(cadena, temporal->caracter) == 0) {
strcpy(destino, temporal->morse);
return;
}
temporal = temporal->siguiente;
}
}
void buscarCaracterDeMorse(char *morse, char destino[MAXIMA_LONGITUD_EQUIVALENTE_MORSE]) {
struct nodo *temporal = superior;
if (strcmp(morse, ESPACIO_PERSONALIZADO) == 0) {
strcpy(destino, " ");
return;
}
while (temporal != NULL) {
if (strcmp(morse, temporal->morse) == 0) {
strcpy(destino, temporal->caracter);
return;
}
temporal = temporal->siguiente;
}
}
void codificarPalabra(const char palabra[MAXIMA_LONGITUD_PALABRA]) {
int i = 0;
char equivalencia[MAXIMA_LONGITUD_EQUIVALENTE_MORSE];
char cadenaTemporal[2] = "\0";
while (palabra[i] != 0) {
cadenaTemporal[0] = palabra[i];
char separador[20] = "";
if (strcmp(cadenaTemporal, " ") == 0) {
strcpy(separador, " ");
} else {
strcpy(separador, " ");
}
buscarMorseDeCaracter(cadenaTemporal, equivalencia);
printf("%s%s", equivalencia, separador);
i++;
}
}
void codificarArchivo() {
FILE *archivoOriginal, *archivoCodificado;
int tamanioBuffer = 255;
char linea[tamanioBuffer];
archivoOriginal = fopen(nombreArchivoOriginal, "r");
if (archivoOriginal == NULL) {
printf("El archivo %s no existe", nombreArchivoOriginal);
return;
}
archivoCodificado = fopen(nombreArchivoCodificado, "w");
if (archivoCodificado == NULL) {
printf("Imposible abrir el archivo %s", nombreArchivoCodificado);
return;
}
while (fgets(linea, tamanioBuffer, archivoOriginal)) {
// De la línea, removemos el salto de línea
strtok(linea, "\n");
// La recorremos
int i = 0;
char equivalencia[MAXIMA_LONGITUD_EQUIVALENTE_MORSE];
char cadenaTemporal[2] = "\0";
while (linea[i] != 0) {
cadenaTemporal[0] = linea[i];
char separador[20] = "";
if (strcmp(cadenaTemporal, " ") == 0) {
strcpy(separador, " ");
} else {
strcpy(separador, " ");
}
buscarMorseDeCaracter(cadenaTemporal, equivalencia);
// Tenemos la equivalencia, la escribimos
fprintf(archivoCodificado, "%s%s", equivalencia, separador);
i++;
}
fprintf(archivoCodificado, "\n");
}
fclose(archivoOriginal);
fclose(archivoCodificado);
printf("Archivo codificado\n");
}
void decodificarArchivo() {
FILE *archivoCodificado, *archivoDecodificado;
int tamanioBuffer = 900;
char linea[tamanioBuffer];
archivoCodificado = fopen(nombreArchivoCodificado, "r");
if (archivoCodificado == NULL) {
printf("Imposible abrir el archivo %s", nombreArchivoCodificado);
return;
}
archivoDecodificado = fopen(nombreArchivoDecodificado, "w");
if (archivoDecodificado == NULL) {
printf("Imposible abrir el archivo %s", nombreArchivoCodificado);
return;
}
while (fgets(linea, tamanioBuffer, archivoCodificado)) {
// Remover salto de línea
strtok(linea, "\n");
int indice = 0;
int cantidadDeEspacios = 0;
char palabra[5000] = "";
while (linea[indice] != 0) {
char actual = linea[indice];
if (actual == '-' || actual == '.') {
// Cadena para convertir char a string
char temporal[2] = "\0";
temporal[0] = actual;
if (cantidadDeEspacios == 0) {
strcat(palabra, temporal);
} else if (cantidadDeEspacios == 3) {
strcat(palabra, " ");
strcat(palabra, temporal);
cantidadDeEspacios = 0;
} else if (cantidadDeEspacios == 5) {
strcat(palabra, " ");
strcat(palabra, ESPACIO_PERSONALIZADO);
strcat(palabra, " ");
strcat(palabra, temporal);
cantidadDeEspacios = 0;
}
} else {
cantidadDeEspacios++;
}
indice++;
}
// Llegados a este punto ya tenemos una línea codificada, pero sin espacios que estorban
char *token = strtok(palabra, " ");
if (token != NULL) {
while (token != NULL) {
char equivalencia[MAXIMA_LONGITUD_EQUIVALENTE_MORSE];
buscarCaracterDeMorse(token, equivalencia);
fprintf(archivoDecodificado, "%s", equivalencia);
token = strtok(NULL, " ");
}
}
fprintf(archivoDecodificado, "\n");
}
printf("\n");
fclose(archivoCodificado);
fclose(archivoDecodificado);
printf("Archivo descodificado\n");
}
int main(void) {
cargarDiccionarioEnMemoria();
int opcion = -1;
char palabra[5000] = "";
while (1) {
printf("\n1 - Codificar palabra\n");
printf("2 - Codificar archivo\n");
printf("3 - Descodificar archivo\n");
printf("0 - Salir\n");
printf("Elige:\n");
char escanearInt[20];
fgets(escanearInt, 20, stdin);
opcion = strtol(escanearInt, NULL, 0);
if (opcion == 0) {
break;
} else if (opcion == 1) {
printf("\nEscribe la palabra. Max 15 caracteres:\n");
fgets(palabra, 5000, stdin);
strtok(palabra, "\n");// Remover salto de línea
if (strlen(palabra) > MAXIMA_LONGITUD_PALABRA_SIN_NULL) {
printf("Longitud fuera de rango.\n");
continue;
}
printf("Resultado:\n");
codificarPalabra(palabra);
} else if (opcion == 2) {
codificarArchivo();
} else if (opcion == 3) {
decodificarArchivo();
}
}
return EXIT_SUCCESS;
}
Al ejecutarlo aparece lo siguiente:

Si quieres ejecutarlo instala gcc y descarga (además del código fuente) el archivo morse.txt así como original.txt presentados anteriormente.
Un poco de historia
Este ejercicio me llevó casi un día. El requisito era terminarlo antes de determinada hora en ese mismo día, y por la presión del tiempo me confundí en algunos aspectos.
Además, fue muy complejo trabajar con las cadenas; me ayudaron bastante las funciones strtok
así como strcpy
y strcat
.
Sin embargo lo más complejo fue parsear los espacios, pues strtok
no permite hacer un split de 5 espacios o 3 espacios ya que toma los 5 o 3 como uno solo.
En fin, es uno de los ejercicios que más me ha costado y quería comentarlo. Si el post te gustó probablemente quieras aprender más sobre C o ver otro ejercicio de Morse en ANSI C.