> En informatique, il est peut-être utile et intéressant de déterminer la présence ou non d'un motif au sein d'un texte. Par exemple quand vous cherchez un mot précis dans un long document, si vous voulez vous entraîner pour des chiffres et des lettres...
On se donne un texte et un motif représentés en Python par des chaînes de caractères (*type str*). La question est de déterminer la présence ou l'absence de ce motif.
Le texte et le motif peuvent être représentés par des espaces, lettres, chiffres, différents types de symboles et autres signes de ponctuation.
> En langage Python, chaque caractère d'une chaîne a un indice permettant d'accéder directement à ce caractère. Les indices vont de *0* à *n -1* si *n* est le nombre de caractères de la chaîne, ou la longueur de la chaîne.
La fonction ***len*** renvoie la longueur d'une chaîne. Par exemple, si la chaîne *ch* est définie par *ch = "un exemple"* alors ***len**(ch)* a pour longueur 10.
Il faut faire attention à ne pas oublier de caractère d'espace. Nous utilisons les indices pour obtenir un caractère particulier, par exemple *ch[0]* a pour valeur le caractère "u" et *ch[9]* a pour valeur le caractère "e".
### 1. Recherche d'un caractère dans un texte
> Le cas le plus facile à aborder est celui où le motif est un simple caractère. Il s'agit d'une recherche linéaire déjà étudiée en classe de première. Pour le parcours de la chaîne, on peut utiliser une boucle ***while*** ou une boucle ***for***.
Si une des valeurs successives de la variable ***car*** est égale à la valeur de la variable ***motif*** alors la fonction renvoie ***True*** et la fonction est interrompue. Si tous les caractères du texte sont examinés et que la boucle n'est pas interrompue, la fonction renvoie ***False***.
Nous remplaçons l'instruction ***return True*** par ***return i*** si nous souhaitons obtenir la place du caractère dans le texte. La fonction renvoie alors la place du caractère s'il a été trouvé et rien sinon (donc la valeur ***None***).
Le coût de cette recherche est linéaire en la longueur de la chaîne, en effet, si *n* est la longueur de la chaine, on procède à *n* comparaisons dans le pire des cas, celui où le caractère cherché n'est pas trouvé avant la fin du texte.
Cet algorithme est implémenté dans le programme suivant, où nous pouvons remplacer l'instruction ***return j*** par ***return True*** si seulement la présence ou non du motif dans le texte nous intéresse.
> Le nombre total de comparaisons entre caractères est donc majoré par *m(n - m + 1)*. Par exemple, si *m = 5* alors on peut dire que le nombre total de comparaisons est majoré par *5n*. Le nombre *m* est compris entre *0* et *n* et on peut donc montrer que la valeur maximale du produit *m(n - m + 1)* est atteinte pour *m ≈ (n + 1) / 2*, donnant une complexité dans le pire cas en **O(n²)**.
Le texte est "***abcababcabc***" et le motif recherché est "***abcabc***". Les caractères sont comparés un par un depuis le début, de la gauche vers la droite.
| Texte | a | b | c | a | b | a | b | c | a | b | c |
| | | | | | | ↕ | ↕ | ↕ | ↕ | ↕ | ↕ |
| Motif | | | | | | a | b | c | a | b | c |
| Indice i | | | | | | 0 | 1 | 2 | 3 | 4 | 5 |
Il nous aura donc fallu dix-huit comparaisons pour trouver le motif. Si le dernier caractère du texte n'était pas un "***c***", nous aurions appris l'absence du motif après le même nombre de comparaisons.
### 3. Pré-traitement du motif
> On peut remarquer qu'il n'est pas judicieux, lorsqu'une comparaison n'est pas trouvée, de décaler le motif d'une seule unité.
>
> Par exemple, après les six premières comparaisons, on constate que les cinq premiers caractères du texte sont les cinq premiers du motif. Donc, en décalant, on va forcément être amené à comparer le début du motif avec une autre partie du motif lui même...
> Les informaticiens Morris et Pratt ont eu séparément l'idée d'effectuer un pré-traitement du motif qui permet de déterminer, à partir de quelle place la recherche doit se poursuivre, et éviter ainsi de contrôler les mêmes caractères plusieurs fois.
| Texte | a | b | c | a | b | **a** | b | c | a | b | c |
| | ↕ | ↕ | ↕ | ↕ | ↕ | ↕ | | | | | |
| Motif | a | b | c | a | b | **c** | | | | | |
| Indice i | 0 | 1 | 2 | 3 | 4 | 5 | | | | | |
Après six comparaisons de caractères, nous avons un échec. Sur les cinq caractères correspondants, les deux premiers sont identiques aux deux derniers. Donc le seul décalage qui peut être intéressant, c'est celui qui aligne ces deux premiers caractères avec les deux derniers. Cette étude, ce pré-traitement du motif, peut être réalisée avant de commencer la recherche.
Le motif est donc décalé de trois unités vers la droite.
Il n'est pas intéressant de décaler uniquement d'une unité vers la droite. Le caractère est un "a" et nous savons parfaitement où se trouve ce caractère dans le motif.
La fonction `traitement` construit un tableau de décalages. Pour chaque position `i` du motif, `s[i]` indique la position où reprendre la comparaison après un échec.
```python
def traitement(motif):
"""Construit le tableau des décalages pour Morris-Pratt."""
Ici, par rapport à recherche2, on modifie les instructions dans le cas où ***motif[i]*** est différent de ***texte[j]***
Dans le programme naïf, les indices i et j sont modifiés par ***j = j - i + 1*** pour décaler le motif d'une unité et ***i = 0*** pour recommencer les comparaisons à partir du premier caractère du motif.
Dans ce nouveau programme, j n'est pas modifié. On reste donc sur le caractère posant problème, on ne revient pas en arrière.
Pour la valeur de i, qui permet d'effectuer le bon décalage, on effectue une fois le traitement s = traitement (motif), s est alors un tableau, et s[i] est la nouvelle valeur de i.
### 4. Algorithme de Boyer Moore
> Boyer, mathématicien et informaticien, et Moore, informaticien, sont tous les deux de l'université d'Austin au Texas. L'algorithme qui porte leurs noms a été inventé en 1977.
>
> Pour observer le comportement de l'algorithme, on peut aller [ici](https://www.cs.utexas.edu/users/moore/best-ideas/string-searching/fstrpos-example.html)
Le principe général des algorithmes de recherche textuelle est de comparer un motif à certaines parties du texte, le motif se décalant vers la droite après chaque échec.
Comme cela est pratiqué avec l'algorithme de Morris et Pratt, le calcul du décalage s'obtient par un pré-traitement du motif. La différence fondamentale avec ce que l'on a vu précédemment, est que ***la comparaison entre le motif et une partie du texte se fait de droite à gauche en commençant par la fin du motif***.
En procédant ainsi, un type de décalage est particulièrement intéressant : à la première différence constatée, on décale le motif vers la droite de manière à faire coïncider le caractère du texte concerné avec un caractère du motif. Si ce n'est pas possible, alors on décale le motif après le caractère.
| Texte | a | b | c | a | b | **a** | b | c | a | b | c |
| | | | | | | ↕ | | | | | |
| Motif | a | b | c | a | b | **c** | | | | | |
| Indice i | 0 | 1 | 2 | 3 | 4 | 5 | | | | | |
La comparaison commence donc par la fin du motif, de droite à gauche. Nous avons un échec dès la première tentative : on décale donc le motif de manière à faire coïncider le caractère "a" du texte avec sa dernière occurence dans la partie du motif non utilisée dans cette comparaison, soit "abcab".
On décale donc le motif de deux unités vers la droite.
| Texte | a | b | c | a | b | **a** | b | c | a | b | c |
| | | | | | ↕ | ↕ | ↕ | ↕ | | | |
| Motif | | | a | b | c | a | b | c | | | |
| Indice i | | | 0 | 1 | 2 | 3 | 4 | 5 | | | |
Ici, on rencontre donc un échec après quatre comparaisons. Nous décalons le motif de nouveau, afin de faire coïncider le caractère "b" du texte avec sa dernière occurence dans la partie du motif non utilisée, soit "ab"
| Texte | a | b | c | a | b | **a** | b | c | a | b | c |
| | | | | | | | | | | | |
| Motif | | | | a | b | c | a | b | c | | |
| Indice i | | | | 0 | 1 | 2 | 3 | 4 | 5 | | |
On recommence donc à comparer depuis l'élément le plus à droite. C'est un échec après une comparaison. le caractère du texte est un "a" et il faut donc décaler de nouveau le motif pour faire correspondre la dernière occurence dans la partie non utilisée ici du motif.
| Texte | a | b | c | a | b | **a** | b | c | a | b | c |
| | | | | | | ↕ | ↕ | ↕ | ↕ | ↕ | ↕ |
| Motif | | | | | | a | b | c | a | b | c |
| Indice i | | | | | | 0 | 1 | 2 | 3 | 4 | 5 |
Il nous faudra ici six comparaisons finales pour affirmer la présence du motif au sein du texte. Le nombre total de comparaisons est alors de douze.
Remarque : si la dernière lettre du texte n'était pas un "c", alors nous n'aurions eu besoin que d'une seule comparaison au lieu de six pour affirmer l'absence du motif, soit sept comparaisons au total.
<arel="license"href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><imgalt="Licence Creative Commons"style="border-width:0"src="https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png"/></a><br/>Ce cours est mis à disposition selon les termes de la <arel="license"href="http://creativecommons.org/licenses/by-nc-sa/4.0/">Licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International</a>.