sábado, 25 de mayo de 2013

Tarea – Generar Sucesores y Mostrar Puzle


Trabajo en Clases 25.05.2013


Enunciado del trabajo:

Con su grupo de trabajo, debe escribir el código java para los siguientes métodos:
generarSucesores(), Que recibe como parámetro el nodo actual del puzle y retorna la lista de nodos sucesores del nodo actual. (Ojo solo del actual)
mostrarPuzzle(), recibe el nodo actual y lo muestra en pantalla.
El programa Principal, debe permitir ingresar los datos del tablero (nodo actual), e imprimir  en tablero inicial y mostrar los sucesores generados. (los tableros generados)


Dado el enucniado anterior nuestro trabajo se concentro en modificar (limpiar) el código presentado en el trabajo correspondiente a la cátedra uno, con el fin que este solo permita:
  1. Leer desde la pantalla la configuración del tablero
  2. Generar los sucesores para la configuración de tablero ingresada 
  3. Imprimir por pantalla la configuración ingresada
  4. Imprimir por pantalla las configuraciones sucesoras correspondientes a la configuración de tablero ingresada
 Dado lo anterior, el código resultante fue el siguiente:

Clase Main

package tarea;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
/**
 *
 * @author GrupoZ
 */
public class Main {

    private final static int TOTALPOSICIONES = 9;
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {

        List<Integer> matriz = new ArrayList<Integer>();

        while(matriz.size()<9){
            System.out.println("Ingrese un número entero");
            try {
                matriz = Lectura.leerEntero(matriz);
            } catch (IOException ex) {
                System.out.println("Error Generando Matriz " + ex);
            }
            System.out.println("Largo de la Matriz " + matriz.size());
        }
        System.out.println("Matriz Lista");
        System.out.println("Esta es la matriz Inicial");
        ImprimeEstado(matriz);
        System.out.println("Sus siguientes estados son: ");
        generadorNuevosEstados(matriz);      
    }   
    public static void ImprimeEstado(List<Integer> matriz) {
        Integer[] matrizActual = matriz.toArray(new Integer[matriz.size()]);
        System.out.println(matrizActual[0] + " | " + matrizActual[1] + " | "
                        + matrizActual[2]);
        System.out.println("---------");
        System.out.println(matrizActual[3] + " | " + matrizActual[4] + " | "
                        + matrizActual[5]);
        System.out.println("---------");
        System.out.println(matrizActual[6] + " | " + matrizActual[7] + " | "
                        + matrizActual[8]);
        System.out.println(" ");
        System.out.println(" ");
    }
    public static void generadorNuevosEstados(List<Integer> matriz) {
       int vacio = getVacio(matriz);
        // mover izquierda el vacio
        if (vacio != 0 && vacio != 3 && vacio != 6) {
                intercambiaGuarda(vacio - 1, vacio, matriz);
        }

        // bajar vacio
        if (vacio != 6 && vacio != 7 && vacio != 8) {
                intercambiaGuarda(vacio + 3, vacio, matriz);
        }
        // subir vacio
        if (vacio != 0 && vacio != 1 && vacio != 2) {
                intercambiaGuarda(vacio - 3, vacio, matriz);
        }
        // cambiar vacio a derecha
        if (vacio != 2 && vacio != 5 && vacio != 8) {
                intercambiaGuarda(vacio + 1, vacio, matriz);
        }
    }
    private static int getVacio(List<Integer> matriz) {
        int vacioIndex = -1;
        Integer[] matrizActual = matriz.toArray(new Integer[matriz.size()]);
        for (int i = 0; i < TOTALPOSICIONES; i++) {
                if (matrizActual[i] == 0)
                        vacioIndex = i;
        }
        return vacioIndex;
    }
    private static void intercambiaGuarda(int d1, int d2, List<Integer> matriz) {
        Integer[] matrizActual = matriz.toArray(new Integer[matriz.size()]);
        Integer[] copia = copiaMatriz(matrizActual);
        Integer temp = copia[d1];
        copia[d1] = matrizActual[d2];
        copia[d2] = temp;

        ImprimeEstado(Arrays.asList(copia));     
    }
    private static Integer[] copiaMatriz(Integer[] state) {
        Integer[] ret = new Integer[TOTALPOSICIONES];
        for (int i = 0; i < TOTALPOSICIONES; i++) {
            ret[i] = state[i];
        }
        return ret;
}

}

Clase Lectura

package tarea;

import java.io.*;
import java.util.List;

public class Lectura {

    static BufferedReader ob=new BufferedReader(new InputStreamReader(System.in));
    static String entrada=null;

    public static List<Integer> leerEntero(List<Integer> matriz) throws IOException{

            entrada=ob.readLine();
            int num=0;
            try{
                num=Integer.parseInt(entrada);
                if (num < 9 && num > -1) {
                    if (matriz.isEmpty()) {
                        matriz.add(num);
                    }else{
                        if (matriz.indexOf(num) == -1) {
                            matriz.add(num);
                        }else {
                            System.out.println("El numero ya se encuentra en la lista");
                        }
                    }
                }
            }catch(NumberFormatException e){
                System.out.println("Valor ingresado no es entero");
            }
            return matriz;
    }
}



Dando como resultado lo siguiente:



De forma adicional el código presentado cuenta con las siguientes funcionalidades extras:
  • Valida que los datos ingresados sean solo números
  • Valida que los datos ingresados no se encuentren repetidos


Integrantes:
  • Juan Carlos Delgado
  • Marcelo Contreras
  • Marco Infante (Ausente)
  • Michael Bernales

viernes, 10 de mayo de 2013

TRABAJO DE INVESTIGACIÓN MÉTODOS DE BÚSQUEDA UTILIZANDO INTELIGENCIA ARTIFICIAL

Trabajo Cátedra 1 - Resolución 8 Puzzle
ACI710-181

1.    Resumen

Podemos decir que la resolución del puzzle de ocho casilleros para nosotros y cualquier ser humano es un simple y entrenado juego, pera al intentar crear una proceso automático que lo resuelva hemos descubierto la complejidad que conlleva algo tan simple como este juego.
En la literatura podemos encontrado un método que nos permiten abordar la complejidad de este problema, este método es conocido como la resolución de problemas por  espacio de estado, donde se define que cada  posición que pueden tomar las fichas en el tablero es un estado, entonces para llegar desde cualquier punto a la solución debemos pasar por una serie de estados este conjunto se llamara espacios de estados. Ahora no todos los estado son abordables ya que no se cumplen con las condiciones definidas para el juego por ejemplo sacar una ficha del tablero a demás determinamos que solo podemos considerar que para cumplir el objetivo de ordenar el tablero debemos realizar una serie de operaciones siempre pares. 
Hemos tomados dos métodos que nos permiten encontrar los caminos hacia la solución búsqueda en profundidad y búsqueda por anchura, ambos realizan un recorrido por todos los estado del espacio de estado hasta encontrar el ordenamiento definido como solución. En la búsqueda por profundidad se avanza de estado en estado, marcando cada estado visitado, siempre se avanza hacia un estado no marcado, internándose “profundamente” en el grafo sin repetir ningún estado. En la búsqueda por anchura se comienza por estado que es el primer estado activo. En el siguiente paso se etiquetan como visitados todos los vecinos del estado activo que no han sido etiquetados. Se continúa etiquetando todos los vecinos de los hijos de esta, es como ir buscando soluciones por cada nivel.


2.    Introducción

