NSI Terminale • Programme français, lycées de Montréal

Exercices corrigés de NSI : recherche textuelle et algorithme de Boyer-Moore

Voici une série d'exercices corrigés de NSI pour la classe de Terminale, sur la recherche textuelle et l'algorithme de Boyer-Moore, au programme de l'écrit. Elle s'adresse aux élèves des lycées français, dont le Lycée Marie de France et le Collège Stanislas à Montréal, et à tout élève qui prépare l'épreuve de spécialité NSI.

Le fil de la série : la table des décalages se construit avec le MOTIF seul, une fois, avant de lire le texte ; elle se consulte avec une lettre du TEXTE, celle qui est alignée avec la fin du motif. Presque toutes les erreurs du chapitre viennent d'une de ces deux phrases oubliée : une table recalculée à chaque fenêtre, une première apparition au lieu de la dernière, une dernière lettre qui reçoit 00, un décalage lu sur la lettre du motif au lieu de celle du texte.

Conformément au programme, l'étude générale du coût de Boyer-Moore n'est jamais demandée : on compte les comparaisons sur des exemples précis et l'on en tire une conclusion, y compris le contre-exemple où la recherche naïve gagne. Les recherches naïve et dichotomique de base sont dans la série d'algorithmique et complexité.

Faites chaque exercice au complet avant d'ouvrir la correction : c'est en cherchant qu'on apprend, pas en lisant la solution.

Série autocorrigée Tape tes réponses sous chaque question : la page te dit juste ou faux avant d'ouvrir la correction. Avec un compte, chaque bonne réponse du premier coup rapporte des points.

Ce chapitre fait partie de NSI en Terminale
Avant de commencer Fiche de révision : les pièges et la méthode de ce chapitre

Avant ce chapitre

Ces notions sont supposées acquises ici. Si le premier exercice résiste, le blocage vient presque toujours de l'une d'elles, pas du chapitre lui-même.

Remonter plus loin : la chaîne complète (4 chapitres) ↓

Le chemin de remédiation, du plus ancien au plus proche. Un élève qui reprend ce chapitre de zéro le reprend dans cet ordre.

  1. 1Algorithmique : preuve, terminaison et coûtPremière
  2. 2Révision : l'algorithmique de Première, récursivité et Horspool
  3. 3Structures de données : piles, files, arbres et graphes
  4. 4Diviser pour régner, programmation dynamique et graphes

Rappel de cours

  • • FENÊTRE en ii : les caractères texte[i] à texte[i + m - 1], comparés au motif de longueur mm. Il y a n−m+1n - m + 1 fenêtres dans un texte de longueur nn.
  • • RECHERCHE NAÏVE : chaque fenêtre, de gauche à droite, glissement de 11. Au plus m(n−m+1)m(n - m + 1) comparaisons.
  • • PRÉTRAITEMENT DE HORSPOOL : pour kk de 00 à m−2m - 2, d[motif[k]] = m - 1 - k. La DERNIÈRE apparition gagne, la dernière case est EXCLUE, une lettre qui n'est pas une clé donne mm : d.get(c, m).
  • • RECHERCHE DE HORSPOOL : dans chaque fenêtre, comparer de DROITE à GAUCHE ; puis avancer de d.get(texte[i + m - 1], m), la lettre du TEXTE sous la fin du motif, après un échec comme après une occurrence.
  • • Tout décalage vaut au moins 11 : la boucle termine. Le décalage de la table est le plus grand saut qui ne peut manquer aucune occurrence.
  • • RÈGLE DU MAUVAIS CARACTÈRE (Boyer-Moore) : échec en kk sur la lettre c, pos = dernière position de chaque lettre dans le motif entier, décalage max(1, k - pos.get(c, -1)).
  • • Le prétraitement ne dépend que du motif : il se fait une fois, quel que soit le nombre ou la longueur des textes.
  • • Cas favorable : lettre lue absente du motif, saut de mm. Cas défavorable : texte répétitif qui ressemble au motif sauf en tête ; Horspool peut alors comparer plus que la recherche naïve.

Partie A : les bases (/50)

Exercice 1 : La recherche naïve, fenêtre après fenêtre

Chercher un MOTIF de longueur mm dans un TEXTE de longueur nn, c'est trouver toutes les positions ii telles que les mm caractères du texte qui commencent en ii soient exactement ceux du motif. On appelle FENÊTRE la portion du texte qui va de texte[i] à texte[i + m - 1], celle que l'on compare au motif.

La version naïve ci-dessous sépare les deux rôles : la fonction coincide compare UNE fenêtre au motif, de gauche à droite, et s'arrête au premier caractère différent ; la fonction occurrences fait glisser la fenêtre d'une case à la fois. On appelle COMPARAISON chaque évaluation de texte[i + k] == motif[k].

On étudie l'appel occurrences('balalaika', 'ala').

Python
def coincide(texte, motif, i):
    k = 0
    while k < len(motif) and texte[i + k] == motif[k]:
        k = k + 1
    return k == len(motif)

def occurrences(texte, motif):
    res = []
    for i in range(len(texte) - len(motif) + 1):
        if coincide(texte, motif, i):
            res.append(i)
    return res
  • a) Donnez nn, mm et le nombre de fenêtres examinées par la boucle for. Quelle est la dernière valeur prise par ii, et pourquoi la boucle ne va-t-elle pas jusqu'à n−1n - 1 ?
  • b) Que renvoie l'appel ? La méthode count de Python, 'balalaika'.count('ala'), renvoie 11 : expliquez l'écart.
  • c) Pour chaque fenêtre, donnez le nombre de comparaisons effectuées par coincide, puis le total.
  • d) Un élève inverse les deux tests de la boucle while et écrit texte[i + k] == motif[k] and k < len(motif). Que se passe-t-il ?
  • e) On cherche le motif 'aaaa' dans un texte formé de 5050 lettres a. Combien de fenêtres, d'occurrences et de comparaisons ? Déduisez-en une majoration du nombre de comparaisons de la recherche naïve en fonction de nn et mm.

Tape tes réponses, la page te dit juste ou faux 0/11

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) n=9n = 9, m=3m = 3, 77 fenêtres, ii va de 00 à 66
  • b) [1, 3] : deux occurrences qui se chevauchent ; count ne compte pas les chevauchements
  • c) 1+3+1+3+1+2+1=121 + 3 + 1 + 3 + 1 + 2 + 1 = 12 comparaisons
  • d) IndexError dès la première fenêtre qui coïncide entièrement
  • e) 4747 fenêtres, 4747 occurrences, 188188 comparaisons ; au plus m(n−m+1)m(n - m + 1)

a) n=9n = 9 et m=3m = 3. La variable ii parcourt range(n - m + 1), soit les valeurs 00 à 66 : 9−3+1=79 - 3 + 1 = 7 fenêtres. La dernière fenêtre commence en i=6i = 6 et se termine en 6+3−1=86 + 3 - 1 = 8, dernier indice du texte. Une fenêtre qui commencerait en i=7i = 7 réclamerait texte[9], qui n'existe pas. La borne n−m+1n - m + 1 n'est donc pas un détail : c'est elle qui garantit que chaque fenêtre tient entière dans le texte. Écrire range(len(texte)) est l'erreur la plus fréquente sur ce code, et elle se paie par une IndexError sur les dernières positions.

b) Les fenêtres qui coïncident commencent en 11 (a, l, a aux indices 11, 22, 33) et en 33 (a, l, a aux indices 33, 44, 55) : la fonction renvoie [1, 3]. Les deux occurrences se CHEVAUCHENT, elles partagent le a d'indice 33. La méthode count compte les occurrences sans chevauchement : après avoir trouvé ala en 11, elle reprend la lecture en 44 et ne voit plus rien. Notre fonction examine chaque fenêtre indépendamment des autres, donc elle trouve les deux. Les deux réponses ne répondent pas à la même question ; en génomique, c'est presque toujours la version avec chevauchement que l'on veut, et c'est celle que demandent les sujets.

c) On compte jusqu'au premier échec INCLUS : i=0i = 0, b contre a, 11 comparaison ; i=1i = 1, trois égalités, 33 ; i=2i = 2, l contre a, 11 ; i=3i = 3, trois égalités, 33 ; i=4i = 4, l contre a, 11 ; i=5i = 5, a égal à a puis i contre l, 22 ; i=6i = 6, i contre a, 11. Total : 1+3+1+3+1+2+1=121 + 3 + 1 + 3 + 1 + 2 + 1 = 12 comparaisons. Une fenêtre qui coïncide coûte mm comparaisons, une fenêtre qui échoue coûte le rang de l'échec. Le piège de comptage est d'oublier la comparaison qui échoue : elle a bien été faite, c'est même elle qui arrête la boucle.

d) L'opérateur and évalue de gauche à droite et s'arrête dès que le premier test est faux : c'est l'évaluation paresseuse. Dans l'ordre correct, quand kk atteint len(motif), le test k < len(motif) est faux et l'accès motif[k] n'est jamais tenté. Dans l'ordre inversé, après trois égalités sur la fenêtre i=1i = 1, kk vaut 33 et Python évalue d'abord texte[4] == motif[3] : motif[3] n'existe pas, et le programme s'arrête sur une IndexError. L'erreur ne se produit qu'à la première fenêtre qui coïncide entièrement : un test sur un texte qui ne contient pas le motif ne la détecte pas. Le garde-fou se place TOUJOURS avant l'accès qu'il protège.

e) n=50n = 50 et m=4m = 4 : 50−4+1=4750 - 4 + 1 = 47 fenêtres, et toutes coïncident, donc 4747 occurrences, chacune au prix de 44 comparaisons : 47×4=18847 \times 4 = 188. C'est le pire cas possible : aucune fenêtre ne coûte plus de mm comparaisons et il y a n−m+1n - m + 1 fenêtres, donc la recherche naïve fait au plus m(n−m+1)m(n - m + 1) comparaisons, de l'ordre de n×mn \times m quand le motif est court devant le texte. Pour un motif de 2020 caractères dans un texte d'un million, cela peut dépasser vingt millions de comparaisons. Tout l'enjeu de Boyer-Moore est de ne plus examiner chaque fenêtre.

Coche ici les exercices faits ou à revoir : un compte gratuit, sans mot de passe, retient tes coches d'une visite à l'autre et te dit quel chapitre attaquer ensuite. Crée ton espace, un courriel suffit.

Exercice 2 : Le prétraitement : la table des décalages de Horspool

L'algorithme de Boyer-Moore, dans sa version simplifiée due à Horspool, commence par un PRÉTRAITEMENT du motif : avant de lire le moindre caractère du texte, il range dans un dictionnaire, pour chaque lettre du motif SAUF LA DERNIÈRE, la distance entre sa dernière apparition et la fin du motif. Une lettre qui n'est pas une clé du dictionnaire donne un décalage égal à mm, ce qu'on écrit d.get(c, m).

On étudie le motif 'cascade', dont la figure donne les indices.

cascademotif0123456
Python
def table_decalages(motif):
    m = len(motif)
    d = {}
    for k in range(m - 1):
        d[motif[k]] = m - 1 - k
    return d
  • a) Calculez à la main les décalages associés à c, a, s et d. Pourquoi la lettre c, présente deux fois, reçoit-elle 33 et non 66 ?
  • b) Quel décalage obtient-on pour la lettre e ? pour la lettre z ? Combien de clés contient le dictionnaire renvoyé ?
  • c) Pourquoi la boucle s'arrête-t-elle à l'indice m−2m - 2, avec range(m - 1) ? Que se passerait-il avec range(m) ?
  • d) Un élève parcourt le motif de droite à gauche, avec for k in range(m - 2, -1, -1), sans rien changer d'autre. Quels décalages obtient-il pour c et pour a ? Combien d'occurrences sa recherche trouve-t-elle dans le texte 'la cascade', qui en contient une en position 33 ?
  • e) Combien d'affectations le prétraitement exécute-t-il ? Ce nombre change-t-il si le texte dans lequel on cherche passe de 2020 caractères à un million ?

Tape tes réponses, la page te dit juste ou faux 0/13

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) c 33, a 22, s 44, d 11 : la dernière apparition écrase la première
  • b) e et z donnent 77 ; 44 clés (c, a, s, d)
  • c) Avec range(m), e recevrait 00 et la recherche n'avancerait plus
  • d) c 66 et a 55 ; 00 occurrence trouvée au lieu de 11
  • e) m−1=6m - 1 = 6 affectations, quel que soit le texte

a) Le motif a m=7m = 7 lettres, d'indices 00 à 66. La boucle parcourt k=0k = 0 à 55 et écrit d[motif[k]] = 6 - k : k=0k = 0, c reçoit 66 ; k=1k = 1, a reçoit 55 ; k=2k = 2, s reçoit 44 ; k=3k = 3, c est ÉCRASÉ par 33 ; k=4k = 4, a est écrasé par 22 ; k=5k = 5, d reçoit 11. Table finale : c 33, a 22, s 44, d 11. Comme la boucle va de gauche à droite et qu'une affectation dans un dictionnaire remplace l'ancienne valeur, c'est la DERNIÈRE apparition qui gagne. C'est voulu : le décalage doit amener sous la lettre lue la copie de cette lettre la plus proche de la fin du motif, c'est-à-dire le plus PETIT déplacement possible. Un déplacement plus grand risque de sauter par-dessus une occurrence, et la question d) le montre.

b) La lettre e n'apparaît qu'en dernière position, que la boucle n'atteint jamais : elle n'est pas une clé, et d.get('e', 7) renvoie 77. Même chose pour z, absente du motif : 77. Le dictionnaire a 44 clés, c, a, s et d. Le piège classique est de donner 00 à e parce qu'elle est à distance 00 de la fin. Or si une fenêtre se termine par un e du texte et échoue plus à gauche, aucune AUTRE copie de e ne peut venir se placer sous ce e : le motif peut passer entièrement au-delà, d'où le décalage mm.

