
/**
 * @author Francisco Jose Aguilar Celdran
 *          Sergio Blanco Cuaresma
 *
 * Intel·ligencia Artificial II
 * Exercici de Memoria Associativa
 * Memoria: Classe que genera la memoria associativa a partir dels patrons
 *          rebuts, i que permet comprovar recuperacions de patrons sobre
 *          la memoria associativa creada.
 */

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

public class Memoria {
   int nombrePatrons;
   int longitudPatrons;
   int numIntentsRecuperacio;
   int memoriaAssociativa[][];
   boolean debug;

   public Memoria (int nombrePatrons, int longitudPatrons, int intents, boolean debug) {
      this.longitudPatrons = longitudPatrons;
      this.nombrePatrons = nombrePatrons;
      this.debug = debug;
      numIntentsRecuperacio = intents;
      memoriaAssociativa = new int[longitudPatrons][longitudPatrons];
   }

   // Genera la memoria (matriu) a partir dels patrons que ha de guardar
   public void aprendrePatrons(Collection patrons) {
      Iterator it = patrons.iterator();
      Integer vector[][] = new Integer[nombrePatrons][longitudPatrons];
      int comptador = 0;

      // Transformem la colecció en un vector
      while (it.hasNext() && (comptador < nombrePatrons)) {
         vector[comptador] = (Integer[])it.next();
         comptador++;
      }

      if (debug) {
         System.out.println();
         System.out.print("Memoria Associativa:");
      }
      
      // Algorisme de la memoria associativa
      for(int i = 0; i < longitudPatrons; i++) {
         if (debug) {
            System.out.println("");
         }
         for(int j = 0; j < longitudPatrons; j++) {
            int valor = 0;
            for (int k = 0; k < nombrePatrons; k++) {
               valor += vector[k][i].intValue() * vector[k][j].intValue();
            }

            // No es fa la divisió entre del valor entre longitudPatrons, ja que
            // es realitza al recuperar els patrons;
            memoriaAssociativa[i][j] = valor;
            if (debug) {
               System.out.print(String.valueOf(memoriaAssociativa[i][j]) + " ");
            }
         }
      }
      if (debug) {
         System.out.println();
      }
   }
   
   public void escriureMemoria(String nomFitxer) throws IOException {
      FileWriter arxiu = null;
      BufferedWriter sortida = null;

      arxiu = new FileWriter(nomFitxer);
      sortida = new BufferedWriter(arxiu);
   
      for(int i = 0; i < longitudPatrons; i++) {
         for(int j = 0; j < longitudPatrons; j++) {
            sortida.write(String.valueOf(memoriaAssociativa[i][j]) + " ");
         }
         sortida.write("\n");
      }
      sortida.close();      
   }
   
   public void llegirMemoria(String fitxerEntrada) throws IOException {
      int total_llegits= 0;
      int comptador = 0;
      FileReader arxiu = new FileReader(fitxerEntrada);
      BufferedReader entrada = new BufferedReader(arxiu);
      String linia = entrada.readLine();
      
      while ((linia != null) && (total_llegits < longitudPatrons)) {
         StringTokenizer valor = new StringTokenizer(linia);
         comptador = 0;
         while (valor.hasMoreElements()) {
            String lectura = (String)valor.nextElement();
            memoriaAssociativa[total_llegits][comptador] = Integer.parseInt(lectura);
            comptador++;
         }
         linia = entrada.readLine();
         total_llegits++;
      }
      System.out.println(total_llegits);
      if (total_llegits < longitudPatrons) {
         throw new IOException();
      }
      
   }

   // Multiplica un patro (vector) per la matriu de la memoria
   private int[] multiplicarPatroMemoria(int actual[]) {
      int resultat[] = new int[longitudPatrons];

      for(int i = 0; i < longitudPatrons; i++) {
         for(int j = 0; j < longitudPatrons; j++) {
            resultat[i] += actual[j] * memoriaAssociativa[i][j];
         }
         
         // ATENCIÓ: la divisió ha de fer-se amb el casting (float), de lo contrari
         // hi haurà problemes de precisió (e.g. -992 / 1000 = 0 !!)
         if ((float)resultat[i]/longitudPatrons >= 0) {
            resultat[i] = 1;
         } else {
            resultat[i] = -1;
         }

      }
      if (debug) {
         System.out.print("Resultat de multiplicar patroActual(");
         for (int i=0; i<actual.length;i++) {
            System.out.print(String.valueOf(actual[i]) + " ");
         }
         System.out.print(") per la matriu: ");
         for(int i = 0; i<resultat.length;i++) {
            System.out.print(String.valueOf(resultat[i]) + " ");
         }
         System.out.println("");
      }

      return resultat;
   }