El ocho puzzle es una versión simplificada del juego 15 puzzle que fue inventado por Sam Loyd  en 1870 ,Loyd propuso un juego de mesa para un único jugador  que consistía en una caja cuadrada que contenía 15 piezas cuadradas numeradas del  1 al 15. La casilla vacía permitirá mover a las piezas adyacentes de forma que estas fueran ordenadas en forma creciente.
El objetivo del ocho puzzle es alcanzar un estado solución solo realizando movimientos permitidos, a esto se le puede llamar “espacio de estados” 


3.    Representación del problema en el espacio de estado

3.1.    Definición del juego

El juego de 8 Puzzle consiste en resolver un tablero de 3x3 sobre el cual se dispone 8 fichas y un espacio en blanco, el objetivo final del juego es dejar de forma ordenadas las fichas las cuales se encuentran numeradas (del 1 al 8) tal cual se muestra en la figura 1

El juego comienza con las fichas distribuidas en cualquier orden dentro del tablero como por ejemplo se muestra en la figura 2

Es necesario mencionar que el espacio en blanco puede estar en cualquier posición dentro del tablero al igual que el orden de las fichas.
Como regla del juego solo se permite que las fichas puedan ser desplazadas al espacio en blanco y siempre dentro del tablero, solo con los movimientos ARRIBA, ABAJO, IZQUIERDA, DERECHA.

3.2.    Reglas y movimientos

Este juego lo podemos representar utilizando el concepto espacio de estado, diremos que un estado es cualquier posición de las fichas dentro del tablero. A partir de este identificamos dos estados:
  • Estado inicial: Es cualquier posición de la fichas en el tablero, en nuestro juego será el punto inicial de las fichas
  • Estado final: Se especifica como una posición determinada que deseamos alcanzar.
Para alcanzar nuestro estado final debemos realizar una secuencia de movimientos, como los que vemos en la siguiente animación


A estos movimientos los llamaremos operadores:
  1. (op1)Mover Blanco --> Derecha
  2. (op2)Mover Blanco --> Izquierda
  3. (op3)Mover Blanco --> Arriba
  4. (op3)Mover Blanco --> Abajo
Debido a que nuestro tablero lo debemos representar como una matriz de 3x3,  identificamos  que existen posiciones donde no todas las operaciones se podrán ejecutar, de esta forma se tendrán que definir las operaciones permitidas, las que se mencionan en la siguiente tabla.

Diremos que i representara el eje vertical y  j el eje de horizontal vertical dentro de nuestro tablero.
Cada vez que aplicamos una operación a nuestra matriz este quedara ordenada de una forma diferente la cual la conocemos como estado, las múltiples transformaciones realizadas para llegar a nuestro objetivo lo llamaremos espacio de estado.

 

3.3.    Restricciones del modelo

Al determinar las operaciones posibles estamos en condiciones de evaluar las rutas que debemos seguir para alcanzar nuestro objetivo.


El número de diferentes ordenaciones de las nueve fichas sin repetición son precisamente, todas sus permutaciones:
9! = 362.880
En otras palabras, en el problema de las 8 fichas, que consiste en 9 constantes, tiene hasta 362.880 números de estados posibles.
Sin embargo, se debe establecer que no todos ellos son alcanzables desde el estado final descrito anteriormente:
  • Observemos el número de movimientos que debe hacer el 0  para volver a su posición inicial, una vez que empieza a moverse, desde el estado final de la figura 4a debe ser necesariamente par.
Por ejemplo, el blanco puede volver a la esquina superior izquierda en dos movimientos con[op2,op4,op3,op1 ] pero en ningún caso podrá volver a la esquina superior izquierda en 3 movimientos, ni en 5 o 7, . . . o, en general, en un número impar de movimientos.
  • En cada movimiento del blanco se altera el número de inversiones de la permutación resultante.