c) La borne range(m - 1) exclut l'indice m−1m - 1, la dernière lettre. Avec range(m), la dernière itération écrirait d['e'] = 0. Dans la recherche, une fenêtre terminée par un e du texte ferait alors avancer ii de 00 : la boucle while tournerait indéfiniment sur la même fenêtre. La terminaison de l'algorithme repose sur ce détail, tout décalage vaut au moins 11. C'est une question classique de l'écrit, et la réponse attendue tient en une phrase : « on exclut la dernière lettre pour qu'aucun décalage ne soit nul ».

d) De droite à gauche, kk prend les valeurs 5,4,3,2,1,05, 4, 3, 2, 1, 0 et la dernière écriture est celle de la PREMIÈRE apparition : c reçoit 66 à k=0k = 0, a reçoit 55 à k=1k = 1. Dans 'la cascade', n=10n = 10, n−m=3n - m = 3 et l'occurrence est en 33. La fenêtre i=0i = 0 se termine sur l'indice 66, qui porte un c. Avec la bonne table, c donne 33 : ii passe à 33 et la fenêtre coïncide. Avec la table de l'élève, c donne 66 : ii passe à 66, qui dépasse n−m=3n - m = 3, et la recherche s'arrête avec 00 occurrence. Un décalage trop GRAND n'est pas une optimisation, c'est une occurrence manquée, et le programme ne signale rien.

e) La boucle fait m−1=6m - 1 = 6 tours, une affectation par tour : 66 affectations, quel que soit le texte. Le prétraitement ne dépend que du motif ; il se fait UNE fois, avant la lecture du texte, et son coût est négligeable dès que le texte est long. C'est l'intérêt que le programme demande de mettre en avant : un petit travail sur le motif, fait une seule fois, permet ensuite des sauts à chaque fenêtre. Si l'on cherche le même motif dans dix textes, on calcule la table une fois et on la réutilise ; la recalculer dans la boucle de recherche ne fausserait pas le résultat mais gâcherait exactement ce que le prétraitement fait gagner.

lettrecasdautredécalage32417

Exercice 3 : Dérouler Horspool sur une phrase

On cherche le motif 'rose' dans le texte 'une prose sur les roses', de 2323 caractères espaces compris. La figure montre le texte, ses indices, et le motif posé sur la première fenêtre ; les espaces sont notés ␣, et la lettre du texte alignée avec la fin du motif est en bleu.

La règle : à chaque fenêtre, on compare de DROITE à GAUCHE, de motif[m - 1] vers motif[0], en s'arrêtant au premier échec. Puis, que la fenêtre ait coïncidé ou non, on avance de d.get(c, m), où c = texte[i + m - 1] est la lettre du TEXTE alignée avec la dernière case du motif. On s'arrête dès que i>n−mi > n - m.

une␣prose␣sur␣les␣rosestexterosemotif012345678910111213141516171819202122
  • a) Construisez la table des décalages du motif 'rose'.
  • b) Pour les deux premières fenêtres, i=0i = 0 puis i=4i = 4 : donnez la lettre lue, le nombre de comparaisons et le décalage appliqué.
  • c) Poursuivez la trace jusqu'au bout. Combien de fenêtres sont examinées ? Que se passe-t-il à la fenêtre i=12i = 12, dont la dernière lettre coïncide avec celle du motif ?
  • d) Quelles positions sont renvoyées ? Combien de comparaisons au total ?
  • e) Combien de fenêtres et de comparaisons la recherche naïve de l'exercice 1 effectue-t-elle sur le même appel ? Combien de caractères du texte Horspool n'a-t-il jamais lus ?

Tape tes réponses, la page te dit juste ou faux 0/15

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) r 33, o 22, s 11, toute autre lettre 44
  • b) i=0i = 0 : ␣, 11 comparaison, décalage 44 ; i=4i = 4 : s, 11 comparaison, décalage 11
  • c) 77 fenêtres : 0,4,5,9,12,16,180, 4, 5, 9, 12, 16, 18 ; en i=12i = 12, 22 comparaisons puis un saut de 44
  • d) Positions 55 et 1818, 1414 comparaisons
  • e) Naïf : 2020 fenêtres, 2727 comparaisons ; Horspool n'a jamais lu 1111 caractères

a) m=4m = 4, et la boucle ne regarde que r, o, s, d'indices 00, 11, 22 : r reçoit 4−1−0=34 - 1 - 0 = 3, o reçoit 22, s reçoit 11. La dernière lettre, e, n'apparaît nulle part ailleurs : elle n'est pas une clé et donne 44, comme l'espace, le p ou le u. Écrire la table AVANT de toucher au texte n'est pas une formalité : toute la trace ne fait plus ensuite que la consulter.

b) Fenêtre i=0i = 0 : elle couvre les indices 00 à 33, et la lettre alignée avec la fin du motif est texte[3], un espace. On compare l'espace au e du motif : échec, 11 comparaison. L'espace n'est pas une clé, on avance de 44. Fenêtre i=4i = 4 : indices 44 à 77, la lettre lue est texte[7] = s, différente de e : 11 comparaison, et d[s] = 1, donc i=5i = 5. Remarquez que le décalage se lit sur la lettre du TEXTE, s, et non sur la lettre du motif qui lui fait face : c'est l'erreur qui fausse le plus de traces.

c) i=5i = 5 : texte[5..8] = rose, quatre égalités de droite à gauche, 44 comparaisons, occurrence ; la lettre lue est e, décalage 44, i=9i = 9. i=9i = 9 : lettre lue texte[12] = r, 11 comparaison, d[r] = 3, i=12i = 12 : le r du motif vient se placer exactement sous ce r. i=12i = 12 : la lettre lue texte[15] est un e, qui coïncide avec la fin du motif ; on continue vers la gauche et texte[14] = l diffère de s : 22 comparaisons, fausse alerte. Le décalage se lit toujours sur texte[15] = e, qui n'est pas une clé : 44, i=16i = 16. i=16i = 16 : lettre lue o, 11 comparaison, d[o] = 2, i=18i = 18. i=18i = 18 : rose, 44 comparaisons, occurrence ; lettre e, i=22i = 22, qui dépasse n−m=19n - m = 19 : arrêt. Fenêtres : 0,4,5,9,12,16,180, 4, 5, 9, 12, 16, 18, soit 77.

d) Positions 55 et 1818. Comparaisons : 1+1+4+1+2+1+4=141 + 1 + 4 + 1 + 2 + 1 + 4 = 14. Contrôle rapide : deux occurrences coûtent au moins 2×4=82 \times 4 = 8 comparaisons, et chacune des cinq autres fenêtres au moins 11, donc 1313 au minimum ; la fausse alerte en ajoute une.

e) La recherche naïve examine les 23−4+1=2023 - 4 + 1 = 20 fenêtres et fait 2727 comparaisons : elle en coûte presque le double, et elle examine presque trois fois plus de fenêtres. Horspool n'a lu que les indices 3,5,6,7,8,12,14,15,18,19,20,213, 5, 6, 7, 8, 12, 14, 15, 18, 19, 20, 21 (le 77 et le 1919 deux fois) : 1212 caractères distincts, donc 23−12=1123 - 12 = 11 caractères jamais lus. C'est le trait qui distingue Boyer-Moore : il ne se contente pas de comparer moins, il SAUTE des parties du texte sans les lire. Le programme ne demande pas d'étude générale de ce coût ; il demande de savoir le constater sur un exemple, comme ici.

