sábado, 9 de enero de 2016

Aprendiendo informatica: Programar un rombo. Practica universitaria.

Hace unos dias, un amigo y ex-compañero de trabajo me consultaba acerca de una practica que le habian encargado en su universidad y que por lo visto se le habia atascado. Como ya sabeis, me gusta transmitir los proyectos en que voy participando con fines divulgativos. Por eso, voy a explicar como se realiza lo que se describe en la practica. Podeis encontrar el enunciado aqui: https://app.box.com/s/4jl75k2gpsz5x04anr3xj4b1r433e4ch. Ire subiendo el codigo de cada parte que realice, para que veais como se hace, podeis leerlo y descargarlo en la carpeta correspondiente al proyecto: https://app.box.com/s/4ekl5r4puwdmjlkpby7jxe7gzwswb5zt

Este que veis sería el resultado de hacer un rombo de lado igual a 7.


      @
     @.@
    @.O.@
   @.O.O.@
  @.O.@.O.@
 @.O.@.@.O.@
@.O.@.O.@.O.@
 @.O.@.@.O.@
  @.O.@.O.@
   @.O.O.@
    @.O.@
     @.@
      @


Lo primero, analicemos como se hace un rombo, simplificandolo lo mas posible. Un rombo está formado por varias partes (sustituiré los puntos por espacios y el patrón por asteriscos). Tambien voy a numerar las lineas y columnas.


  1234567|654321 
1 ......*|       1
2 .....**|*      2
3 ....***|**     3
4 ...****|***    4
5 ..*****|****   5
6 .******|*****  6
7 *******|****** 7
----------------
6 .******|*****  6
5 ..*****|****   5
4 ...****|***    4
3 ....***|**     3
2 .....**|*      2
1 ......*|       1


Podemos observar que existen cuatro sectores. En el primer sector de nuestro ejemplo (recordemos que tiene un lado igual a 7), que es el situado arriba a la izquierda, vemos que existen siete columnas y siete filas. En el segundo sector, arriba a la derecha, vemos que existe el mismo numero de filas, pero una columna menos. En el tercer sector hay una fila menos y en el cuarto tanto una fila menos como una columna.

Os preguntareis por que he escrito varios numeros en forma creciente, y otros en forma decreciente. Esto tiene su razon de ser: en este caso necesitaremos que sea simetrico. El sistema sería mucho mas simple sin la simetría requerida, pero la práctica no cumpliría su finalidad.

Vamos a empezar a codificar nuestro programa. Usaré el valor de lado = 7 para las pruebas. Mas adelante incluiré la opcion de que pregunte que queremos hacer.

Necesitaremos un bucle que nos imprima los espacios. Como vamos a usar para nuestro ejemplo asteriscos, puntos y finalmente espacios, crearemos un pequeño procedimiento auxiliar que llamaré escribeCar.

void escribeCar(int veces, char car){
for (int i = 1; i <= veces; i++){
printf("%c",car);
}
}

Esto quiere decir que escriba el caracter car un numero veces de veces. Para ello, usaremos un contador llamado i.

Lo pondremos en práctica con la primera parte del rombo. En cada linea l podemos ver que hay e espacios y l asteriscos y que la suma de ambos es e+l = lado. Entonces en cada linea tendremos que poner (lado-l)-1 espacios  y asteriscos.

Vamos a codificar eso. Usamos el codigo que he puesto en el archivo ejemplo1.cpp

......*
.....**
....***
...****
..*****
.******
*******

Para verificar la simetria, en el siguiente punto, cambiaremos los asteriscos por el numero de caracter en la linea, y agregaremos el segundo sector. Para ese segundo sector, será necesario contar al revés, desde l-1 hasta 1. Codificamos y compilamos el ejemplo2.cpp y obtenemos lo siguiente:

......1
.....121
....12321
...1234321
..123454321
.12345654321
1234567654321

Tenemos ya medio rombo, ahora solo tenemos que hacer lo mismo de manera opuesta. Reorganizaremos parte del codigo para que quede mas "limpio", creando dos nuevos procedimientos correspondientes a la parte "creciente" y a la "decreciente" y despues le agregaremos la parte inferior usando los procedimientos mencionados previamente. Aqui teneis el ejemplo3.cpp:

......1
.....121
....12321
...1234321
..123454321
.12345654321
1234567654321
.12345654321
..123454321
...1234321
....12321
.....121
......1

Esto tambien se puede reorganizar mas todavia. En la siguiente versión lo haré. Lo proximo es asignar el caracter correspondiente para que el patrón sea el que nos piden. El patrón debe ser el siguiente:

Si el resto de dividir el numero del caracter mostrado por 4 es
0, poner @
2, poner o
1 o 3, poner .