Por ejemplo, el estado de la figura 4b no tiene ninguna inversión. Esto es, todos los símbolos están correctamente ordenados. Después de aplicar el desplazamiento [op2] la permutación que resulta es (1,0,2,3,4,5,6,7,8) en el que hay una inversión —el 1 está ahora por delante del 0 decimos que está invertido. Si volvemos a aplicar el mismo operador [op2], resultara ahora la permutación siguiente:(1,2,0,3,4,5,6,7,8) que tiene exactamente dos inversiones (ahora el 1 y el 2 están invertidos con el 0), a la que hemos llegado con dos movimientos: [op2,op2]Si ahora aplicamos por ejemplo el operador [op4] resultara la secuencia: (1,2,5,3,4,0,6,7,8) que tiene hasta 7inversiones (1, 2, 5, 3 y 4 están invertidos con el 0 y el 5, además, con el 3, el 4) a la que hemos llegado con tres movimientos: [op2,op2,op4]
Después de analizar el ejemplo anterior podemos advertir que con un número impar de movimientos se genera un número impar de  permutaciones que a su vez tienen un número impar de inversiones, con un número par de movimientos solo se puede llegar posiciones con un número par de inversiones.
En términos generales, decimos que: la paridad del número de movimientos es siempre igual a la paridad del número de inversiones.
Examinando ahora el caso de la figura 4b, se ve que solo hay una inversión (solo el 8 está invertido con el 7) de modo que solo se podría llegar hasta esta permutación desde el estado de la figura 4a con un número impar de movimientos.
Sin embargo, como vemos el blanco 0 aparece en la figura 4b en la esquina superior izquierda, debería hacer un numero de movimientos par desde el estado de la figura 4a, puesto que también a está en la misma posición, y sabemos que el número de movimientos para volver a la misma posición es necesariamente par.
En consecuencia, estamos frente una matriz que no tiene solución y por lo tanto, el estado de la figura 4b es inalcanzable.
Como el número de permutaciones posibles, 9! se dividen en 



Con un número de inversiones par y otros tantos con inversiones impares, solo la primera mitad es alcanzable desde el estado de la figura 4a. Por lo tanto, el tamaño del espacio de estados del 8-puzle es 181.440.
Con esta información sabemos que es impracticable tratar de dibujar el un grafo de que podría representar todos los estado posible para alcanzar esta solución




 

4.    Implementación de los algoritmos de búsqueda en el espacio de estado

La búsqueda en anchura o en profundidad son procedimientos que realizan un recorrido sistemáticamente por todos los estados de un grafo. Son muy utilizados especialmente para resolver problemas de optimización, en los que se deba elegir la mejor solución entre varias posibles
Primero sabemos que vamos a ver el tablero como un matriz de 3x3 entonce debemos definir como nos referimos cada casillero 




Antes de generar las búsquedas ya sea en profundidad y anchura debemos generar un algoritmo que nos permita genera un estado valido, según la regla ya definida


Con esta regla de decisiones generaremos nuestro primer método llamado “GeneradorNuevosEstado”.

 

4.1.    Búsqueda en profundidad

En esta búsqueda se avanza de estado en estado, marcando cada estado visitado, siempre se avanza hacia un estado no marcado, internándose “profundamente” en el grafo sin repetir ningún estado. Cuando se alcanza un estado cuyos vecinos han sido marcados, se retrocede al anterior estado visitado y se avanza desde éste nuevamente. Nuestra implementación utiliza una pila LIFO (Ultimo en entrar, primero en salir) por esta razón siempre nos avanzamos desde la derecha



Los nodos han sido numerados por orden de procesamiento. Como se puede ver este tipo de ramificación por nodo es finito y ha sido capaz de encontrar la solución.

 

4.2.    Búsqueda en anchura

Al igual que en la búsqueda en profundidad se comienza en un estado (la raíz) que es el primer estado activo. En el siguiente paso se etiquetan como visitados todos los vecinos del estado activo que no han sido etiquetados. Se continúa etiquetando todos los vecinos de los hijos de E (que no hayan sido visitados aún). En este proceso nunca se visita un estado dos veces por lo que se construye un grafo sin ciclos, que será un árbol generador de la componente conexa que contiene a v. Nuestra implementación utiliza una pila FIFO (primero en entrar, último en salir) por esta razón siempre nos avanzamos desde la derecha