Exercice 4 : Code à trous : la recherche de Horspool

Voici la fonction de recherche, à compléter. Elle utilise la fonction table_decalages de l'exercice 2 et doit renvoyer la liste des positions de toutes les occurrences, dans l'ordre croissant. Les quatre trous sont numérotés de (1) à (4).

Python
def cherche_horspool(texte, motif):
    n, m = len(texte), len(motif)
    d = table_decalages(motif)
    res = []
    i = 0
    while i <= n - m:
        k = ......(1)......
        while ......(2)......:
            k = k - 1
        if ......(3)......:
            res.append(i)
        i = i + ......(4)......
    return res
  • a) Par quoi faut-il remplacer le trou (1) ? Justifiez par le sens de comparaison.
  • b) Choisissez la condition du trou (2). Que renverrait la fonction si l'on écrivait k > 0 au lieu de k >= 0 ?
  • c) Choisissez le test du trou (3), qui reconnaît une occurrence.
  • d) Choisissez l'expression du trou (4), puis expliquez pourquoi la boucle while principale termine toujours.
  • e) Que renvoie cherche_horspool('tic tac tic', 'tic') ? Combien de fenêtres examine-t-elle ? Si l'on écrit d[texte[i + m - 1]] au lieu de d.get(texte[i + m - 1], m), que se passe-t-il sur cet appel ?

Tape tes réponses, la page te dit juste ou faux 0/8

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) (1) : m - 1, la dernière case du motif
  • b) (2) : k >= 0 and texte[i + k] == motif[k] ; avec k > 0 la fonction renvoie toujours une liste vide
  • c) (3) : k == -1
  • d) (4) : d.get(texte[i + m - 1], m) ; chaque décalage vaut au moins 11
  • e) [0, 8] en 44 fenêtres ; sans get, KeyError dès la première fenêtre

a) On compare de droite à gauche : le premier caractère examiné est la dernière case du motif, d'indice m−1m - 1, d'où k = m - 1. Avec k = 0, on comparerait de gauche à droite et la boucle k = k - 1 sortirait aussitôt du motif ; avec k = m, le premier accès motif[m] lèverait une IndexError.

b) (2) est k >= 0 and texte[i + k] == motif[k] : on descend tant qu'il reste des cases à comparer ET que la case coïncide. Le test sur k vient en premier, pour que Python ne lise jamais texte[i + k] avec k = -1 : l'indice -1 désigne en Python le DERNIER caractère, donc l'ordre inverse ne lèverait aucune erreur, il ferait simplement une comparaison de trop hors de la fenêtre après chaque occurrence, silencieusement. Avec k > 0, la boucle s'arrête à k = 0 sans jamais comparer motif[0], et k ne vaut jamais -1 : le test (3) n'est jamais vrai et la fonction renvoie une liste vide, quel que soit le texte.

c) Si les mm cases ont coïncidé, la boucle a fait descendre k de m - 1 jusqu'à -1 : c'est le signe qu'aucun échec n'a eu lieu, d'où k == -1. Le test k == 0 serait vrai quand l'échec a lieu sur la première lettre, ce qui est tout le contraire d'une occurrence.

d) (4) est d.get(texte[i + m - 1], m) : la lettre lue est celle du TEXTE sous la dernière case du motif, et une lettre qui n'est pas une clé donne mm. Les deux distracteurs sont les deux erreurs de copie les plus répandues : motif[m - 1] donne toujours la même lettre, donc toujours le même saut, et texte[i + k] mélange la table de Horspool avec la lettre de l'échec, ce qui peut faire manquer une occurrence. Terminaison : la table ne contient que des valeurs m−1−km - 1 - k avec k≤m−2k \le m - 2, donc au moins 11, et la valeur par défaut mm vaut au moins 11. Chaque tour augmente ii d'au moins 11, et ii ne peut pas dépasser n−mn - m indéfiniment : la boucle fait au plus n−m+1n - m + 1 tours.

e) Table de 'tic' : t 22, i 11, et c n'est pas une clé. i=0i = 0 : tic coïncide, 33 comparaisons, lettre lue c, saut 33. i=3i = 3 : lettre lue texte[5] = a, 11 comparaison, saut 33. i=6i = 6 : lettre lue texte[8] = t, 11 comparaison, d[t] = 2, i=8i = 8. i=8i = 8 : tic coïncide, saut 33, i=11>8i = 11 > 8 : arrêt. Résultat [0, 8] en 44 fenêtres et 88 comparaisons. Avec d[texte[i + m - 1]], la première fenêtre se termine par c, qui n'est pas une clé : Python lève une KeyError dès i=0i = 0. Ce n'est pas un hasard : la lettre lue à la fin d'une occurrence est toujours la dernière lettre du motif, exclue de la table sauf si elle apparaît plus tôt. Sans get, le programme plante donc à la première occurrence de presque tous les motifs.

Python
def cherche_horspool(texte, motif):
    n, m = len(texte), len(motif)
    d = table_decalages(motif)
    res = []
    i = 0
    while i <= n - m:
        k = m - 1
        while k >= 0 and texte[i + k] == motif[k]:
            k = k - 1
        if k == -1:
            res.append(i)
        i = i + d.get(texte[i + m - 1], m)
    return res

Exercice 5 : Boyer-Moore : la règle du mauvais caractère

Dans l'algorithme de Boyer-Moore d'origine, le décalage ne se lit pas sur la dernière lettre de la fenêtre mais sur la lettre du texte où la comparaison a ÉCHOUÉ. Si l'échec a lieu à l'indice kk du motif, sur la lettre c du texte, on cherche la dernière position de c dans le motif ENTIER et l'on décale pour l'amener sous c ; si c n'est pas dans le motif, on fait passer le motif entièrement au-delà de c.

On travaille avec le motif 'tomate' (m=6m = 6) et les trois fenêtres de la figure, extraites de trois textes différents. Dans chaque fenêtre, on compare de droite à gauche.

pilotecometeviolontextetomatetomatetomatemotif(1)(2)(3)
Python
def derniere_position(motif):
    pos = {}
    for k in range(len(motif)):
        pos[motif[k]] = k
    return pos

def saut_mauvais_caractere(pos, c, k):
    return max(1, k - pos.get(c, -1))
  • a) Donnez la table renvoyée par derniere_position('tomate') : les valeurs associées à t, o, m, a et e.
  • b) Fenêtre (1), pilote : à quel indice kk du motif la comparaison échoue-t-elle, et quel est le décalage ?
  • c) Fenêtre (2), comete : l'échec a lieu sur un e. Calculez kk moins la position de e, puis le décalage renvoyé. À quoi sert le max ?
  • d) Fenêtre (3), violon : quel décalage ? Quels décalages Horspool aurait-il appliqués aux fenêtres (1), (2) et (3) ?
  • e) Montrez que lorsque l'échec a lieu dès la première comparaison, en k=m−1k = m - 1, les deux règles donnent le même décalage. L'une des deux règles saute-t-elle toujours plus loin que l'autre ?

