Ejercicio en C – Teclado descompuesto

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.

Descripción del ejercicio

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.

Casos de uso

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

Cargar archivo en lista

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.

Arreglar texto

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);
}

Escribir lista enlazada en archivo usando C

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.

Código completo

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.

Conclusión

Si quieres puedes ver más ejercicios de C en mi blog.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.

Dejar un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *