Les op´erations ensemblistes sur les automates d´efinies `a la section 2.2.6 page 30 sont mises en œuvre grˆace `a des adaptateurs de curseur monodirectionnel unaires (le compl´ementaire dans Σ∗) ou binaires (intersection, union, diff´erence, diff´erence sym´etrique, concat´enation). La figure 5.2 d´ecrit les interactions de l’adaptateur unaire du compl´ementaire not_cursor et de l’adaptateur binaire d’intersection intersection_cursor avec les autres composants. Nous allons nous int´eresser `a l’intersection pour illustrer la d´emarche de cr´eation d’un tel curseur.
SoientA(Σ, Q, i, F,∆) etA′(Σ, Q′, i′, F′,∆′) deux automates dont on veut calculer l’inter-section B(Σ, Q′′, i′′, F′′,∆′′) d´efinie par :
B= (Σ, Q×Q′,(i, i′), F ×F′,∆′′)
5.2. LES OP ´ERATIONS ENSEMBLISTES 79
Q×Q′ et dont l’ensemble des transitions ∆′′ est d´efini par la fonction de transition :
δ1′′((q, q′), σ) = (δ1(q, σ), δ1′(q′, σ)).
Les ´etats terminaux sont les couples dont les ´etats sont tous deux terminaux dans les auto-mates de d´epart.
Soit xun curseur d’intersection ´evoluant sur l’automateB et encapsulant deux curseurs mo-nodirectionnels c1 et c2 respectivement sur A et A′. D’apr`es les d´efinitions pr´ec´edentes, x doit avoir le comportement suivant :
– L’´etat point´e par xest constitu´e d’une paire d’´etats des automates sous-jacents :
q′′= (q, q′).
State src() const {
return make_pair(c1.src(), c2.src()); }
– q′′ est final siq etq′ sont finaux : bool src_final() const {
return c1.src_final() && c2.src_final(); }
– forwardimpl´emente la fonction de transitionδ1′′. Une transition ´etiquet´ee parasortant de l’´etat q′′ est d´efinie si et seulement si il en existe une ´etiquet´ee par la mˆeme lettre sortant des ´etatsq etq′. Autrement dit,xpeut avancer sur une transition sic1etc2le peuvent :
bool forward(int a) {
return c1.forward(a) && c2.forward(a); }
– Une transition sortant deq′′´etiquet´ee par aest d´efinie dansB si elle est d´efinie pourq
etq′ :
bool exists(int a) const {
return c1.exists(a) && c2.exists(a); }
– L’´etat q′′ est un ´etat puits si au moins un des deux ´etatsq etq′ est un ´etat puits : bool sink() const {
return c1.sink() || c2.sink(); }
Cette interface concerne un mod`ele de curseur d’intersection simple. Elle est suffisante pour tester si un mot appartient `aB. Le niveau de fonctionnalit´es sup´erieur qu’offre le curseur d’in-tersection monodirectionnel doit permettre de parcourir les transitions d’un ´etat, δ2((q, q′)),
80 CHAPITRE 5. LES ADAPTATEURS
grˆace aux m´ethodes first_transitionetnext_transition.
D’apr`es la sixi`eme propri´et´e des curseurs de la section 4.4.2 que nous g´en´eralisons `a l’inter-section, les transitions sortant d’un ´etat sont rang´ees en s´equence dont la transition puits ((q, q′), ǫ,0) mat´erialise la position de fin :
δ2((q, q′)) = ((σ1,(p1, p′1)), ...,(σn,(pn, p′n)),(ǫ,0))
Ici 0 repr´esente l’´etat puits de l’automate intersection, c’est-`a-dire un couple d’´etats dont au moins une des deux composantes est nulle. 0 peut donc prendre les valeurs (q,0), (0, q′) ou (0,0). On construit cette s´equence en choisissant les transitions communes aux deux auto-mates :
(σi,(pi, p′i))∈δ2((q, q′))⇔(σi, pi)∈δ2(q) et (σi, p′i)∈δ2(q′)
Comme tout adaptateur, le curseur r´ealisera l’intersection des deux s´equences `a la vol´ee et de mani`ere incr´ementale en recherchant l’´el´ement commun suivant l’´el´ement courant lors de l’appel `anext_transition.
Pour des raisons d’efficacit´e, nous allons imposer que ces transitions soient tri´ees selon l’ordre croissant des lettres les ´etiquetant :
pour 1≤i, j≤n, i < j⇔σi < σj
Cette contrainte suppl´ementaire nous permet d’´ecrire des m´ethodes first_transition et next_transitionde complexit´e lin´eaire. Plus exactement, au cours d’une it´eration compl`ete de δ2((q, q′)) le nombre de transitions compar´ees est born´e par la somme des cardinaux des contextes droits des deux ´etats sources : |~c(q)|+|~c(q′)|. Sans cette propri´et´e, le temps de parcours de la s´equence intersection est quadratique.
– La m´ethode priv´ee ci-dessous factorise les parties communes de first_transition et next_transition. Son rˆole consiste `a trouver la transition suivante (σi+1,(pi+1, p′i+1)) commune aux deux curseursc1etc2positionn´es sur (q, σi, pi) et (q′, σi, p′i). Elle renvoie faux si (σi+1,(pi+1, p′ i+1)) = (ǫ,0) : bool find_next() { while(1) { if (c1.letter() < c2.letter()) {
if (!c1.next_transition()) return false; }
else
if (c2.letter() < c1.letter()) {
if (!c2.next_transition()) return false; }
else // c1.letter() == c2.letter() return true;
}
return false; }
5.2. LES OP ´ERATIONS ENSEMBLISTES 81 – La m´ethode first_transition positionne le curseur x sur les deux premi`eres
transi-tions communes des curseurs c1etc2 : bool first_transition() {
return c1.first_transition() && c2.first_transition() && find_next(); }
– Partant de l’´el´ement courant (σi,(pi, p′
i)), la m´ethode next_transition it`ere sur les deux s´equences `a la fois jusqu’`a trouver la transition commune suivante (σi+1,(pi+1, p′i+1)) : bool next_transition() {
return c1.next_transition() && c2.next_transition() && find_next(); }
– Enfin, les m´ethodesforward etfind compl`etent l’interface : void forward() { c1.forward(); c2.forward(); } bool find(int a) {
return c1.find(a) && c2.find(a); }
Remarque Nous avons impos´e le mˆeme alphabet Σ aux trois automates A, A′, B et ce dans un but de simplification de l’expos´e. En fait, cette limitation n’en est pas vraiment une car il est possible de modifier les propri´et´es des alphabets de mani`ere externe, soit en utilisant des adaptateurs de curseur filtrant les caract`eres en entr´ee ou en sortie de l’inter-face (voir la section 5.5.3 sur les automates isomorphes), soit en red´efinissant les relations d’ordre et d’´equivalence sur les ´el´ements de Σ. ´Evidemment, ces deux possibilit´es ne sont pas mutuellement exclusives. La seconde consiste `a fournir `a l’adaptateur de curseur deux nouveaux op´erateurs de comparaisons au sein de ce qu’on appelle un trait [46]. Un trait est une classe centralisant les m´ethodes impl´ementant les op´erations standards propres `a un type particulier. Le trait standardchar_traitsfournit entre autres une m´ethode de comparaison eq (equal) renvoyant vrai si les deux caract`eres pass´es en argument peuvent ˆetre consid´er´es comme ´egaux. La m´ethodelt(lower than) renvoie vrai si le premier argument est inf´erieur au deuxi`eme. Le comportement par d´efaut consiste `a utiliser les op´erateurs==et<sur les carac-t`eres mais la possibilit´e est laiss´ee `a l’utilisateur de l’adapter selon ses besoins. Par exemple, le trait suivant rend la comparaison des caract`eres insensible `a la casse («case-insensitive») :
struct insensitive_traits {
static bool eq(int x, int y) { return tolower(x) == tolower(y);
82 CHAPITRE 5. LES ADAPTATEURS
}
static bool lt(int x, int y) { return tolower(x) < tolower(y); }
};
La fonction C standard tolower convertit un caract`ere en son ´equivalent en minuscule si n´ecessaire induisant une ´equivalence entre a et A, b et B, etc. En munissant l’adaptateur d’intersection de ces op´erateurs :
intersection_cursor<fcursor1, fcursor2, insensitive_traits> c;
on rend le calcul plus«lˆache»`a condition bien sˆur que les comparaisons de lettres ´etiquetant les transitions passe par le trait. Voici le code r´eel de la m´ethode find_next introduite plus haut :
bool find_next() {
while(1) {
if (traits::lt(c1.letter(), c2.letter())) { if (!c1.next_transition()) return false; }
else
if (traits::lt(c2.letter(), c1.letter())) { if (!c2.next_transition()) return false; }
else // c1.letter() == c2.letter() return true;
}
return false; }
En lieu et place de l’op´erateur <des appels `a la m´ethode statiquelt sont effectu´es. Comme d’habitude, une utilisation usuelle d’un composant doit se traduire par du code simple `a ´ecrire et lisible, c’est pourquoi par d´efaut le type du trait utilisera le comportement standard de la classe char_traits<int> ce qui, dans ce cas pr´ecis, nous ram`ene `a la pr´ec´edente version de la fonction.
Le polymorphisme permet n’importe quelle combinaison de mani`ere imm´ediate comme d´ecrit `a la figure 5.3 o`u l’adaptateur impl´emente la diff´erence sym´etrique de deux automates
A1 etA2 d´efinie par (A1 ∪A2)\(A1∩A2). L’utilisation d’un tel objet en particulier et des adaptateurs en g´en´eral est d´ecrite `a la section 5.6 sur les algorithmes.