Tape tes réponses, la page te dit juste ou faux 0/14

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) t 44, o 11, m 22, a 33, e 55
  • b) Échec en k=3k = 3 sur o : 3−1=23 - 1 = 2
  • c) 3−5=−23 - 5 = -2, décalage max⁡(1,−2)=1\max(1, -2) = 1
  • d) 5−(−1)=65 - (-1) = 6 ; Horspool : 66, 66 et 66
  • e) En k=m−1k = m - 1 les deux tables donnent le même nombre ; aucune règle ne domine l'autre

a) Cette fois la boucle parcourt TOUT le motif, dernière lettre comprise, et range l'indice de la dernière apparition : t apparaît en 00 puis en 44, donc 44 ; o 11, m 22, a 33, e 55. Ce n'est pas la table de Horspool : celle-ci range une distance à la fin et exclut la dernière case, celle-ci range une position et inclut tout. Les confondre est la première source d'erreur quand un sujet présente les deux.

b) pilote contre tomate, de droite à gauche : e et e coïncident (k=5k = 5), t et t coïncident (k=4k = 4), puis o contre a échoue en k=3k = 3. 33 comparaisons. La lettre de l'échec est o, dernière position 11 dans le motif : décalage 3−1=23 - 1 = 2. Après ce décalage, le o du motif, d'indice 11, se trouve sous le o du texte qui a provoqué l'échec : c'est le plus petit déplacement qui ne remet pas une lettre différente en face de ce o.

c) comete contre tomate : e, puis t coïncident, puis e contre a échoue en k=3k = 3. La lettre de l'échec est e, dont la dernière position dans le motif est 55, À DROITE de l'échec : k−5=−2k - 5 = -2. Un décalage négatif ferait reculer le motif et reviendrait sur une fenêtre déjà examinée, et un décalage nul ferait tourner l'algorithme sur place. Le max(1, ...) impose d'avancer d'au moins une case : décalage 11. C'est le cas que la règle du mauvais caractère gère mal, et c'est pour lui que Boyer-Moore complet ajoute une seconde règle, celle du bon suffixe, dont on prend le plus grand décalage ; le programme ne demande pas de la connaître.

d) violon : la première comparaison, n contre e, échoue en k=5k = 5, et n n'est pas dans le motif : pos.get('n', -1) vaut −1-1 et le décalage est 5−(−1)=65 - (-1) = 6, le motif passe entièrement au-delà du n. Horspool, lui, lit toujours la lettre sous la fin du motif. En (1) et (2) c'est un e, dernière lettre de tomate et absente du reste du motif : décalage 66. En (3) c'est n : 66. Sur les fenêtres (1) et (2), Horspool saute donc de 66 quand la règle du mauvais caractère n'avance que de 22 et de 11.

e) Si l'échec a lieu en k=m−1k = m - 1 sur la lettre c, alors c est différente de la dernière lettre du motif, donc sa dernière position jj dans le motif entier est aussi sa dernière position parmi les m−1m - 1 premières cases. Le mauvais caractère donne m−1−jm - 1 - j, exactement la valeur de la table de Horspool ; et si c est absente, m−1−(−1)=mm - 1 - (-1) = m, comme d.get(c, m). Les deux règles ne diffèrent donc qu'après au moins une égalité. Aucune ne domine : sur pilote, Horspool saute de 66 contre 22. Mais avec le motif 'coucou', dont la dernière lettre u apparaît aussi en 22, une fenêtre qui se termine par z puis u fait l'inverse : Horspool lit u et n'avance que de 6−1−2=36 - 1 - 2 = 3, quand le mauvais caractère, lisant z en k=4k = 4, avance de 4−(−1)=54 - (-1) = 5.

pilotecometeviolontextetomatetomatetomatemotif(1)(2)(3)

Partie B : problèmes et raisonnement (/50)

Exercice 6 : Cas favorables, cas défavorables : le sens de lecture décide

Un fichier binaire est une suite de caractères 0 et 1. On y cherche une signature de 55 caractères dans une zone de 100100 caractères tous égaux à 0, un secteur vide. On compare la recherche naïve de l'exercice 1 (gauche à droite, glissement de 11) et la recherche de Horspool de l'exercice 4 (droite à gauche, glissement lu dans la table).

Le programme précise que l'étude générale du coût de Boyer-Moore n'est pas exigible ; en revanche, compter les comparaisons sur un exemple et en tirer une conclusion est une question classique de l'écrit.

  • a) Motif '00001'. Combien de fenêtres la recherche naïve examine-t-elle, et combien de comparaisons fait-elle ?
  • b) Même motif avec Horspool : donnez le décalage appliqué à chaque fenêtre et le nombre total de comparaisons.
  • c) Motif '10000' : nombre de comparaisons de la recherche naïve, puis de Horspool. Comparez avec a) et b).
  • d) Motif '11111' : combien de fenêtres Horspool examine-t-il, et combien de comparaisons fait-il ?
  • e) Horspool est-il toujours plus rapide que la recherche naïve ? Dans quelle situation fait-il ses plus grands sauts ?

Tape tes réponses, la page te dit juste ou faux 0/10

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) 9696 fenêtres, 55 comparaisons chacune : 480480
  • b) Décalage 11 partout, 11 comparaison par fenêtre : 9696
  • c) Naïf 9696, Horspool 480480 : les rôles s'inversent
  • d) 2020 fenêtres, 2020 comparaisons
  • e) Non ; les grands sauts viennent d'une lettre lue absente du motif, ou présente loin de sa fin

a) n=100n = 100, m=5m = 5 : 100−5+1=96100 - 5 + 1 = 96 fenêtres. De gauche à droite, les quatre 0 du motif coïncident avec des 0 du texte et seul le 1 final échoue : 55 comparaisons par fenêtre, soit 96×5=48096 \times 5 = 480. C'est le pire cas de la recherche naïve : chaque fenêtre ressemble au motif sur toute sa longueur sauf la dernière case, et l'algorithme ne le découvre qu'en dernier.

b) Table de '00001' : les quatre premières cases sont des 0, la dernière apparition est en 33, donc 0 donne 5−1−3=15 - 1 - 3 = 1 ; le 1 n'est qu'en dernière case et n'est pas une clé. À chaque fenêtre, Horspool compare D'ABORD la dernière case : un 0 du texte contre le 1 du motif, échec immédiat, 11 comparaison. La lettre lue est un 0, décalage 11. Horspool examine donc les 9696 mêmes fenêtres que la recherche naïve, mais pour 9696 comparaisons au lieu de 480480. Ici le gain ne vient pas des sauts, qui valent 11 : il vient du SENS de lecture, qui tombe tout de suite sur la case qui diffère.

c) Motif '10000'. Recherche naïve : le 1 initial contre un 0 échoue d'emblée, 11 comparaison par fenêtre, 9696 au total. Horspool : table 1 donne 44, 0 donne 11 (dernière apparition hors fin en 33). De droite à gauche, les quatre 0 coïncident et seul le 1 échoue : 55 comparaisons, puis la lettre lue, un 0, donne un saut de 11. Total 96×5=48096 \times 5 = 480. Les rôles se sont exactement inversés par rapport à a) et b) : chaque algorithme est rapide quand la case qui diffère est la première qu'il lit, et lent quand c'est la dernière. Sur ce motif, Horspool fait cinq fois plus de comparaisons que la méthode naïve.