Vemos ahora que hay un error que he cometido, que ha sido empezar a escribir las lineas empezando por 1. Tenemos ahora dos opciones, modificar el bucle original y resolverlo, o hacer un parchecito. Como me gusta hacer las cosas bien, corregiré el ejemplo3, y de paso le quitaré los puntos y los sustituiré por espacios. Podeis descargar la correccion en ejemplo4.cpp. Aqui teneis el resultado de la correccion.

      0
     010
    01210
   0123210
  012343210
 01234543210
0123456543210
 01234543210
  012343210
   0123210
    01210
     010
      0


Para cambiarlo, usaré una sentencia switch, que tambien la meteré en una funcion. Veamos el resultado del ejemplo5.cpp:


      @
     @.@
    @.o.@
   @.o.o.@
  @.o.@.o.@
 @.o.@.@.o.@
@.o.@.o.@.o.@
 @.o.@.@.o.@
  @.o.@.o.@
   @.o.o.@
    @.o.@
     @.@
      @

Y como podemos observar, el programa nos muestra los resultados deseados. Solo nos quedan dos detalles: introducir la posibilidad de elegir el tamaño del lado y comprobar que los resultados son correctos para 1, 2 y varios numeros pares e impares al azar.

Mi amigo tambien me planteaba una cuestion acerca de los arrays y me pregunto, ¿sería posible usar un array para la secuencia? La respuesta es muy simple: si. Cambiaremos el procedimiento selecCarac para que sea

char selecCarac(int carac){
char secuencia[]={'@','.','o','.'};
printf("%c",secuencia[carac%4]);
}

en lugar de ser

char selecCarac(int carac){
switch (carac%4){
case 0:
printf("%c", '@');
break;
case 2:
printf("%c", 'o');
break;
default:
printf("%c", '.');
break;
}
}

De hecho, la opcion que usa arrays es mucho mas eficiente, ya que en vez de realizar comparaciones simplemente accede a memoria. Por tanto, dejaremos esta opcion en el programa.

Tambien vamos a agregar parte de la interfaz, para que podamos pasar a la parte de pruebas. Aqui teneis el ejemplo6.cpp con la modificacion anterior y las lineas para poder introducir el numero de lados.

Ahora vamos a hacer unas comprobaciones, si no las hemos ido haciendo al probar. Que ocurre si:

-  Ponemos un valor impar de lados (probaremos con 9 lados):


        @
       @.@
      @.o.@
     @.o.o.@
    @.o.@.o.@
   @.o.@.@.o.@
  @.o.@.o.@.o.@
 @.o.@.o.o.@.o.@
@.o.@.o.@.o.@.o.@
 @.o.@.o.o.@.o.@
  @.o.@.o.@.o.@
   @.o.@.@.o.@
    @.o.@.o.@
     @.o.o.@
      @.o.@
       @.@
        @

El resultado es completamente valido.

- Ponemos un valor par de lados (probaremos con 8 lados):

       @
      @.@
     @.o.@
    @.o.o.@
   @.o.@.o.@
  @.o.@.@.o.@
 @.o.@.o.@.o.@
@.o.@.o.o.@.o.@
 @.o.@.o.@.o.@
  @.o.@.@.o.@
   @.o.@.o.@
    @.o.o.@
     @.o.@
      @.@
       @

El resultado es igualmente valido.

- Ponemos 1 unico lado:

@

Correcto de nuevo. Si hubieramos cometido un error, posiblemente habrian aparecido dos arrobas en vertical.

- Ponemos 2 lados:

 @
@.@
 @

Tambien es correcto. Si hubieramos cometido un error, podrian haber salido dos arrobas, como en el caso anterior.

- Ponemos 0 lados:

Obviamente, el programa no saca nada por pantalla.

- Ponemos un numero negativo (probaremos con -5):

Analogamente, un poligono no puede tener un valor de lado negativo, con lo que no muestra nada por pantalla. De hecho, vamos a hacer una comprobacion adicional: ¿escribe algo raro en donde van los espacios? Si cambio de nuevo el codigo para que en vez de los espacios vuelva a mostrar puntos no muestra absolutamente nada.

El código, por tanto, es correcto tanto en los casos indicados por el enunciado como en un juego de pruebas extendido. No voy a agregar el limite que indicaban en el enunciado, puesto que no me es necesario en para el proyecto.

Doy por concluido este proyecto. Espero que os haya resultado interesante. Desde el punto de vista didactico creo que lo es, ya que trata sobre descomposicion de problemas, bucles, secuencias de seleccion, arrays y juegos de pruebas.

Sobre la licencia del software: esta permitida la descarga, compilacion y modificación del codigo para su uso personal con fines didacticos (o sea, para experimentar). Todas las versiones derivadas de este software también deberán cumplir esta licencia. Queda prohibido su uso como implementación de la práctica (nada de copiar, que nos conocemos). Queda prohibida su uso didactico por terceros, ya sea para su uso en clases a titulo individual o publicacion en material docente. En cualquier caso, queda prohibido su uso con fines comerciales.

Hasta el proximo proyecto, un saludo.

No hay comentarios:

Publicar un comentario