   // Compara dos vectors i retorna true si son iguals.
   private boolean resultatsIdentics(int patro1[], int patro2[]) {
      boolean iguals = true;

      int comptador = 0;
      while ((comptador < patro1.length) && (iguals == true)) {
         if (patro1[comptador] != patro2[comptador]) {
            iguals = false;
         }
         comptador++;
      }
      return iguals;
   }

   // Intenta trobar un patró a la memoria (matriu) fent la multiplicació
   // com a màxim els nombre especificat a la variable "intents".
   public boolean recuperarPatro(Integer valorPatro[]) {
      boolean trobat = false;
      
      // Transformació d'array de Integers a int
      int patro[] = new int[longitudPatrons];
      for (int i = 0; i < longitudPatrons; i++) {
         patro[i] = valorPatro[i].intValue();
      }
      
      // Multipliquem patro_actual * matriu
      // 1) El primer cop patro_actual es el patró que volem recuperar
      // 2) Si patro_actual * matriu no dona el patro original, es multiplica de nou
      //    per matriu. Es a dir, patro_actual = patro_actual * matriu
      int actual[] = patro;
      int intents = 0;
      while (!trobat && intents < numIntentsRecuperacio) {
         int resultat[] = multiplicarPatroMemoria(actual);

         if (resultatsIdentics(resultat,patro)) {
            trobat = true;
            if (debug) {
               System.out.println("Patró trobat en " + String.valueOf(intents+1) + " intents!");
            }
         } else {
            if (resultatsIdentics(resultat,actual)) {
               trobat = false;
               if (debug) {
                  System.out.println("Patro no trobat, " + String.valueOf(intents+1) + " intents!");
               }
               break;
            }
         }
         actual = resultat;

         intents++;
      }
      if (debug) {
         if (intents == numIntentsRecuperacio) {
            System.out.println("Nombre màxim d'intents assolit ("+String.valueOf(intents+1)+"), patro no trobat! ");
         }
      }

      return trobat;
   }

   public Similar patroSimilar(Integer valorPatro[]) {
      Integer[] resultat = new Integer[longitudPatrons];
      int similar[] = null;
      boolean identic = false;
      
      // Transformació d'array de Integers a int
      int patro[] = new int[longitudPatrons];
      for (int i = 0; i < longitudPatrons; i++) {
         patro[i] = valorPatro[i].intValue();
      }
      
      // Multipliquem patro_actual * matriu
      // 1) El primer cop patro_actual es el patró que volem recuperar
      // 2) Si patro_actual * matriu no dona el patro original, es multiplica de nou
      //    per matriu. Es a dir, patro_actual = patro_actual * matriu
      int actual[] = patro;
      int intents = 0;
      while (intents < numIntentsRecuperacio) {
         similar = multiplicarPatroMemoria(actual);

         if (resultatsIdentics(similar,patro)) {
            if (debug) {
               System.out.println("Patró idèntic trobat en " + String.valueOf(intents+1) + " intents!");
            }
            identic = true;
            break;
         } else {
            if (resultatsIdentics(similar,actual)) {
               if (debug) {
                  System.out.println("Patro idèntic no trobat, " + String.valueOf(intents+1) + " intents!");
               }
               break;
            }
         }
         actual = similar;

         intents++;
      }
      
      if (debug) {
         if (intents == numIntentsRecuperacio) {
            System.out.println("Nombre màxim d'intents assolit ("+String.valueOf(intents+1)+"), patro idèntic no trobat! ");
         }
      }
      
      for (int i = 0; i < longitudPatrons; i++) {
         resultat[i] = new Integer(similar[i]);
      }

      return new Similar(resultat, identic);
   }
}