d) Motif '11111' : la table ne contient que 1, avec le décalage 11. Chaque fenêtre se termine par un 0, absent du motif : 11 comparaison et un saut de 55. Les fenêtres commencent en 0,5,10,…,950, 5, 10, \ldots, 95, soit 2020 fenêtres et 2020 comparaisons, quand la recherche naïve en ferait 9696. Voilà le cas favorable : la lettre lue n'est pas dans le motif, et le motif passe d'un bloc au-delà d'elle. On n'a lu qu'un caractère sur cinq.

e) Non. Sur le motif '10000', Horspool fait 480480 comparaisons contre 9696 : aucune affirmation du type « Boyer-Moore est toujours plus rapide » ne résiste à ce contre-exemple, et un correcteur l'attend. Ce qui est vrai : dans les textes usuels, où la lettre lue en fin de fenêtre est souvent absente du motif ou n'y figure que loin de la fin, les sauts sont grands et Horspool examine une petite fraction des fenêtres. Les cas défavorables demandent un texte très répétitif et un motif qui lui ressemble presque partout ; ils sont rares dans un roman, beaucoup moins dans un fichier binaire ou dans une séquence d'ADN.

Exercice 7 : Le saut ne manque aucune occurrence : le prouver, puis casser l'algorithme

Un algorithme de recherche qui va vite mais manque une occurrence est faux. On vérifie ici que la table de Horspool ne fait jamais sauter par-dessus une occurrence, puis on casse l'algorithme de deux façons.

Texte : 'une belle ficelle', n=17n = 17. Motif : 'elle', m=4m = 4. La figure montre le texte et le motif posé sur la première fenêtre.

une␣belle␣ficelletexteellemotif012345678910111213141516
  • a) Donnez la table des décalages de 'elle', puis le nombre de fenêtres que la recherche de Horspool examine.
  • b) Quelles positions sont trouvées, et avec combien de comparaisons ?
  • c) Variante A : un élève décale toujours de mm, « pour aller plus vite ». Combien d'occurrences trouve-t-il ? Quelle est la première occurrence qu'il manque ?
  • d) Variante B : un élève garde la PREMIÈRE apparition de chaque lettre, ce qui donne l : 22. Combien d'occurrences trouve-t-il, et laquelle manque-t-il ?
  • e) On revient à la vraie table. La fenêtre en ii se termine sur la lettre c du texte et l'on avance de s=s = d.get(c, m). Pourquoi aucune position i+ti + t, avec 0<t<s0 < t < s, ne peut-elle être une occurrence ?

Tape tes réponses, la page te dit juste ou faux 0/11

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) e 33, l 11 ; 66 fenêtres : 0,4,5,8,12,130, 4, 5, 8, 12, 13
  • b) Positions 55 et 1313, 1212 comparaisons
  • c) 00 occurrence : les fenêtres 0,4,8,120, 4, 8, 12 sautent 55 et 1313
  • d) 11 occurrence, en 1313 : celle en 55 est manquée
  • e) Il faudrait que c apparaisse dans le motif plus à droite que sa dernière apparition : contradiction

a) Les trois premières cases sont e, l, l : e donne 4−1−0=34 - 1 - 0 = 3, l donne 3−1=23 - 1 = 2 puis est ÉCRASÉ par 3−2=13 - 2 = 1. Table : e 33, l 11. Trace : i=0i = 0, lettre lue texte[3], un espace, saut 44 ; i=4i = 4, lettre l, saut 11 ; i=5i = 5, occurrence, lettre e, saut 33 ; i=8i = 8, lettre i, saut 44 ; i=12i = 12, lettre l, saut 11 ; i=13i = 13, occurrence, i=16>13i = 16 > 13 : arrêt. 66 fenêtres.

b) Positions 55 et 1313. Comparaisons : 1+1+4+1+1+4=121 + 1 + 4 + 1 + 1 + 4 = 12. Les deux sauts de 11 sont ceux qui sauvent les deux occurrences : à chaque fois la fenêtre se terminait sur un l, et le dernier l du motif, placé juste avant la fin, est venu se mettre dessous.

c) Variante A : fenêtres 0,4,8,120, 4, 8, 12, puis 16>1316 > 13. Aucune ne coïncide : 00 occurrence, pour 44 comparaisons. La fenêtre i=4i = 4 se terminait sur un l ; en sautant de 44, on passe de 'bell' à 'e fi' sans jamais essayer i=5i = 5, qui était une occurrence. Un saut fixe de mm ne serait juste que si la lettre lue n'apparaissait nulle part dans le motif. L'élève a gagné du temps et perdu toutes les réponses : un sujet qui demande « l'algorithme est-il correct ? » attend précisément ce contre-exemple, avec le texte et la position manquée.

d) Variante B : i=0i = 0, saut 44 ; i=4i = 4, lettre l, saut 22 au lieu de 11, donc i=6i = 6 : l'occurrence en 55 est sautée. i=6i = 6, lettre lue un espace, saut 44 ; i=10i = 10, lettre e, qui coïncide, puis c contre l échoue, saut 33 ; i=13i = 13, occurrence ; i=16i = 16 : arrêt. Une seule occurrence, en 1313, et encore par chance. La première apparition donne un décalage trop grand dès qu'une lettre est répétée : c'est la raison pour laquelle la boucle du prétraitement va de gauche à droite et laisse la dernière écriture gagner.

e) Supposons qu'il y ait une occurrence en i+ti + t avec 0<t<s0 < t < s. Le motif posé en i+ti + t place sa case d'indice m−1−tm - 1 - t sous la position i+m−1i + m - 1 du texte, qui porte la lettre c ; une occurrence exige donc motif[m - 1 - t] = c. Or m−1−tm - 1 - t est compris entre m−sm - s et m−2m - 2 : c'est une case, hors dernière, située STRICTEMENT à droite de l'indice m−1−sm - 1 - s, qui est la dernière apparition de c parmi les m−1m - 1 premières cases (ou −1-1 si c n'y est pas, quand s=ms = m). Contradiction. Le décalage de la table est donc le plus grand saut sûr, et tout saut plus grand, comme en c) et d), peut manquer une occurrence.

Exercice 8 : Cinq affirmations à corriger

Chacune des cinq affirmations suivantes est FAUSSE. Dites pourquoi et donnez l'énoncé correct.

  • a) « L'algorithme de Boyer-Moore lit le texte de droite à gauche. »
  • b) « La table des décalages se recalcule à chaque fenêtre, puisque la lettre lue change. »
  • c) « La recherche de Horspool fait toujours moins de comparaisons que la recherche naïve. »
  • d) « Dès qu'une fenêtre coïncide avec le motif, la recherche est terminée. »
  • e) « Plus le motif est long, plus la recherche de Horspool est lente. » On considérera un texte de 12 00012\,000 caractères dont aucun n'apparaît dans le motif, avec un motif de 44 puis de 1212 caractères.