Los nodos han sido numerados por orden de procesamiento. Como se puede ver este tipo de ramificación por nodo es finito y ha sido capaz de encontrar la solución.

 

5.    Experimentación de los algoritmos implementados

Una vez desarrollado los algoritmos, los sometimos a prueba con los problema propuestos iniciando con E1 (2,8,3,1,6,4,7,0,5) en dos equipos, el primero realizando la búsqueda en profundidad y el segundo realizando la búsqueda en anchura, transcurridos una significativa cantidad de tiempo notamos que el programa no entregaba resultado por lo cual abortamos el proceso y lo repetimos ingresando la segunda propuesta E2 (4,8,1,3,0,2,7,6,5) obteniendo el mismo resultado.
En virtud de las experiencia anteriores, analizamos nuevamente el código e incorporamos un mecanismo de validación para detectar si la configuración ingresada efectivamente tiene resultado, esto se detecta contando la cantidad de inversiones si esta son impares estamos frente a una matriz sin resolución, dicho lo anterior detectamos que ninguna de las configuraciones planteadas es posible de ser resuelta.
Por lo anterior expuesto, decidimos someter el código a una configuración que nos permitiese obtener un resultado.

 

5.1.    Resultados de Búsqueda en profundidad



Caso 1 :

Dada esta configuración se ejecuta algoritmo detectando que no existe solución porque la cantidad de inversiones es impar.


Caso 2 :


Dada esta configuración se ejecuta algoritmo detectando que no existe solución porque la cantidad de inversiones es impar.




Caso 3:
 


Dada esta configuración se ejecuta algoritmo detectando y que no existe solución porque la cantidad de inversiones es impar.




Caso 4:
 


Dada esta configuración se ejecuta algoritmo detectando que no existe solución porque la cantidad de inversiones es impar.
 

 
Como las configuraciones sugeridas no tiene una solución posible proponemos la siguiente matriz para demostrar la valides del algoritmo.
 



Una vez ingresa esta matriz al algoritmos comprobamos que la cantidad de nodos recorridos  fue de 181346




5.2.    Resultados de Búsqueda en anchura


Como ya detectamos que con las configuraciones propuestas no es posible alcanzar una matriz a solución, proponemos realizar la búsqueda con la siguiente configuración




Al aplicar esta configuración en nuestro algoritmo de búsqueda por anchura encontramos los siguientes resultados, se recorrieron 213054 nodos antes de encontrar la solución.




6.    Conclusión


Al ejecutar ambos métodos de búsqueda fue posible observar que el método de “búsqueda  por anchura”  respondido más rápido a pesar de que se recorrió más estados para llegar al estado solución.
Con todo lo investigado, leído y practicado podemos mencionar lo siguiente respecto de: 


Búsqueda en anchura:
  • Ventajas 
    • Es completo: siempre encuentra la solución si existe.
    • Es óptimo si el costo de cada rama es constante: en Inteligencia Artificial puede que cada nodo sea un estado de un problema, y que unas ramas tengan un costo diferente a las demás. 
  • Inconveniente
    • Complejidad exponencial en espacio y tiempo.
Búsqueda en profundidad:
  • Ventajas  
    • Es completa si no existen ciclos repetidos.
    • Tiene menor complejidad en espacio que la búsqueda en anchura, porque solo mantenemos en memoria un camino simultáneamente. 
  • Inconveniente
    • No es óptima.
    • Puede no encontrar la solución aunque exista si hay caminos infinitos. Luego no es completa.
En ambos casos dependerá mucho de cuál es nuestro estado de inicio para poder decir a priori cual será el método más óptimo para la situación dada.

Descarga el codigo en Java aqui (para eclipse)
Descarga el documento en PDF aqui