Translate

miércoles, 10 de agosto de 2011

Lo básico para iniciar en los concursos (Lectura de casos de prueba)


Existen muchos concursos que varían su forma en evaluar los problemas pero por lo regular una de las cosas necesaria que tienen la mayoría de ellos es la lectura de casos de prueba, estos casos de prueba pueden ser cortos o otros tener miles de líneas.

Iniciemos por ver como leer los casos de entrada de un problema, existen diferentes tipos de datos, enteros, cadenas, caracteres, flotantes, doble flotantes, y bueno en la escuela aprendieron a leer todos estos tipos de datos aquí solo escribiré la forma de leerlos en C.

/*Entero*/ int dato; scanf(“%d”, &dato);
/*Caracteres*/ char c: scanf(“%c”, &c);
/*Flotante*/ float f: scanf(“%f”, &f);
/*Flotante*/ float f: scanf(“%f”, &f);
/*doublé flotante*/ double df: scanf(“%lf”, &df);
/*long long*/ long long lng: scanf(“%lld”, &lng);
/*cadena*/ char cad[100]; scanf(“%s”, cad);

Este tipo de lecturas ya ustedes las conocen y no representa ninguna novedad que las vuelva a escribir, pero existe otra forma de leer casos de entra, veamos un ejemplo en especifico.
Cuando participe en un concurso nacional ANPA nos pidieron leer un numero M seguido de M líneas que representaban las aristas de un grafo que venían de la siguiente manera (nodoA,nodoB) , así es con todo y paréntesis. La mayoría de los competidores trataron de parsear la cadena quitando los paréntesis y luego obtener con un for por separado los valores de las aristas. Veamos como estaba el caso de entrada:

4
(10,123)
(10,1)
(2,4)
(12,10023)

Suponiendo que estos son los elementos que vamos a leer como los leerías tu? Piénsalo un poquito, yo escribiré por lo mientras un código que los lee de una manera no muy fácil.

# include
# include

main(){
int nodoa, nodob, numnodos, x, pos;
char line[100];
scanf("%d", &numnodos);
for( x = 0; x < numnodos; x++){
scanf("%s", line);
nodoa = 0;
for( pos = 1; line[pos] != ','; pos++){
nodoa *= 10;
nodoa += line[pos] -'0';
}
nodob = 0;
for( pos++ ; line[pos] != ')'; pos++){
nodob *= 10;
nodob += line[pos] -'0';
}
printf("Los valores son : %d %d\n" , nodoa, nodob);
}
return 0;
}


Como pueden observar lo que se intenta es obtener el nodoa, partiendo de la suposición que el primer carácter de la cadena es un paréntesis que abre ‘(‘ y por eso se omite su lectura, luego se empieza a leer el numero hasta encontrar una coma ‘,’ y el nodob se lee hasta que se encuentre con un paréntesis que cierra ‘)’, para el caso especifico de este problema , este pequeño algoritmo lee los datos correctamente, pero si existiese algún espacio entre el paréntesis y el numero este algoritmo fallaría. Además de este inconveniente el algoritmo de lectura es muy largo y hace más difícil de debugeo. Existe otra forma de leer esto mucho mas sencillo. Utilizando correctamente el scanf podemos realizar la lectura de la siguiente manera :

main(){
int nodoa, nodob, numnodos, x;
scanf("%d", &numnodos);
for( x = 0; x < numnodos; x++){
scanf(" ( %d , %d ) ", &nodoa, &nodob);
printf("Los valores son : %d %d\n" , nodoa, nodob);
}
return 0;
}

Se redujo considerablemente el código, y adicional a eso también se corrigió el error de que la entra tuviera un espacio o enter. Si el Archivo de entrada fuera el siguiente:

4
( 10, 123)
(10, 1)
(
2
,4)
(12, 10023 )
El programa leería correctamente la entrada.

¿Como hace esto el scanf?

Para poder leer este tipo de problemas de una manera mas sencilla es necesario estudiar todo lo que puede hacer el scanf y como funciona.
Primero si leemos cualquier tipo de dato, entero, flotante, cadena, etc, el scanf omite los espacios enters y tabs que tiene el dato al inicio de este, si nosotros queremos leer una cadena que contenga espacios al inicio no lo podríamos leer un solo %s para leer cadenas tendríamos que utilizar otra técnica u otro método de lectura como el gets. Con eso podemos resolver el problema de leer números que estén separados por muchos espacios o caracteres no imprimibles.

¿Que hacemos cuando están separados por una coma?