Tape tes réponses, la page te dit juste ou faux 0/6

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) Le motif glisse de gauche à droite ; seule la comparaison dans une fenêtre va de droite à gauche
  • b) La table ne dépend que du motif : calculée une fois, seulement consultée ensuite
  • c) Contre-exemple : 480480 contre 9696 comparaisons (exercice 6)
  • d) On enregistre la position et l'on continue : on veut toutes les occurrences
  • e) 3 0003\,000 fenêtres pour m=4m = 4, 1 0001\,000 pour m=12m = 12 : un motif long saute plus loin

a) Le motif part du début du texte, en i=0i = 0, et glisse vers la droite : les positions testées sont croissantes, et les occurrences sont renvoyées dans l'ordre croissant. Ce qui va de droite à gauche, c'est la comparaison À L'INTÉRIEUR d'une fenêtre : k part de m - 1 et descend. Énoncé correct : « Boyer-Moore fait glisser le motif de gauche à droite et, dans chaque fenêtre, compare les caractères de droite à gauche. » La confusion fait écrire des traces qui commencent par la fin du texte : toute la question est perdue.

b) La table est calculée par table_decalages(motif), qui ne reçoit même pas le texte en paramètre : elle ne peut pas en dépendre. Elle est construite UNE fois, avant la boucle de recherche. Ce qui change à chaque fenêtre, c'est la CLÉ avec laquelle on la consulte, la lettre texte[i + m - 1]. Énoncé correct : « le prétraitement ne dépend que du motif ; on le fait une fois, puis chaque fenêtre ne coûte qu'une consultation du dictionnaire. » C'est exactement l'intérêt du prétraitement que le programme demande de mettre en avant.

c) Sur un secteur de 100100 zéros, le motif '10000' coûte 480480 comparaisons à Horspool et 9696 à la recherche naïve : de droite à gauche, les quatre 0 coïncident avant que le 1 échoue, alors que la recherche naïve bute sur le 1 dès la première case. Énoncé correct : « Horspool fait souvent beaucoup moins de comparaisons, surtout quand les lettres lues sont absentes du motif, mais il existe des textes où il en fait plus que la recherche naïve. » Un seul contre-exemple suffit à réfuter un « toujours ».

d) La fonction doit renvoyer la liste de TOUTES les positions. Après une occurrence, on enregistre ii puis on avance du décalage de la lettre lue, exactement comme après un échec, et l'on continue jusqu'à i>n−mi > n - m. S'arrêter à la première occurrence est une autre fonction, utile parfois (« le motif est-il présent ? »), mais ce n'est pas celle qu'on demande. Attention aussi à ne pas sauter de mm après une occurrence : deux occurrences peuvent se chevaucher, comme ala dans balalaika.

e) Si aucun caractère du texte n'est dans le motif, chaque fenêtre coûte 11 comparaison et fait sauter de mm : les fenêtres commencent en 0,m,2m,…0, m, 2m, \ldots Pour m=4m = 4 : 12 0004=3 000\frac{12\,000}{4} = 3\,000 fenêtres ; pour m=12m = 12 : 12 00012=1 000\frac{12\,000}{12} = 1\,000 fenêtres, trois fois moins. Énoncé correct : « avec Boyer-Moore, un motif plus long permet des sauts plus longs ; dans les cas favorables, la recherche est d'autant plus rapide que le motif est long. » C'est le contraire exact de l'intuition, et du comportement de la recherche naïve, dont le pire cas croît avec mm.

Exercice 9 : Problème : les sites de coupure d'une enzyme dans l'ADN

Une molécule d'ADN s'écrit comme un texte sur l'alphabet de quatre lettres A, C, G et T. L'enzyme de restriction EcoRI reconnaît la séquence GAATTC et coupe le brin entre le G et le premier A. Repérer les sites de coupure revient donc à chercher toutes les occurrences du motif 'GAATTC' dans la séquence.

On étudie le fragment 'TTGAATCCGAATTCAGGAATTCTA', de 2424 bases, représenté sur la figure avec le motif posé sur la première fenêtre. On utilise la fonction cherche_horspool de l'exercice 4.

TTGAATCCGAATTCAGGAATTCTAADNGAATTCmotif01234567891011121314151617181920212223
  • a) Construisez la table des décalages du motif. Quel décalage donne la lettre C, et pourquoi ?
  • b) Déroulez la recherche. Combien de fenêtres sont examinées ? Combien de comparaisons coûte la fenêtre i=1i = 1, et que contient-elle ?
  • c) Donnez les positions des deux sites et le nombre total de comparaisons. La recherche naïve en fait 3434 : quel pourcentage des comparaisons Horspool économise-t-il ici ?
  • d) Sur une longue séquence où les quatre bases sont également fréquentes, quel est le décalage moyen, en faisant la moyenne des décalages des quatre lettres possibles en fin de fenêtre ? Pourquoi est-il bien plus petit que dans un texte en français ?
  • e) EcoRI coupe entre le G et le A de chaque site. Donnez les longueurs des trois fragments obtenus, de gauche à droite.

Tape tes réponses, la page te dit juste ou faux 0/14

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) G 55, A 33, T 11 ; C n'est qu'en dernière case : 66
  • b) 77 fenêtres : 0,1,7,8,14,15,160, 1, 7, 8, 14, 15, 16 ; en i=1i = 1, 33 comparaisons sur TGAATC, un site incomplet
  • c) Sites en 88 et 1616, 1919 comparaisons, environ 44 %44\ \% d'économie
  • d) 5+3+1+64=3,75\frac{5 + 3 + 1 + 6}{4} = 3{,}75 : toutes les lettres sont dans le motif
  • e) 99, 88 et 77 bases

a) Parmi les cinq premières cases G, A, A, T, T : G donne 6−1−0=56 - 1 - 0 = 5, A donne d'abord 44 puis, à sa dernière apparition en 22, 33 ; T donne 22 puis, en 44, 11. Table : G 55, A 33, T 11. La lettre C n'apparaît qu'en dernière case, exclue de la table : elle donne m=6m = 6. Une fenêtre qui se termine par un C, qu'elle coïncide ou non, fait donc sauter le motif tout entier.

b) i=0i = 0 : lettre lue T (indice 55) contre C, 11 comparaison, saut 11. i=1i = 1 : la fenêtre TGAATC se termine par C, qui coïncide ; puis T contre T coïncide ; puis A contre T échoue : 33 comparaisons. Cette fenêtre contient GAATC, un site auquel il manque un T : ce n'est pas un site EcoRI, et l'algorithme le rejette en trois comparaisons. Lettre lue C, saut 66, i=7i = 7. i=7i = 7 : T, 11 comparaison, saut 11. i=8i = 8 : GAATTC, 66 comparaisons, site trouvé, saut 66. i=14i = 14 : T, saut 11. i=15i = 15 : T, saut 11. i=16i = 16 : site, 66 comparaisons, saut 66, i=22>18i = 22 > 18 : arrêt. 77 fenêtres.

c) Sites en 88 et 1616. Comparaisons : 1+3+1+6+1+1+6=191 + 3 + 1 + 6 + 1 + 1 + 6 = 19. La recherche naïve, sur ses 1919 fenêtres, en fait 3434 : l'économie est de 34−19=1534 - 19 = 15 comparaisons, soit 1534≈44 %\frac{15}{34} \approx 44\ \%. C'est un gain réel mais modeste, et la question suivante dit pourquoi.

