TP1 -- L3 -- Langage et Compilation
Appication des automates finis à la recherche de motifs

A. Un premier automate

On considère l'automate fini déterministe A qui reconnait tous les mots sur l'alphabet {a,b} qui ne contiennent pas de b isolé.

Cet automate

Pour sa mise en oeuvre en Java, on utilise la classe Automate et un programme de test TestAutomate

La classe Automate

import java.util.*;

public class Automate {

    /** La fonction de transition de l'automate */
    protected int[][] _tableTransitions;
    /** État initial */
    protected int _etatInitial;
    /** Liste d’états finaux */
    protected List<Integer>  _etatsFinaux;
   
    public Automate(int Initial, Integer[] Acceptants, int[][] _tableTransitions) {
        this._tableTransitions = _tableTransitions;
        this._etatInitial = Initial;
        this._etatsFinaux = Arrays.asList(Acceptants);
    }

    /** État interne de l'automate */
    protected int etat;

    /** Positionne l'automate dans son état initial */
    protected void etatInitial() {
	etat = _etatInitial;
    }
    /** L'état courant est-il acceptant ? */
    protected boolean etatCourantEstAcceptant() {
	return _etatsFinaux.contains(etat);
    }
    /** Étant donné un caractère (donné par son code unicode), déclencher la transition */
    protected void etatSuivant(char c) {
	etat = _tableTransitions[etat][c-'a'];
    }

    public boolean reconnait(String mot) {
	etatInitial();
	char[] ch = mot.toCharArray();
	for (int i = 0; i < mot.length(); i++) { 
	    char c=ch[i];
	    System.out.println("État : "+ etat + " entrée " + String.format("%c", c));
	    etatSuivant(c);
	}
	System.out.println("État : "+ etat);
	return  etatCourantEstAcceptant();
    }
}


Test de l'automate A

public class TestAutomate {


    static Automate automate =
            new Automate(
                            0,
			    new Integer[] {0,2},
		            new int[][] {{0,1},
					 {3,2},
					 {0,2},
					 {3,3}}
			 ); 


    public static void main(String args[]) {
        if (args.length < 1) {
            System.err.println("Usage : TestAutomate mots ...");
            System.exit(1);
        }
        for (int i = 0; i < args.length; i++) {
           String arg = args[i];
	   boolean r = automate.reconnait(arg);
	   System.out.println("\"" + arg + "\"" + (r ? " est" : " n'est pas") + " reconnu\n"); 
        }
    }
}


À présent, on s'intéresse au problème de recherche de motif dans un texte. Il s'agit de retrouver toutes les occurrences d’un mot u dans un texte t.

On envisage deux algorithmes de recherche basés sur des automates finis.

  1. L'automate de localisation
  2. L'algorithme miroir

B. L'automate de localisation

Principe

B.1. Recherche du mot bababb

L'AFD qui reconnaît {a,b}*bababb est décrit par le graphe de transition suivant :

afd Loc
Définir la classe TestAutomate_bababb pour tester cet automate sur les entrées bababb, babbababb (on s'inspirera de la classe TestAutomate).

Remarque. L'automate entre dans l'état i quand le plus long suffixe de la partie actuellement lue de t qui est préfixe de u est de longueur i.

B.2. Prétraitement. À partir d'un motif u quelconque, construire un AFD qui reconnaît Σ*u

Soit m la longueur du motif u, l'AFD qui reconnaît Σ*u se calcule comme suit :

Écrire une méthode analyser qui prend en entrée un texte t et qui signale la position de toutes les ocurences du mot u dans t
Tester la recherche pour divers mots u et textes t.

Référence. C. Charras - T. Lecroq : Deterministic Finite Automaton algorithm

C. L'algorithme du miroir

Principe

On utilise une fenêtre coulisante de la taille du mot u cherché qui se déplace le long du texte.
Initialement cette fenêtre est au début du texte.
Quand la fenêtre est positionnée en i, on détermine le plus long préfixe pref de u qui soit suffixe du texte inscrit dans la fenêtre.

pref
Le texte  babbabaabababbaaabababbbaba
Fenêtre de  iài+|u|-1

Recherche avec l'automate des suffixes

Comment déterminer ce préfixe de u qui soit le plus long suffixe de la fenêtre lue ?
En cherchant de droite à gauche dans la fenêtre (i.e dans le sens inverse) les suffixes du mot miroir de u (i.e. le mot lu en sens inverse).
Concrétement, on construit l'automate des suffixes du mot miroir de u (l'AFD qui reconnaît tous les suffixes du mot miroir). On applique l'automate sur les caractères de la fenêtre lue de droite à gauche, et à chaque fois que l'automate entre dans un état final (sans que tous les caractères de la fenêtre n'aient été consommés) on mémorise la position dans une variable der.

C.1. Recherche du mot bababb

  1. Construction de l'automate des suffixes du mot miroir bbabab.
    afd des Suffixes
  2. La recherche dans le texte babbabaabababbaaabababbbaba.

    La fenêtre est de longueur m = |bababb| = 6. On déroule l'automate avec comme entrée le dernier caractère de la fenêtre, ici b qui fait passer l'automate à l'état 1 ; 1 étant final, on mémorise der = 1. Puis l'entrée a pour l'état 7 qui n'est pas final. Et b pour l'état 8 qui est final, donc der = 3. Le b suivant fait passer l'automate dans un état puits et on peut arrêter la reconnaissance.

    Le texte  babbabaabababbaaabababbbaba
    États   8710
    On décale la fenêtre de m - der, soit 3 caractères, et on recommence.
    Le texte  babbabaabababbaaabababbbaba
    États   710
    der vaut 1, on décale la fenêtre de 5.
    Le texte  babbabaabababbaaabababbbaba
    États   6543210
    bababb est trouvé, et comme der vaut 1, on décale la fenêtre de 5.
    Le texte  babbabaabababbaaabababbbaba
    États   5870
    Et on décale la fenêtre de m - 2

Définir la classe TestAutomateMiroir pour tester l'automate des suffixes de miroir(u)=bbabab.
Écrire une méthode analyser, basée sur l'algorithme du miroir, qui prend en argument un texte t et retourne la liste de toutes les positions initiales du mot u=bababb recherché dans le texte t.
Tester la méthode analyser sur le texte t=babbabaabababbaaabababbbaba.

Référence. C. Charras - T. Lecroq : Reverse Factor algorithm