búsqueda

Búsqueda personalizada

resultados

miércoles, 7 de octubre de 2009

Recusividad: una aplicación Clásica

Recursividad: una aplicación clásica




Objetivo


El objetivo es explicar el concepto de la recursividad y utilizarlo para desarrollar la resolución de un laberinto en código C++.

Introducción



Recursión o recursividad es la forma en la cual se especifica un proceso basado en su propia definición.

(Definición de wikipedia)


¿Qué Significa esto?


Los ejemplos siempre son buenos para entender algo abstracto.
Veamos el ejemplo clásio del factorial.

Cómo resolvemos el factorial de 4.
4 x 3 x 2 x 1 = 24

Ahora llamemos a 4, N.
Cómo lo resolvemos.
N x (N-1) x (N-2) x (N-3)

Ahora pensemos cómo lo resolvemos, cuales son los pasos que aplicamos.

Supuestos:
Tenemos un número N.
(!: es el símbolo del factorial.)
Lo tenemos que multiplicar por sus enteros anteriores hasta llegar al 1.
Sabemos que por definición de factorial: 1! = 1 y 0! = 1.
Guardo el producto en un acumulador que comienza en 1.

Pasos:

1. ¿N = 1 ?
Sí, entonces terminé el calculo. El resultado está en Acumulador;
Sino Acumulador = Acumulador x N.
2. Ahora llamo N a (N-1)
3. Voy al paso 1.

Probemos los pasos: Con N = 4 (Recordemos Acumulador = 1)

1. ¿4 = 1?
No, entonces Acumulador = Acumulador x N.
Reemplazando, Acumulador = 1 x 4.
2. N = N -1.
Reemplazando, N = 4 -1, N = 3
3. Voy al paso 1 con N=3.

1. ¿3 = 1?
No, entonces Acumulador = 4 x 3 = 12;
2. N = 2
3. Voy al paso 1 con N=2

1. ¿2 = 1?
No, entonces Acumulador = 12 x 2 = 24
2. N = 1
3. Voy al paso 1 con N = 1

1. ¿1=1?
Sí, entonces terminé el calculo. El resultado está en Acumulador = 24.


Sí, sí, las cuentitas muy lindas, pero.....
¿¿
qué tiene ver con la recursividad??

En el primer paso se define como es el cálculo del factorial y se indica en qué momento se termina de calcular.
En el segundo paso tomo el entero anterior.
En el tercer paso tenemos que aplicar el mismo cálculo para el entero anterior. Y cómo aplico el cálculo: Como se indica en el paso 1. (Cómo ya se lo apliqué al entero posterior)
Ahí está la recusividad: Realizo un proceso basado en la definición. Itero realizando la misma definición hasta que se de la condición de corte.

Realizo un cálculo parcial, pero descubro que tengo que hacer lo mismo para el entero anterior y cuando lo realizo para este vuelvo a descubrir lo mismo, así hasta que llegue al 1.

La recursividad nos pertime resolver problemas que posean la característica de tener que utiliza la misma definición de resolución, los mismos moviemientos, los mismos pasos, los mismos cálculos o consideraciones recurrentemente.
Con la recursividad se resuelve el factorial, la sucesión de Fibonacci, las torres de Hanoi, y muchos juegos de ingenio.


Por supuesto, recorrido de árboles y resolución de caminos.


Problema del laberinto




El problema del laberinto dice que tenemos una matriz de NxM, formadas por ceros y unos.

Definiciones:
  • Los unos son camino
  • Los ceros son pared
  • Comenzamos en las coordenadas (Columna, Fila)=(0,0)
  • Sólo nos podemos mover hacia abajo o hacia la derecha
  • No podemos movernos en diagonal
  • No podemos saltar, entonces el camino se determina por unos que esten en casillas vecinas, siguiendo las reglas ya mencionadas
  • La prioridad para movernos será: Abajo, Derecha.

¿Cómo lo resolverían sin la recusividad?

Seguro se puede, pero sería muy compleja y difícil de enterder, hasta para nosotros mismo si tenemos que mantener el código después de un tiempo.

Veamos la magia de la recursividad