d) Les décalages possibles sont 55 pour G, 33 pour A, 11 pour T et 66 pour C. Si les quatre lettres sont également fréquentes en fin de fenêtre, le saut moyen vaut 5+3+1+64=3,75\frac{5 + 3 + 1 + 6}{4} = 3{,}75, loin du maximum 66. Sur un alphabet de quatre lettres, toutes les lettres ou presque figurent dans le motif, et la lettre lue fait rarement sauter tout le motif. Dans un texte en français, avec une trentaine de caractères et un motif d'une dizaine de lettres distinctes, la lettre lue est le plus souvent absente du motif, et le saut vaut mm. C'est pourquoi, en bio-informatique, on préfère des variantes qui lisent plusieurs lettres à la fois pour décider du saut ; l'idée du prétraitement reste la même.

e) Le premier site commence en 88 : la coupure passe entre l'indice 88 (le G) et l'indice 99. Premier fragment : indices 00 à 88, 99 bases. Le second site commence en 1616 : coupure entre 1616 et 1717. Deuxième fragment : indices 99 à 1616, 88 bases. Troisième : indices 1717 à 2323, 77 bases. Contrôle : 9+8+7=249 + 8 + 7 = 24, la longueur du fragment. Chaque site est coupé juste APRÈS sa position, puisque la coupure suit le G initial : confondre la position d'un site avec celle de la coupure décale tous les fragments d'une base.

Exercice 10 : Problème : chercher un mot dans une fable

Une bibliothèque numérique veut repérer des mots dans les fables de La Fontaine. On travaille sur les deux premiers vers du Corbeau et le Renard, stockés tels quels, majuscules, accents et ponctuation compris :

vers = 'Maître Corbeau, sur un arbre perché, Tenait en son bec un fromage.'

Cette chaîne compte 6666 caractères. On y cherche le motif 'fromage' avec la fonction cherche_horspool de l'exercice 4.

  • a) Construisez la table des décalages de 'fromage'. Donnez les décalages de r et de a, le nombre de clés, et le décalage de la lettre e.
  • b) La trace examine les fenêtres i=0,7,14,21,28,35,42,46,53,58i = 0, 7, 14, 21, 28, 35, 42, 46, 53, 58. Justifiez les sauts de 4242 à 4646 et de 5353 à 5858. La fenêtre i=28i = 28 se termine sur le é de perché : pourquoi saute-t-elle de 77 ?
  • c) Horspool fait 1818 comparaisons et trouve le mot en 5858. Combien de fenêtres la recherche naïve examine-t-elle, et combien de comparaisons fait-elle ? On remarquera que le vers ne contient qu'un seul f.
  • d) Horspool a lu 1717 caractères distincts du vers. Combien n'en a-t-il jamais lu ?
  • e) On cherche maintenant 'renard' dans le vers suivant, 'Maître Renard, par l'odeur alléché,'. Combien d'occurrences la fonction trouve-t-elle ? Que faut-il changer pour trouver le mot ?

Tape tes réponses, la page te dit juste ou faux 0/12

a)
b)
c)
d)
e)
Voir la correction

Réponses

  • a) f 66, r 55, o 44, m 33, a 22, g 11 : 66 clés ; e donne 77
  • b) Lettre lue o : 44 ; lettre lue r : 55 ; é n'est pas e, donc 77
  • c) Naïf : 6060 fenêtres, 6666 comparaisons ; Horspool : 1010 fenêtres, 1818 comparaisons
  • d) 66−17=4966 - 17 = 49 caractères jamais lus
  • e) 00 occurrence (R majuscule) ; passer texte et motif en minuscules

a) m=7m = 7 et les six premières lettres f, r, o, m, a, g sont toutes distinctes : f 66, r 55, o 44, m 33, a 22, g 11, soit 66 clés. La lettre e n'est qu'en dernière case : elle n'est pas une clé et donne 77. Un motif dont les lettres sont toutes distinctes a la table la plus simple qui soit, chaque lettre y recevant sa distance à la fin.

b) La fenêtre i=42i = 42 se termine en 4848, sur le o de son : d[o] = 4, donc i=46i = 46, ce qui place le o du motif (indice 22) sous ce o. La fenêtre i=53i = 53 se termine en 5959, sur le r de fromage : d[r] = 5, donc i=58i = 58, et le r du motif vient sous ce r ; la fenêtre 5858 coïncide. La fenêtre i=28i = 28 se termine en 3434, sur le é de perché. Pour Python, é et e sont deux caractères DIFFÉRENTS : é n'est pas une clé, la comparaison é contre e échoue, et le saut vaut 77. C'est ici sans conséquence, mais c'est la même raison qui fera échouer la question e).

c) La recherche naïve examine les 66−7+1=6066 - 7 + 1 = 60 fenêtres. Dans chacune, elle compare d'abord la première lettre au f du motif ; le seul f du vers est celui de fromage, en 5858. Les 5959 autres fenêtres coûtent donc 11 comparaison chacune, et la fenêtre 5858 en coûte 77 : 59+7=6659 + 7 = 66 comparaisons, contre 1818 pour Horspool, qui n'a examiné que 1010 fenêtres. Sur un texte en langue naturelle, où la lettre lue en fin de fenêtre est rarement dans le motif, Horspool saute presque toujours de mm.

d) 66−17=4966 - 17 = 49 caractères n'ont jamais été lus, près des trois quarts du vers. C'est ce qu'on appelle un comportement sous-linéaire dans les cas favorables : l'algorithme conclut sans même regarder la plus grande partie du texte, ce qu'aucune méthode qui lit caractère par caractère ne peut faire. Le programme n'exige pas de démontrer ce coût, mais il faut savoir le constater sur une trace comme celle-ci.

e) La fonction renvoie une liste vide, 00 occurrence : le vers écrit Renard avec un R majuscule, et 'R' est différent de 'r'. L'algorithme n'est pas en cause, il compare des caractères, pas des mots. La correction se fait AVANT la recherche : on cherche le motif en minuscules dans vers.lower(), qui donne 'maître renard, ...' et fait apparaître l'occurrence en 77. Les accents posent le même problème : chercher 'alleche' ne trouve pas alléché. Une bibliothèque numérique normalise donc le texte, minuscules et souvent accents retirés, une seule fois, exactement comme on prétraite le motif une seule fois.

Chapitre précédent Diviser pour régner, programmation dynamique et graphes

© Ahmed Squalli Houssaini. Série publiée sur www.letuteurscientifique.ca/exercices/tnsi-recherche-textuelle. Libre pour l'usage personnel et en classe ; sa republication ailleurs demande une autorisation écrite (mentions légales).

Voir aussi

Vous cherchez un tuteur en NSI à Montréal ?

Contactez-moi pour une première séance. Boyer-Moore fait peur sur le papier et se maîtrise en une heure : une table, une lettre lue au bon endroit, une trace tenue proprement, et les questions de l'écrit se ressemblent toutes.

Site par Studio Squalli