Universidad de Costa Rica
Escuela de Ciencias de la Computación e Informática
CI-1201 Programación II - 2014b
Profesor Jeisson Hidalgo-Céspedes
Implemente en C un eficiente comando trim
que permita eliminar espacios en blanco superfluos de archivos o la entrada estándar. Su programa deberá comportarse como un comando normal de Unix. Si se invoca con el parámetro --help
, brindará ayuda al usuario:
Su programa deberá hacer un análisis inicial de los parámetros provistos por el usuario, los cuales se pueden agrupar en cuatro tipos:
-a -i -l -r -t
. Si ninguno se especifica se debe asumir -r
.-W
que indica sobrescribir los archivos de entrada.--help
para proveer ayuda, o el parámetro --version
para conocer la versión del programa.El análisis de parámetros incluye las siguientes consideraciones. Si se especifica --help
, el programa debe imprimir ayuda en la salida estándar indiferentemente de cuáles otros parámetros se hayan provisto. Lo mismo para --version
Si se provee un parámetro no válido, se debe imprimir un mensaje en el error estándar. Si se provee el parámetro -W
sin especificar ningún nombre de archivo, será reportado en el error estándar. En cualquiera de estos casos el programa termina su ejecución inmediatamente. Ejemplos:
$ trim -l -W -r -c trim: -c unknown option $ trim -l -W -r trim: -W option and no files were specified $
Si no se especifican nombres de archivos se asume que el texto será provisto en la entrada estándar y por ende, el resultado será impreso en la salida estándar. Por el contrario, si se proveen nombres de archivo y no la opción -W
, el resultado de hacer trim en cada línea de ellos será impreso en la salida estándar; pero si la opción -W
fue especificada, el contenido de los archivos será sobrescrito. En cualquiera de estos casos, se dice que el comando tendrá una fuente de datos para trabajar, indiferentemente de si sean archivos o la entrada estándar; y también un destino de datos donde serán impresos los textos sin espaciado redundante. Sugerencia: note que las funciones fgets()
, fputs()
y fprintf()
pueden trabajar tanto en archivos como la entrada y salida estándar, simplemente cambiando el parámetro FILE*
.
El programa debe trabajar a nivel de línea. Es decir, el comando lee líneas de la fuente de datos hasta encontrar el carácter fin de archivo. Por cada línea obtenida, imprimirá en el destino de datos una línea resultado de hacerle el tipo de trim escogido por el usuario. Nótese que cada archivo resultante tendrá la misma cantidad de líneas que el respectivo archivo original.
Si la opción -W
es especificada, su programa debe abrir cada archivo por parámetro y el resultado escribirlo en un archivo temporal. Puede utilizar el mismo nombre del archivo original concatenándole la extensión .tmp
ó .trim
. Para efectos de esta tarea asuma que este archivo no existe, y si existiese, será truncado sin aviso. Una vez terminado el procesamiento, el archivo original es eliminado con la función remove()
, y el archivo temporal es renombrado al original con la función rename()
, ambas de la biblioteca <stdio.h>
.
Se considera como espaciado los caracteres: espacio en blanco (' '
), tabulador ('\t'
), cambio de línea ('\n'
), retorno de carro ('\r'
), tabulador vertical ('\v'
), y avance de página ('\f'
). La función isspace(ch)
de <ctype.h>
retorna un valor distinto de 0 si su argumento es uno de estos caracteres considerados espacios, y el valor 0 cuando es otro carácter.
El left trim elimina el espaciado completo que aparece al inicio de la cadena, antes del primer carácter que no es espacio. Si se provee una cadena que consta únicamente de espaciado, el resultado será la cadena vacía. Por ejemplo:
char test1[] = "\t\t Test 1\t\t"; fprintf(stdout, "[%s]\n", trim_left(test1)); // Imprime "[Test 1\t\t]" char test2[] = "\t\t \t\t"; fprintf(stdout, "[%s]\n", trim_left(test2)); // Imprime "[]"
El right trim elimina el espaciado completo que aparece al final de la cadena, después del último carácter que no es espacio. Si se provee una cadena que consta únicamente de espaciado, el resultado será la cadena vacía. Por ejemplo:
char test1[] = "\t\t Test 1\t\t"; fprintf(stdout, "[%s]\n", trim_right(test1)); // Imprime "[\t\t Test 1]" char test2[] = "\t\t \t\t"; fprintf(stdout, "[%s]\n", trim_right(test2)); // Imprime "[]"
El inner trim o trim inside reemplaza cada espacio redundante dentro de la cadena por un espacio simple. Su área de trabajo es a partir del primer carácter que no es espacio hasta el último carácter que no es espacio. Si se provee una cadena que consta únicamente de espaciado, o de una única palabra, el trim inside no tendrá efecto. Por ejemplo:
char test1[] = "\t\t Test 1\t\t"; fprintf(stdout, "[%s]\n", trim_inside(test1)); // Imprime "[\t\t Test 1\t\t]" char test2[] = "\t\t \t\t"; fprintf(stdout, "[%s]\n", trim_inside(test2)); // Imprime "[\t\t \t\t]"
Diseñe su solución para que cada una de estas funciones realice a lo sumo un único recorrido por la cadena que recibe. Nótese que utilizar funciones como strlen()
ya realizan un recorrido completo por la cadena. Esta restricción implica que si el usuario invoca su comando con sólo uno de los parámetros -l
, -r
ó -i
, su programa recorrerá una única vez cada línea con el propósito de hacer trim. Si su comando se invoca con dos tipos de trim (-i -r
, -t
, ...), su programa recorrerá a lo sumo dos veces cada línea para quitar espacio redundante. Si su comando se invoca para hacer los tres tipos de trim (-l -i -r
ó -a
), cada línea será recorrida a lo sumo tres veces para quitar espacio redundante.
La diversidad de líneas a las que se puede enfrentar su comando es infinita. Es imposible probar todas las potenciales cadenas contra todos los posibles parámetros de su comando. Sin embargo, se puede identificar unos casos representativos y probar contra ellos. El profesor provee un programa de pruebas base (incluido en trim.zip
) y cada estudiante deberá agregar al menos tres casos de prueba en el archivo test.cpp
. Además el estudiante deberá documentar una de las funciones del encabezado trim.h
. Estos dos pasos deben realizarse antes que el resto de la tarea. Las instrucciones serán publicadas en Mediación Virtual.
Seguidamente, el estudiante deberá implementar los siguientes cuatro métodos en el archivo trim.c
:
// Removes redundant whitespace at beginning of line. Returns line char* trim_left(char* line); // Removes redundant whitespace at ending of line. Returns line char* trim_right(char* line); // Removes redundant whitespace at ending of line knowing its length. Returns line char* trim_right2(char* line, size_t len); // Reduces redundant whitespace from the first non space character to the last non space character // in line. Each sequence of whitespace is replaced with a single space. Returns line char* trim_inside(char* line);
Para correr los casos de prueba, debe generar el programa de pruebas (make test
). Luego correr el programa resultante (./test
).
Descargue el archivo trim.zip
. Desempáquelo en algún lugar de su sistema de archivos, preferiblemente bajo control de versiones de un repositorio personal de su propiedad (alojado en Bitbucket, por ejemplo). Verá los siguientes archivos:
Makefile
. Permite generar el programa de pruebas (make test
), y el comando de trim
(make trim
) en un sistema operativo basado en Unix (o en Windows bajo Cygwin).Doxyfile
. Archivo de configuración de Doxygen para generar documentación de la biblioteca "trim". Para generar la documentación emita make doc
.trim.h
. Archivo de encabezado de la Trim Library. Debe modificarlo para documentar la función asignada (Parte 1 de la tarea).test.cpp
. Prueba la Trim Library. Debe modificarlo para agregar al menos tres casos de prueba (Parte 1 de la tarea).trim.c
. Implementa las funciones de la Trim Library. Debe modificarlo para implementar las cuatro funciones indicadas en la sección "Casos de prueba" de este enunciado. (Parte 2 de la tarea).main.c
. Implementa el comando trim
. Debe modificarlo para implementar el comando completo (Parte 2 de la tarea).catch.hpp
. Biblioteca Catch para generar casos de prueba. Este archivo no debe ser modificado.test.pro
. Proyecto de QtCreator para generar el programa de pruebas. Archivo incluido para comodidad de quien guste utilizar este IDE o QMake.trim.pro
. Proyecto de QtCreator para generar el comando trim
. Archivo incluido para comodidad de quien guste utilizar este IDE o QMake.--help
, o la versión con --version
, indiferentemente de los demás parámetros. Imprime error ante una opción no válida o ante un -W
sin archivos.-W
, el comando lee líneas de cada uno de ellos en orden e imprime resultados en la salida estándar.-W
, lee líneas de cada uno de ellos en orden e imprime resultados en archivos temporales que luego reemplazan a los originales. Si hay algún error con el manejo de archivos (no existen, no hay espacio en disco, etc.) se reportan en el error estándar. Cierra archivos tan pronto como se dejen de utilizar.fgets()
maneja adecuadamente cambios de línea al final de las cadenas devueltas. Si utiliza memoria dinámica no provoca fugas de memoria.trim_left()
, trim_right()
, trim_right2()
y trim_inside()
trabajan correctamente, no acceden a memoria no permitida, hacen su trabajo en un único recorrido por la cadena. Retornan un puntero hacia el inicio de la cadena original. Pasan al menos todos los casos de prueba provistos por el profesor.trim -lrW file.txt
equivale a trim -l -r -W file.txt
.[10% Opcional] Implemente la función trim_line2()
. Esta función recibe una cadena de longitud conocida y realiza trim left, trim inside, o trim right, de acuerdo a sus parámetros, en un único recorrido por la cadena, lo cual es muy eficiente. Para habilitar esta función en el programa de prueba y las funciones trim() y trim_all(), habilite la macro TRIM_LINE2
en su Makefile
:
DEFINES=-DTRIM_LINE1=1 -DTRIM_LINE2=0
Si trabaja con QtCreator, edite el archivo .pro
y "descomente" la línea DEFINES += TRIM_LINE2
(es decir, elimine el símbolo #
al inicio de la línea). Asegúrese de que su implementación pasa los casos de prueba.
trim_line()
que realiza el mismo trabajo que trim_line2()
, pero no conoce el tamaño de la cadena durante su invocación. Las reglas son las mismas que el punto anterior. La macro a habilitar es TRIM_LINE1
.Discusión: ¿Se puede hacer que su programa trabaje un carácter a la vez?. Es decir, obtenga un carácter de la fuente de datos y determine si ese carácter debe: escribirse en el destino de datos, ser reemplazado por otro carácter o ser ignorado. ¿Cuáles son las ventajas y desventajas de este esquema? ¿Se puede utilizar este esquema cuando la fuente de datos es la entrada estándar?.
La tarea está dividida en dos entregables. Revise las asignaciones en Mediación Virtual para conocer los detalles. Asegúrese de su código compile. Si su código no compila podría ser calificado con 0. Si tiene código experimental en su tarea, que no funciona y quiere que sea analizado por el asistente, puede dejarlo dentro de comentarios o condicionado por el preprocesador (#if 0 ... #endif
), además de al menos un comentario explicando sus inteciones o pruebas.