Este es el código de la resolución:

Decisiones que tomé:

  • El programa está compuesto por dos clases: CAzar y CMatrix.
  • El laberinto se llena con números al azar que los genera la clase CAzar. Un buena idea sería levantar la matriz de un archivo. Si esto lo llevamos a un juego podríamos tener un editor de laberintos.
  • En la clase CMatrix se resuelve la generación, impresión y el chequeo de si tiene salida el laberinto.

CAzar.h
#ifndef CAZAR_H_
#define CAZAR_H_

#include
#include

class CAzar {
public:
CAzar();
virtual ~CAzar();
public:
double getRndNumber(double dMin, double dMax);
};

#endif /* CAZAR_H_ */

CAzar.cpp
#include "CAzar.h"

CAzar::CAzar() {
srand((unsigned)time(0));
}

CAzar::~CAzar() {
}

double CAzar::getRndNumber(double dMin, double dMax){
double random;

random = (rand()% (int(dMax-dMin) + 1)) + dMin;

return random;
}

CMatriz.h

#ifndef CMATRIX_H_
#define CMATRIX_H_

#include "CAzar.h"

//Macros para no tener número mágicos en el código
#define MAT_MAX_COL 10
#define MAT_MAX_ROW 5

#define _CAMINO_ 1.0
#define _PARED_ 0.0

//Macros para hacer más claras las preguntas
#define PUEDO_PASAR(x) (x == _CAMINO_)
#define FILA_FINAL(x) (x == MAT_MAX_ROW-1)


class CMatrix {
public:
CMatrix();
virtual ~CMatrix();

void Generate();
void Print();

bool IsThereExit();
bool RecursiveSolve( int c, int f );

private:
CAzar azar;
int matrix[MAT_MAX_COL][MAT_MAX_ROW];
};

#endif /* CMATRIX_H_ */

CMatrix.cpp

#include "CMatrix.h"
#include


using namespace std;

CMatrix::CMatrix() {
}

CMatrix::~CMatrix() {
// TODO Auto-generated destructor stub
}

////////////////////////////////////////////////
void CMatrix::Generate(){
//

for ( int c = 0; c < MAT_MAX_COL; c++){

for ( int f = 0; f < MAT_MAX_ROW; f++ ){

matrix[c][f] = (int) azar.getRndNumber( _PARED_, _CAMINO_ );
}
}
}

////////////////////////////////////////////////
void CMatrix::Print(){
//
cout << "T H E M A T R I X" << endl;

int n;

for ( int f = 0; f < MAT_MAX_ROW; f++){

for ( int c = 0; c < MAT_MAX_COL; c++ ){

cout << matrix[c][f];
}
cout << endl;
}

}

/////////////////////////////////////////////////
bool CMatrix::IsThereExit(){

return RecursiveSolve(0,0);
}


/////////////////////////////////////////////////
bool CMatrix::RecursiveSolve( int c, int f ){

//1. Condición de corte final
if ( PUEDO_PASAR( matrix[c][f] ) &&
FILA_FINAL( f ) ){
cout << "estoy en (" << c << ", " << f << ")llegue al final" << endl;
return true;
}

//2. No llegué al final, así que recorro según prioridades
if( PUEDO_PASAR( matrix[c][f] ) ){//Si es camino

//2.1. Camino en el laberinto, la prioridad es bajar
if( f+1 < MAT_MAX_ROW &&
PUEDO_PASAR( matrix[c][f+1] ) ){
cout << "estoy en (" << c << ", " << f <<") me fijo en la fila siguiente" << endl;
return RecursiveSolve( c, f+1); //Uso la magia de la recursividad
}
else {

//2.2. Como no puedo bajar, me desplazo a la derecha
if( c+1 < MAT_MAX_COL && PUEDO_PASAR( matrix[c+1][f] ) ){
cout << "estoy en (" << c << ", " << f <<") me fijo en la Columna siguiente" << endl;
return RecursiveSolve( c+1, f);//Uso la magia de la recursividad
}else{
//No tengo camino para seguir.
cout << "estoy en (" << c << ", " << f <<") No tengo camino para seguir" << endl;
return false;
}
}
}

//3.Encontré una pared, por acá no.
cout << "estoy en (" << c << ", " << f <<") NADA!" << endl;
return false;
}





RecursiveSolve


  • Este método devuelve true si hay salida o false en caso contrario.
  • Recibe como parámetros las coordenadas en donde estoy parado en la matriz.
  • Lo primero que chequeamos es la condición de corte: Que pueda pasar (que esté parado en un 1) y que la fila sea la última
  • Si no se da la condición de corte me fijo si puedo pasar: es decir si estoy en un 1 y no estoy en la última fila. De ser así, me muevo de acuerdo a las prioridades: primero hacia abajo y después hacia la derecha.
  • Notemos que si puedo pasar, camino. ¿cómo camino? Acá es donde uso la magia de la recursividad, me llamo a mi mismo(RecursiveSolve), aplico mi definición. El paso uno del ejemplo del factorial que escribí al principio.
  • Cada vez que hacemos el "return RecursiveSolve( ... , ...);" estamos agregando en la callstack un nodo más, así que la recursividad podría bajar la performance del sistema si hacemos demasiados llamados recursivos. Este es un punto a tener en cuenta.
  • Cuando se ejecutan las líneas "return true;"o "return false;" el procesador quita un nodo del callstack y regresa al punto que se había llamado. Eso provoca que se vuelva en la matríz por cada uno de los caminos que se bifurcaron.
  • El método avanza en un 1 y analiza todos lo caminos desde allí, así con cada 1, regresando cuando encuentra un cero o cuando llega al final

bool CMatrix::RecursiveSolve( int c, int f ){

//1. Condición de corte final
if ( PUEDO_PASAR( matrix[c][f] ) &&
FILA_FINAL( f ) ){
cout << "estoy en (" << c << ", " << f << ")llegue al final" << endl;
return true;
}

//2. No llegué al final, así que recorro según prioridades
if( PUEDO_PASAR( matrix[c][f] ) ){//Si es camino

//2.1. Camino en el laberinto, la prioridad es bajar
if( f+1 < MAT_MAX_ROW &&
PUEDO_PASAR( matrix[c][f+1] ) ){
cout << "estoy en (" << c << ", " << f <<") me fijo en la fila siguiente" << endl;
return RecursiveSolve( c, f+1); //Uso la magia de la recursividad
}
else {

//2.2. Como no puedo bajar, me desplazo a la derecha
if( c+1 < MAT_MAX_COL && PUEDO_PASAR( matrix[c+1][f] ) ){
cout << "estoy en (" << c << ", " << f <<") me fijo en la Columna siguiente" << endl;
return RecursiveSolve( c+1, f);//Uso la magia de la recursividad
}else{
//No tengo camino para seguir.
cout << "estoy en (" << c << ", " << f <<") No tengo camino para seguir" << endl;
return false;
}
}
}

//3.Encontré una pared, por acá no.
cout << "estoy en (" << c << ", " << f <<") NADA!" << endl;
return false;
}

Prueba de la solución


int main(void) {

puts("Laberinto");

CMatrix matrix;

for ( int i = 0; i < 10; i++){
cout << endl;
cout << "----------------------------------------------------------" << endl;

matrix.Generate();
matrix.Print();

cout << matrix.IsThereExit() << endl;
}
}


Laberinto

----------------------------------------------------------
T H E M A T R I X
1101010110
0000101110
1000110100
0111011100
0010000100
estoy en (0, 0) me fijo en la Columna siguiente
estoy en (1, 0) No tengo camino para seguir
0

----------------------------------------------------------
T H E M A T R I X
1000001010
0110010011
1011001110
0100010110
0001000001
estoy en (0, 0) No tengo camino para seguir
0

----------------------------------------------------------
T H E M A T R I X
0000001101
1110010000
0010101000
1010101101
0110001101
estoy en (0, 0) NADA!
0

----------------------------------------------------------
T H E M A T R I X
0010011100
0111101110
1101010100
1001011110
1010111000
estoy en (0, 0) NADA!
0

----------------------------------------------------------
T H E M A T R I X
0001100111
1011110001
0000101000
0110010011
1010101001
estoy en (0, 0) NADA!
0

----------------------------------------------------------
T H E M A T R I X
1001101100
1110101001
0010100100
1010000110
1011110110
estoy en (0, 0) me fijo en la fila siguiente
estoy en (0, 1) me fijo en la Columna siguiente
estoy en (1, 1) me fijo en la Columna siguiente
estoy en (2, 1) me fijo en la fila siguiente
estoy en (2, 2) me fijo en la fila siguiente
estoy en (2, 3) me fijo en la fila siguiente
estoy en (2, 4)llegue al final
1

----------------------------------------------------------
T H E M A T R I X
1101000111
0100011101
0001001110
1111011000
0101001010
estoy en (0, 0) me fijo en la Columna siguiente
estoy en (1, 0) me fijo en la fila siguiente
estoy en (1, 1) No tengo camino para seguir
0

----------------------------------------------------------
T H E M A T R I X
1000010001
0111000100
0011011101
0011101011
1011011110
estoy en (0, 0) No tengo camino para seguir
0

----------------------------------------------------------
T H E M A T R I X
1100100111
0101100001
1011100000
1010011011
0001100011
estoy en (0, 0) me fijo en la Columna siguiente
estoy en (1, 0) me fijo en la fila siguiente
estoy en (1, 1) No tengo camino para seguir
0

----------------------------------------------------------
T H E M A T R I X
1011001100
1011101110
1110111010
1001001010
0110110011
estoy en (0, 0) me fijo en la fila siguiente
estoy en (0, 1) me fijo en la fila siguiente
estoy en (0, 2) me fijo en la fila siguiente
estoy en (0, 3) No tengo camino para seguir
0

martes, 23 de junio de 2009

Desarrollar para unix sobre windows

Contenido del documento

  1. Objetivo
  2. Introducción
  3. Infraestructura
  4. Cygwin
  5. Eclipse
  6. Conclusión

  1. Objetivo:

    Este documento tiene como objetivo presentar una alternativa para armar un entorno de desarrollo sobre Windows para aplicaciones c/c++, que se implementarán en sistemas unix o linux.



  2. Introducción:

    Si vamos a desarrollar programas para que corran en un sistema unix, o basado en unix, lo ideal es que desarrollemos directamente sobre ese sistema. Si tiene un entorno gráfico podemos usar el eclipse, si sólo tenemos consola, siempre tenemos la alternativa del vi para generar el código fuente y luego compilar usando makefiles.
    Pero, ¿qué pasa si sólo contamos con plataformas windows para desarrollar y además no podemos conseguir un imagen del sistema unix para montar una máquina virtual, o si el cliente nos brinda acceso a su entorno de desarrollo vía SVN, pero la conexión es tediosamente lenta cómo para desarrollar?
    Para solucionar este problema, una alternativa es usar el IDE eclipse para windows, con el que podemos generar el código, compilar y depurar (o "debbugear" para los amigos). También podemos probarlo directamente sobre el cygwin copiando los fuentes, compilándolos por línea de comando y realizando los casos de prueba en la consola.

    Para la etapa de aseguramiento de la calidad (testing & bug fixing): Es altamente recomendable que podamos hacerlo accediendo al entorno del desarrollo del cliente.


  3. Infraestructura:

    Partimos del supuesto que sólo tenemos computadoras con Windows y que vamos a desarrollar sobre ansi c o c++.
    Necesitaremos instalarnos cygwin y eclipse CDT para desarrollar sobre ansi C/C++.

    [PC o Notebook]/Windows - Eclipse - Cygwin - SVN

    • VNC - Conexión con el cliente
  4. Cygwin:


    1. ¿Qué es Cygwin?

      En su página web: http://www.cygwin.com/ nos indica lo que no es cygwin:

      • No es una manera de correr aplicaciones nativas de linux sobre windows
      • No es una manera de correr mágicamente aplicaciones de windows con funcionalidades de unix.
      Para este documento diremos que cygwin es una plataforma que nos permite compilar sobre windows aplicaciones para sistemas basados en unix, además de brindarnos la posibilidad de ejecutar comandos en su consola.

      El hecho que las aplicaciones compilen y se ejecuten con éxito los casos de prueba en esta plataforma, no garantiza el éxito en el entorno unix/linux del cliente, pero sí aumenta de las probabilidades de que así sea.

    2. ¿Cómo lo usaremos?

      Lo vamos a usar para que el eclipse compile sobre windows usando el gcc/g++, que es el compilador nativo de los sistemas unix.
      También es muy útil para probar comandos de unix que no conocemos o que son complejos, sobre la consola que nos provee el cygwin.

      Esto es muy útil si estamos analizando un shell script, por ejemplo.

    3. ¿Cómo lo instalamos?

      1. Nos bajamos el instalador desde su página: http://www.cygwin.com/setup.exe
      2. Lo ejecutamos
      3. Seleccionamos "install from internet"
      4. Seleccionamos un repositorio
      5. Seleccionamos los paquetes para gcc/g++ y el make

    4. ¿Por qué CygWin?


      • Es libre
      • Es de fácil instalación
      • Consume pocos recursos
      • Eclipse lo puede utilizar para compilar con el gcc/g++ sobre windows


  5. Eclipse:


    1. ¿Qué es Eclipse?


      Es un IDE (integrated development environment).
      En un principio, utilizado para desarrollar programas java, pero hoy por hoy, tenemos muchos plugin para desarrollar en diferentes lenguajes. En nuestro caso para desarrollar en c/c++.

    2. ¿Cómo lo usaremos?

      Lo usaremos para el desarrollo del código, compilación, depuración, testeos y para realizar el control de versiones.

    3. ¿Cómo lo instalamos?


      1. Nos bajamos el instalador desde su página: http://www.eclipse.org/downloads/download.php?file=/technology/epp/downloads/release/ganymede/SR2/eclipse-cpp-ganymede-SR2-win32.zip .
      2. Lo descomprimimos
      3. Agregamos a la variable de entorno PATH, el path de cygwin bin, por ejemplo: "C:\cygwin\bin" .
      4. Podemos generar un archivo de proceso por lotes (eclipse.bat) que contenga:

        PATH=c:\Program Files\Java\jdk1.6.0_03\bin;%PATH%
        eclipse.exe

        Cambiar "c:\Program Files\Java\jdk1.6.0_03\bin" por path que se adecue a su instalación de java.
        Con esto el eclipse arrancará cuando ejecutemos eclipse.bat
    4. ¿Por qué Eclipse?


      • Es libre
      • Es de fácil instalación
      • Es un editor de código fuente potente que nos permite manejar múltiples documentos a la vez. Es potente porque tenemos la posibilidad de agregarle funcionalidades mediante plugins. Dentro de las cuales tenemos: resaltación de sintaxis, incorporación de código repetitivo en las clases, como son los "getters" y "setters", refactorización de código y compilación, entre muchas otras.
      • Permite trabajar con diferentes lenguajes de programación.
      • Tiene diferentes perpectivas para ver el editor.
      • Permite "debuggear" de manera visual.
      • Se pueden incluir funcionalidades para testear.
      • Se puede agregar funcionalidad de SVN.
      • Se puede actualizar.
      • Se puede conseguir mucha documentación en internet.

    5. ¿Por qué no el Vi?

      El Vi puede ser potente cuando uno se aprende todos los comandos. Además debemos destacar que el Vi está en cualquier consola unix.

      Cygwin
      probablemente tenga un paquete para Vi, pero simplemente no lo busqué porque considero que es mucho más cómodo tener una herramienta visual con un interfaz amigable, sobre todo si se trabaja con múltiples documentos.
      Por lo tanto, creo que debemos usar el Vi sólo cuando no tengamos otra opción. Aprovechemos los avances tecnológicos que nos simplifican la vida.



  6. Conclusión:




    Si necesitamos montar un entorno de desarrollo para unix/linux y contamos con máquinas con sistema windows solamente, una alternativa rápida, liviana, potente y gratuita es trabajar con Eclipse + Cygwin para desarrollar nuestras aplicaciones.