Simplemente agregamos en el scanf la coma para que el propio scanf la lea y la omita de la lectura por ejemplo scanf(“%d , %d”, &a, &b);. Pero que pasaría si nosotros tenemos una cadena por ejemplo Rodrigo,Burgos donde indica el nombre coma apeldó, si leemos esto con un scanf(“%s,%s”, nombre, apellido); fallaría ya que el %s lee todos los caracteres imprimibles, incluyendo la coma.

¿Entonces como leemos estos valores por separado?
Para leer los valores por separados el scanf permite poner entre el porciento % y el indentificador del tipo de dato (cadena,entero,...) parámetros delimitar dicha lectura. También permite que en vez de indicar el tipo de dato se especifiqué que datos quiere que leea utilizando corchetes. Por ejemplo si quiero leer solo numero del 3 al 6 solo escribimos

scanf(“%[3456]”, &numero);

y aparte podemos especificarle al scanf que lea esta un determinado carácter, en especifico que lee todo hasta encontrar un carácter. Cuando decimos todo , queremos decir todo, hasta los espacios y enteres. Y esto se hace utilizando los corchetes y especificando con un caracter especial (^) la letra que delimitara la lectura entonces para leer el nombre Rodrigo,Burgos solo hay que especificarle al scanf que leer el nombre hasta encontrar una coma, luego leer la coma y después el apellido.

scanf(“%[^,],%s”, nombre, apellido);

Si quisiéramos leer toda una línea completa la leeríamos de la siguiente menera:

scanf(“%[^\n]%*c”, línea);

Como puede ver se especifica que se lea hasta llegar a un retorno de línea, adicional a esto se leerá un char.

¿Por qué no se especifica el char en el scanf?
Como pueden ver el char no se esta leyendo de una manera normal se le esta anteponiendo un carácter asterisco (*) antes de la c, esto le indica al scanf que tiene que leer un char pero que no es necesario que lo guarde en ningún lado.


Veamos otro ejemplo, si uno quiere leer un entero solo tiene que poner scanf(“%d”); , pero si uno quiere leer primero 2 digitos luego otros 3 dígitos y luego una cadena podemos usar una expresión muy útil del scanf. Por ejemplo si queremos leer esta cadena 22312cadena y tenerlo separado como lo mencionamos anteriormente basta con tan solo escribir.

scanf(“%2d%3d%s”, &num1, &num2, cad);
Con esta información ya tenemos algunas herramientas a la hora de leer casos de prueba, por ejemplo puede hacer de tare alas siguientes lecturas:

Leer fechas – recibirán en el siguiente formato la fecha y hora del dia “hh:mm dd/MM/aaaa” ustedes tienen que imprimirla de la siguiente manera: “dd hh MM aa”.
Leer aristas de un grafo. Las aristas vendrían colocadas de la siguiente manera:
a->b
b->c
c->d
...

Se me olvido que tenia que leer de archivo, nooooo!!!!

Ya conocemos a cierto punto lo que se puede hacer con el scanf pero la mayoría de los concursos presenciales requieren que se lee de un archivo las entradas y que se escriba a un archivo la salida. Bueno si uno ya hizo todo el código y se percata que tenía que haber leído de un archivo toda la información, pues puede cambiar todos los scanf por fscanf y crear un puntero a un archivo, pero si le da mucha flojera hacer esto tambien puede usar el maravilloso freopen.

Si como lo escucho el maravillo freopen a mitad de precio llame a hora y recibirá este maravilloso método a las puertas de su casa. El freopen es limitado lleme ahora o no ya no lo encontrara en ningun lado.

Bueno ya hablando en serio el freopen nos permite indicar un archivo, el modo de abrirlo , si es lectura, escritura o ambas , y decirle quien escribe sobre el, por ejemplo para todo lo que salga a consola solo tenemos que indicarle que será stdout a donde quien escribe, y si ponemos stdin indicamos que de stdin debe leer, stdout es estándar output , y stdin es Estándar input.

# include
# include

main(){
char nombre[100];
char apellido[100];
freopen("a.sal","w", stdout);
freopen("a.in","r", stdin);
scanf("%[^,],%s", nombre, apellido);
printf("Mi nomber es %s %s\n", nombre, apellido);
return 0;
}


Este pequeño ejemple les mostrará como funciona el freopen. En a.in tenemos que tener la entrada Rodrigo,Burgos , y después de ejecutar el programa en a.sal tendremos la salida. Y de esta menara no tendremos que cambiar ningun scanf o printf de nuestro programa.

Espero les sirve la información si tienen mas dudas y cosas o problemas que quieran que explique mas a fondo, escríbanlo cualquier critica o comentario es bien recibido.


Saludos.

No hay comentarios: