Chapitre 17 : Recherche textuelle.

Introduction :

Votre traitement de texte retrouve un mot dans un document de mille pages en un clin d'œil. Comment fait-il ?

1. Définition :

Le fait de faire "une recherche textuelle" signifie chercher une séquence ordonnée de caractères dans une chaîne de caractères plus grande.

2. Activité : Recherche textuelle basique

Programmer la fonction recherche_textu_basique(sequence, texte, affiche_indices=False) qui prend en paramètres deux chaînes de caractères :

  • sequence : la sous-chaîne que l’on cherche ;
  • texte : la longue chaîne dans laquelle on cherche.
  • affiche_indices est un booléen.

La fonction doit retourner True si sequence est présente dans texte et False sinon.

Exemple : recherche_textu_basique("coucou", "Bonjour, coucou!") doit retourner True.

Si la séquence n’est pas trouvée la fonction retourne False.

Si le paramètre affiche_indices est égal à True, la fonction affichera une liste de deux nombres de type int correspondant à l'indice du début et à l'indice de fin où a été trouvée la séquence dans le texte en cas de succès.

def recherche_textu_basique(sequence, texte, affiche_indices=False): """ Affiche [debut, fin] si 'sequence' est trouvée dans 'texte', sinon []. Retourne True si trouvée, False sinon. """ if len(sequence) > len(texte): # si la séquence est plus longue que le texte return ... # à compléter sequence = "NsI" texte = "nsInsiNsiNNNsiNSInsnsnisinSINSinSInSInSniSinISNiNSniSNiNSIniSNINSINSnsinISniS" print(recherche_textu_basique(sequence, texte, True))

    
>>>

3. Boyer Moore :

L'algorithme de Boyer Moore permet d'optimiser cette recherche.

Il fonctionne à l'aide d'un pré-traitement de la séquence à chercher.

Une vidéo Youtube explique cela.

Observe l'animation ci-dessous : le dictionnaire des décalages est d'abord construit, puis la séquence est comparée au texte de droite à gauche. Dès qu'une lettre ne correspond pas, le dictionnaire indique de combien de cases on peut sauter d'un coup : c'est là que l'algorithme gagne son temps.

Séquence cherchée : Comparaisons : 0

curseur
texte séquence cherchée lettres identiques lettres différentes lettre qui décide du décalage

Clique sur « Démarrer / Redémarrer » pour lancer l'animation.

4. Construction du dictionnaire :

Programmer la fonction constru_dico(sequence) qui prend en paramètre un string sequence.

Cette fonction retournera le dictionnaire des décalages (heuristique du « mauvais caractère » version Horspool) pour l’algorithme de Boyer–Moore : pour chaque caractère apparaissant dans sequence sauf la dernière position, on associe la distance entre cette position (la plus à droite possible) et la dernière case (indice len(sequence)-1).

Exemple : constru_dico("coucou") doit renvoyer {'c': 2, 'o': 1, 'u': 3}.

def constru_dico(sequence): """ Retourne le dictionnaire des décalages pour Boyer-Moore (Horspool). Règle : pour chaque caractère de sequence **avant** la dernière position, on prend sa occurrence la plus à droite et on stocke (len(sequence)-1 - index). """ dico = ... m = len(sequence) # On parcourt jusqu'à l'avant-dernière position (la dernière est exclue) for i in range(...): ... = ... return ... sequence = "coucou" print(f"La sequence {sequence} est associée au dictionnaire : {constru_dico(sequence)} .")

    
>>>

5. Algorithme :

Compléter la fonction recherche_BOYER_MOORE(sequence,texte) qui prend en paramètres deux strings.

Cette fonction retournera un booléen selon l'appartenance ou non de sequence à texte.

Par exemple, recherche_BOYER_MOORE("coucou","Bonjour, coucou!") retournera True.

# COPIER-COLLER ICI LA FONCTION constru_dico(sequence) DE L EXERCICE PRECEDENT AVANT D ECRIRE LA RECHERCHE. def recherche_BOYER_MOORE(sequence, texte): dico = ... if ...: # si sequence est plus long que le texte return False # on démarre le curseur sur l'index du dernier caractère de la séquence curseur = len(sequence) - 1 # tant que le curseur ne dépasse pas la fin du texte while ...: correspondance = True indice_sequence = ... # dernier index de sequence indice_texte = ... # position courante dans le texte # on remonte de droite à gauche tant que ça matche while correspondance and ...: if sequence[...] != texte[...]: correspondance = False else: ... ... if correspondance: return ... # sinon on décale selon le mauvais caractère if texte[curseur] in dico.keys(): curseur += ... else: curseur += ... return False sequence = "NsI" texte = "nsInsiNsiNNNsiNSInsnsnisinSINSinSInSInSniSinISNiNSniSNiNSIniSNINSINSnsinISniS" print(recherche_BOYER_MOORE(sequence, texte))

    
>>>

6. Exercice : recherche textuelle sur un roman complet.

Ce fichier contient l'ensemble du roman "Le Horla" de "Guy de Maupassant".

Le code suivant permet d'ouvrir ce genre de fichier et de mettre le contenu du roman dans la variable contenu.

import os

os.chdir("u:\\") # à modifier

with open("le_horla_guy_de_maupassant.txt", "r", encoding="utf-8") as f:
    contenu = f.read()
    

Dans un IDE Python, tester les fonctions vues précédemment sur ce roman.

7. Exercice, sujet de baccalauréat NSI session 2025

À faire dans le cahier.

Cet exercice porte sur le langage SQL, sur la programmation en Python et la recherche textuelle.

Le sujet d'une étude porte sur les papillons, la corrélation entre leur présence et celle de certaines plantes ainsi que sur la classification de nouvelles espèces.

Partie A. Corrélation avec la présence des plantes

Dans cet exercice, on pourra utiliser les clauses du langage SQL pour :

Dans le cadre de cette étude, une base de données faune_flore.db a été créée pour étudier la corrélation entre la présence d'espèces de papillons et celle de certaines plantes. Cette base de données regroupe les tables papillon, plante et zone_geographique.

La table papillon comporte les informations suivantes :

Un extrait de cette table est donné ci-après.

Table papillon

numnomConomSctaillehabitatzone
458MonarqueDanaus plexippu100Prairies3
459Citron de ProvenceGonepteryx cleopatra30Prairies1
460Paon-du-jourAglais io6Jardins6
461MachaonPapilio machaon85Forêts2
462Petite TortueAglais urticae30Prairies5
463Robert-le-DiablePolygonia c-album25Forêts4

La table plante comporte les informations suivantes :

Un extrait de la table plante est donné ci-dessous.

Table plante

numnomConomSchabitatzone
128Orchidée PhalaenopsisPhalaenopsisForêts5
129BambouBambusoideaeForêts3
130RoseRosaHaies2
131LilasSyringaHaies6
132CoquelicotPapaver rhoeasJardins4
133LavandeLavandulaCollines1

La table zone_geographique contient les informations suivantes :

Un extrait de la table zone_geographique est donné ci-après.

Table zone_geographique

numzone
1Afrique du Nord
2Amérique du Nord
3Amérique du Sud
4Asie
5Asie du Sud
6Europe
  1. Donner la définition d'une clé primaire.
  2. Expliquer pourquoi l'attribut habitat de la table papillon ne peut pas être une clé primaire.
  3. Donner le résultat obtenu suite à l'exécution de la requête suivante si on l'applique sur l'extrait de table donné :
    SELECT taille
    FROM papillon
    WHERE nomCo='Machaon'

Après avoir mesuré l'envergure de plusieurs papillons Petite Tortue, un des scientifiques de l'étude a calculé la nouvelle moyenne des tailles pour ce papillon, qui est maintenant de 50 mm.

  1. Écrire une requête qui met à jour la table papillon, suite au calcul de cette nouvelle moyenne.
  2. Écrire une requête qui affiche le nom commun de tous les papillons présents dans les prairies et dont la taille est strictement inférieure à 55 mm.
  3. Écrire le résultat obtenu suite à l'exécution de la requête suivante si on l'applique sur les extraits des tables donnés.
    SELECT nomSc
    FROM plante
    JOIN zone_geographique
    ON plante.zone = zone_geographique.num
    WHERE zone_geographique.zone = 'Asie'
  4. Écrire une requête qui affiche le nom commun des papillons et celui des plantes qui se trouvent dans le même habitat et dont la taille des papillons est strictement inférieure à 55 mm.
  5. Écrire le résultat obtenu suite à l'exécution de la requête suivante si on l'applique sur les extraits des tables donnés.
    SELECT papillon.nomCo, plante.nomCo
    FROM papillon
    JOIN zone_geographique
    ON papillon.zone = zone_geographique.num
    JOIN plante
    ON plante.zone = zone_geographique.num
    WHERE zone_geographique.zone = 'Europe'
  6. Écrire une requête qui affiche le nom commun des papillons qui se trouvent dans la même zone géographique que les coquelicots.

Partie B. Classification d'une nouvelle espèce

Les espèces de papillons sont regroupées dans une liste de dictionnaires. Pour simplifier, seuls les attributs num (l'identifiant du papillon), nomCo (son nom commun), nomSc (son nom scientifique) et taille (sa taille) seront considérés dans cette partie. Une partie de la liste papillon est donnée ci-dessous :

papillon = [
    {'num': 458, 'nomCo': 'Monarque',
     'nomSc': 'Danaus plexippu', 'taille': 100},
    {'num': 459, 'nomCo': 'Citron de Provence',
     'nomSc': 'Gonepteryx cleopatra', 'taille': 30},
    {'num': 460, 'nomCo': 'Paon-du-jour',
     'nomSc': 'Aglais io', 'taille': 6},
    {'num': 461, 'nomCo': 'Machaon',
     'nomSc': 'Papilio machaon', 'taille': 85},
    {'num': 462, 'nomCo': 'Petite Tortue',
     'nomSc': 'Aglais urticae', 'taille': 50},
    {'num': 463, 'nomCo': 'Robert-le-Diable',
     'nomSc': 'Polygonia c-album', 'taille': 25}
]

Le but de cette partie est de trier la liste des papillons par ordre croissant de taille et de classifier une nouvelle espèce photographiée.

La fonction tri_collec renvoie la liste de dictionnaires des papillons triée par ordre croissant de taille.

 1  def tri_collec(collec):
 2      """Renvoie la collection des papillons triées
 3      par ordre croissant de leur taille.
 4      Paramètre:
 5          collec : liste de dictionnaires des papillons
 6      Renvoie:
 7          liste triée par ordre croissant des tailles
 8          des papillons.
 9      """
10      for i in range(1, len(collec)):
11          pap = collec[i]
12          j = ...
13          while j > 0 and collec[j - 1]['taille'] > ...:
14              collec[j] = collec[j - 1]
15              j = ...
16          collec[j] = pap
17      return ...
  1. Recopier et compléter les lignes 12, 13, 15 et 17 de la fonction tri_collec.
  2. Nommer le tri utilisé.
  3. Indiquer, en justifiant, parmi les propositions suivantes quel est le coût en temps de ce tri, dans le pire cas, pour un tableau de taille n : linéaire, quadratique, logarithmique ou exponentiel.

L'algorithme des k plus proches voisins est utilisé pour classifier la nouvelle espèce photographiée.

  1. Expliquer brièvement le principe de cet algorithme.

Cette nouvelle espèce montre beaucoup de ressemblance avec l'espèce 'Aglais io' mais diffère par la taille et la couleur des motifs des ailes.

Pour vérifier l'hypothèse que la nouvelle espèce est l'espèce 'Aglais io' comportant une mutation génétique, une recherche naïve d'une séquence caractéristique des papillons 'Aglais io' est réalisée sur la chaîne d'ADN extraite de la nouvelle espèce. Une chaîne d'ADN est représentée en Python par une chaine de caractères. Cette recherche utilise la fonction recherche_seq(seq, chaine) qui renvoie l'indice du premier caractère de seq si la séquence seq est présente dans la chaîne d'ADN chaine et -1 sinon.

  1. Recopier et compléter les lignes 15 et 17 de la fonction recherche_seq.
     1  def recherche_seq(seq, chaine):
     2      """Renvoie l'indice du premier caractère de
     3      chaine où commence `seq` si la séquence `seq`
     4      se trouve dans la chaine de caractères chaine,
     5      -1 sinon
     6      Paramètres:
     7          seq : séquence à rechercher
     8          chaine : chaine d'ADN
     9      Renvoie:
    10          indice du premier caractère de seq dans
    11          la chaine, -1 sinon.
    12      """
    13      for i in range(len(chaine)-len(seq) + 1):
    14          j = 0
    15          while j < len(seq) and ...:
    16              j += 1
    17          if ...:
    18              return i
    19      return -1

La fonction recherche_BMH(seq, chaine), donnée ci-dessous, implémente l'algorithme de Boyer-Moore Horspool.

 1  def dico_lettres(seq):
 2      d = {}
 3      for i in range(len(seq)-1):
 4          d[seq[i]] = i
 5      return d
 6
 7  def recherche_BMH(seq, chaine):
 8      decalage = dico_lettres(seq)
 9      i = 0
10      n = len(seq)
11      while i <= len(chaine) - n:
12          j = n-1
13          while j >= 0 and chaine[i + j] == seq[j]:
14              j -= 1
15          if j == -1:
16              return i
17          else:
18              if chaine[i + n - 1] in decalage:
19                  i += n - decalage[chaine[i + n-1]] - 1
20              else:
21                  i += n
22      return -1
  1. Expliquer le principe de cet algorithme et son avantage par rapport à la fonction naïve recherche_seq.