En este post te mostraré cómo resolver un ejercicio en C del teclado descompuesto, se trata del trabajo con cadenas, listas dinámicas y escritura de archivos; es un ejercicio muy interesante.
Supongamos que estamos escribiendo un texto pero que el teclado está descompuesto y por lo tanto, aleatoriamente, se presiona la tecla inicio (la que mueve el cursor al inicio) y fin (la que mueve el cursor al final) mientras escribimos. Nosotros no nos fijamos de ello hasta que miramos el texto.
Afortunadamente, tenemos en el texto el carácter [
que indica que se presionó la tecla Inicio, y el carácter ]
que indica que se presionó la tecla Fin.
Dado un archivo de entrada con texto, y con los caracteres indicando el inicio y el fin, escribir el texto que debió ser escrito, es decir, corregirlo. Por ejemplo:
Texto de entrada es Hola[Mundo
entonces texto de salida es MundoHola
.
Si no te quedó claro con el ejemplo de arriba te muestro otras pruebas de cómo debería ser la salida y la entrada
Entrada,Salida
Hola[Mundo]este[es[un]texto,unesMundoHolaestetexto
Cabrera[Luis,Luis Cabrera
byte[Parzi,Parzibyte
Lo primero que tenemos que hacer es cargar el archivo de entrada en memoria, para ello usamos una lista. En mi caso utilicé simplemente una pila, pero en lugar de agregar los elementos al inicio, agregué los elementos al final.
La función que carga el archivo es la siguiente:
void cargarArchivoEnMemoria() {
FILE *archivoOriginal;
int tamanioBuffer = MAXIMA_LONGITUD_LINEA;
char linea[tamanioBuffer];
archivoOriginal = fopen(nombreArchivoOriginal, "r");
if (archivoOriginal == NULL) {
printf("Imposible leer el archivo %s", nombreArchivoOriginal);
exit(EXIT_FAILURE);
}
while (fgets(linea, tamanioBuffer, archivoOriginal)) {
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 salto de línea
strtok(linea, "\n");
agregarLineaALista(linea);
}
}
fclose(archivoOriginal);
}
Nota: si no entiendes de dónde vienen algunas variables o constantes, al final dejaré el archivo del código completo, no te preocupes.
Como ves, por cada línea del archivo se remueve el salto de línea con strtok. Además, se invoca a la función agregarLineaALista
así:
void agregarLineaALista(char *linea) {
// El que se agregará; reservamos memoria
struct nodo *nuevoNodo = malloc(sizeof(struct nodo));
// Le ponemos el dato
strcpy(nuevoNodo->linea, linea);
// Si es el primer elemento que se agrega...
if (superior == NULL) {
superior = nuevoNodo;
return;
}
// Si no, buscamos el último elemento y le asignamos el valor al mismo
struct nodo *temporal = superior;
while (temporal->siguiente != NULL) {
temporal = temporal->siguiente;
}
temporal->siguiente = nuevoNodo;
}
Lo que hace esa función es crear un nuevo nodo de la lista, colocarle la línea recién leída y después poner ese nodo al final.
La función que realmente hace el trabajo es la que veremos a continuación. Lo que hace es recorrer la cadena e insertar el texto ya arreglado de acuerdo a lo que vaya encontrando; es decir, coloca el cursor antes o después.
void arreglarTexto(char *linea, char destino[MAXIMA_LONGITUD_LINEA]) {
// Bandera para saber si el teclado presionó [ o ]
int deberiaEscribirAlInicio = 0;
// Si se escribe al inicio se debe llevar un contador para evitar
// colocar el texto al revés
int contador = 0;
/*
* Recorrer la línea letra por letra...
* */ for (size_t x = 0; x < strlen(linea); x++) {
char caracterActual = linea[x];
// Una cadena de un único carácter, útil para convertir char a char*
char cadenaTemporal[2] = "\0";
cadenaTemporal[0] = caracterActual;
// Si se detecta un [ o ] entonces se reinicia el contador
if ((caracterActual == FIN || caracterActual == INICIO) && deberiaEscribirAlInicio) {
contador = 0;
}
// Si se encuentra un [ o ] entonces solo se cambia la bandera y se salta en el ciclo
if (caracterActual == INICIO) deberiaEscribirAlInicio = 1;
else if (caracterActual == FIN) deberiaEscribirAlInicio = 0;
if (caracterActual == INICIO || caracterActual == FIN) continue;
// Arreglar la línea. Si es al inicio inserta la letra en la cadena según el contador
if (deberiaEscribirAlInicio) {
insertarSubcadena(destino, cadenaTemporal, contador);
contador++;
} else {
// Si no, la agrega al final, usando strcat
strcat(destino, cadenaTemporal);
}
}
}
Dentro de esa función se utiliza otra función para insertar texto en otra cadena; esto se usa para cuando el texto va al inicio, pues cuando va al final se puede usar strcat
.
void insertarSubcadena(char *original, char *subcadena, int indice) {
// El inicio es copiar la original N caracteres definidos por posición
char inicio[MAXIMA_LONGITUD_LINEA] = "";
strncpy(inicio, original, indice);
// El final es copiar desde la posición N caracteres definidos por los sobrantes
char fin[MAXIMA_LONGITUD_LINEA] = "";
strncpy(fin, original + indice, strlen(original) - indice);
// Agregar la subcadena al inicio
strcat(inicio, subcadena);
// Y agregar el fin a la anterior cadena, es decir, al inicio
strcat(inicio, fin);
// Copiarla dentro de la cadena recibida
strcpy(original, inicio);
}
Para terminar recorremos la lista y escribimos el texto ya arreglado en otro archivo:
void escribirListaEnArchivo() {
FILE *archivoSalida;
archivoSalida = fopen(nombreArchivoSalida, "w");
if (archivoSalida == NULL) {
printf("El archivo %s no existe", nombreArchivoSalida);
return;
}
// Recorrer la lista...
struct nodo *temporal = superior;
while (temporal != NULL) {
char miCadena[MAXIMA_LONGITUD_LINEA] = "";
// Arreglar el teclado descompuesto
arreglarTexto(temporal->linea, miCadena);
// Y escribir en el otro archivo
printf("Escribiendo la cadena %s\n", miCadena);
fprintf(archivoSalida, "%s\n", miCadena);
// Avanzamos en la lista
temporal = temporal->siguiente;
}
fclose(archivoSalida);
}
Eso creará el nuevo archivo con las modificaciones ya hechas.
El código completo del ejercicio del teclado descompuesto en C queda como se ve a continuación.
/*
____ _____ _ _ _
| _ \ | __ \ (_) | | |
| |_) |_ _ | |__) |_ _ _ __ _____| |__ _ _| |_ ___
| _ <| | | | | ___/ _` | '__|_ / | '_ \| | | | __/ _ \
| |_) | |_| | | | | (_| | | / /| | |_) | |_| | || __/
|____/ \__, | |_| \__,_|_| /___|_|_.__/ \__, |\__\___|
__/ | __/ |
|___/ |___/
Blog: https://parzibyte.me/blog
Ayuda: https://parzibyte.me/blog/contrataciones-ayuda/
Contacto: https://parzibyte.me/blog/contacto/
Copyright (c) 2020 Luis Cabrera Benito
Licenciado bajo la licencia MIT
El texto de arriba debe ser incluido en cualquier redistribución
*/#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define MAXIMA_LONGITUD_LINEA 100000
char *nombreArchivoOriginal = "teclado-entrada.txt";
char *nombreArchivoSalida = "teclado-salida.txt";
char INICIO = '[';
char FIN = ']';
// Nodo que tiene la línea
struct nodo {
char linea[MAXIMA_LONGITUD_LINEA];
struct nodo *siguiente;
};
// La parte superior de la lista; es decir, por donde comenzamos
struct nodo *superior = NULL;
void agregarLineaALista(char *linea) {
// El que se agregará; reservamos memoria
struct nodo *nuevoNodo = malloc(sizeof(struct nodo));
// Le ponemos el dato
strcpy(nuevoNodo->linea, linea);
// Si es el primer elemento que se agrega...
if (superior == NULL) {
superior = nuevoNodo;
return;
}
// Si no, buscamos el último elemento y le asignamos el valor al mismo
struct nodo *temporal = superior;
while (temporal->siguiente != NULL) {
temporal = temporal->siguiente;
}
temporal->siguiente = nuevoNodo;
}
void insertarSubcadena(char *original, char *subcadena, int indice) {
// El inicio es copiar la original N caracteres definidos por posición
char inicio[MAXIMA_LONGITUD_LINEA] = "";
strncpy(inicio, original, indice);
// El final es copiar desde la posición N caracteres definidos por los sobrantes
char fin[MAXIMA_LONGITUD_LINEA] = "";
strncpy(fin, original + indice, strlen(original) - indice);
// Agregar la subcadena al inicio
strcat(inicio, subcadena);
// Y agregar el fin a la anterior cadena, es decir, al inicio
strcat(inicio, fin);
// Copiarla dentro de la cadena recibida
strcpy(original, inicio);
}
void arreglarTexto(char *linea, char destino[MAXIMA_LONGITUD_LINEA]) {
// Bandera para saber si el teclado presionó [ o ]
int deberiaEscribirAlInicio = 0;
// Si se escribe al inicio se debe llevar un contador para evitar
// colocar el texto al revés
int contador = 0;
/*
* Recorrer la línea letra por letra...
* */ for (size_t x = 0; x < strlen(linea); x++) {
char caracterActual = linea[x];
// Una cadena de un único carácter, útil para convertir char a char*
char cadenaTemporal[2] = "\0";
cadenaTemporal[0] = caracterActual;
// Si se detecta un [ o ] entonces se reinicia el contador
if ((caracterActual == FIN || caracterActual == INICIO) && deberiaEscribirAlInicio) {
contador = 0;
}
// Si se encuentra un [ o ] entonces solo se cambia la bandera y se salta en el ciclo
if (caracterActual == INICIO) deberiaEscribirAlInicio = 1;
else if (caracterActual == FIN) deberiaEscribirAlInicio = 0;
if (caracterActual == INICIO || caracterActual == FIN) continue;
// Arreglar la línea. Si es al inicio inserta la letra en la cadena según el contador
if (deberiaEscribirAlInicio) {
insertarSubcadena(destino, cadenaTemporal, contador);
contador++;
} else {
// Si no, la agrega al final, usando strcat
strcat(destino, cadenaTemporal);
}
}
}
void cargarArchivoEnMemoria() {
FILE *archivoOriginal;
int tamanioBuffer = MAXIMA_LONGITUD_LINEA;
char linea[tamanioBuffer];
archivoOriginal = fopen(nombreArchivoOriginal, "r");
if (archivoOriginal == NULL) {
printf("Imposible leer el archivo %s", nombreArchivoOriginal);
exit(EXIT_FAILURE);
}
while (fgets(linea, tamanioBuffer, archivoOriginal)) {
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 salto de línea
strtok(linea, "\n");
agregarLineaALista(linea);
}
}
fclose(archivoOriginal);
}
void escribirListaEnArchivo() {
FILE *archivoSalida;
archivoSalida = fopen(nombreArchivoSalida, "w");
if (archivoSalida == NULL) {
printf("El archivo %s no existe", nombreArchivoSalida);
return;
}
// Recorrer la lista...
struct nodo *temporal = superior;
while (temporal != NULL) {
char miCadena[MAXIMA_LONGITUD_LINEA] = "";
// Arreglar el teclado descompuesto
arreglarTexto(temporal->linea, miCadena);
// Y escribir en el otro archivo
printf("Escribiendo la cadena %s\n", miCadena);
fprintf(archivoSalida, "%s\n", miCadena);
// Avanzamos en la lista
temporal = temporal->siguiente;
}
fclose(archivoSalida);
}
int main(void) {
cargarArchivoEnMemoria();
escribirListaEnArchivo();
return EXIT_SUCCESS;
}
Recuerda que si quieres probarlo debes tener el archivo teclado-entrada.txt
en el mismo directorio en donde el programa se ejecuta.
Si quieres puedes ver más ejercicios de C en mi blog.
Hoy te voy a presentar un creador de credenciales que acabo de programar y que…
Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…
En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…
En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…
Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…
En este artículo te voy a enseñar cómo usar un "top level await" esperando a…
Esta web usa cookies.