ajout de tous les cours et TP préparés cet été

This commit is contained in:
2026-01-17 23:10:49 +01:00
parent ed9415bc81
commit 301cf5a98f
125 changed files with 21614 additions and 542 deletions

View File

@@ -0,0 +1,187 @@
# Corrigé des exercices — Recherche textuelle
---
## 1. QCM
| Question | Réponse | Explication |
|----------|---------|-------------|
| 1 | **b.** | On parcourt au maximum tous les caractères du texte une fois : O(n). |
| 2 | **c.** | On teste les positions 0, 1, ..., n-m, soit n - m + 1 positions. |
| 3 | **b.** | Boyer-Moore compare de droite à gauche (fin du motif vers le début). |
| 4 | **b.** | Le pré-traitement permet de savoir de combien décaler après un échec. |
| 5 | **c.** | Dans le pire cas, on compare m caractères pour chacune des n-m+1 positions. |
---
## 2. Questions de cours
### Question 1
Texte : `"abracadabra"` (longueur 11), Motif : `"abra"` (longueur 4)
**a.** Nombre de positions testées : n - m + 1 = 11 - 4 + 1 = **8 positions** (indices 0 à 7)
**b.** Le motif `"abra"` est trouvé aux positions :
- **Position 0** : `abra`cadabra
- **Position 7** : abracad`abra`
### Question 2
L'algorithme de Boyer-Moore est plus efficace car :
1. **Comparaison de droite à gauche** : En cas d'échec sur un caractère qui n'existe pas dans le motif, on peut sauter tout le motif d'un coup.
2. **Sauts importants** : Plus le motif est long, plus les sauts potentiels sont grands. Par exemple, avec un motif de 10 caractères et un caractère non présent dans le motif, on peut avancer de 10 positions.
3. **Cas favorable** : Si l'alphabet est grand et le motif long, Boyer-Moore peut avoir une complexité sous-linéaire O(n/m) dans le meilleur cas.
**Exemple** : Rechercher `"ALGORITHME"` dans un texte. Si on tombe sur un `"Z"` (absent du motif), on saute directement de 10 caractères.
### Question 3
**a.** `recherche("bonjour", "jour")` renvoie **3**
Vérification : `bonjour` → à l'indice 3, on trouve `jour`.
**b.** `recherche("bonjour", "soir")` renvoie **-1**
Le motif `"soir"` n'est pas présent dans `"bonjour"`.
**c.** Modification pour renvoyer toutes les positions :
```python
def recherche_toutes(texte, motif):
n = len(texte)
m = len(motif)
positions = []
for j in range(n - m + 1):
i = 0
while i < m and texte[j + i] == motif[i]:
i = i + 1
if i == m:
positions.append(j)
return positions
```
```python
>>> recherche_toutes("abracadabra", "abra")
[0, 7]
```
---
## 3. Exercices de programmation
### Exercice 1 : Compter les occurrences
```python
def compter_occurrences(texte, motif):
"""Compte le nombre d'occurrences du motif dans le texte."""
n = len(texte)
m = len(motif)
compteur = 0
for j in range(n - m + 1):
i = 0
while i < m and texte[j + i] == motif[i]:
i = i + 1
if i == m:
compteur += 1
return compteur
```
**Tests** :
```python
>>> compter_occurrences("abracadabra", "a")
5
>>> compter_occurrences("abracadabra", "abra")
2
>>> compter_occurrences("aaaa", "aa")
3 # Occurrences aux positions 0, 1 et 2
```
### Exercice 2 : Recherche insensible à la casse
```python
def recherche_insensible(texte, motif):
"""Recherche un motif sans tenir compte des majuscules/minuscules."""
texte_min = texte.lower()
motif_min = motif.lower()
n = len(texte_min)
m = len(motif_min)
for j in range(n - m + 1):
i = 0
while i < m and texte_min[j + i] == motif_min[i]:
i = i + 1
if i == m:
return j
return -1
```
**Tests** :
```python
>>> recherche_insensible("Bonjour tout le Monde", "monde")
16
>>> recherche_insensible("PYTHON", "python")
0
>>> recherche_insensible("Hello World", "WORLD")
6
```
### Exercice 3 : Analyse de complexité
Texte : `"aaaaaaaaaa"` (10 'a'), Motif : `"aaab"` (3 'a' puis 'b')
**a.** Calcul du nombre de comparaisons :
- Positions testées : 10 - 4 + 1 = 7 positions (indices 0 à 6)
- À chaque position, on compare les 3 premiers 'a' avec succès, puis échec sur 'b'
- Soit 4 comparaisons par position
**Total : 7 × 4 = 28 comparaisons**
**b.** Ce cas est défavorable car :
- Le motif partage un long préfixe avec le texte (`"aaa"`)
- À chaque tentative, on effectue presque toutes les comparaisons avant l'échec
- On ne décale que d'une position à chaque fois
- C'est le pire cas : beaucoup de comparaisons pour peu d'avancement
Ce type de motif (caractères répétés suivis d'un caractère différent) maximise le nombre de comparaisons.
### Exercice 4 : Table des dernières occurrences
Pour le motif `"EXEMPLE"` (indices 0 à 6) :
| Caractère | Dernière position |
|-----------|-------------------|
| E | 6 |
| X | 1 |
| M | 3 |
| P | 4 |
| L | 5 |
**Note** : Le caractère 'E' apparaît aux positions 0 et 6. On garde la dernière occurrence (6).
**Construction en Python** :
```python
def table_occurrences(motif):
table = {}
for i in range(len(motif)):
table[motif[i]] = i
return table
>>> table_occurrences("EXEMPLE")
{'E': 6, 'X': 1, 'M': 3, 'P': 4, 'L': 5}
```
**Utilisation** : Si on rencontre un caractère 'X' lors d'un échec, on sait que la dernière occurrence de 'X' dans le motif est à la position 1. On peut donc calculer le décalage optimal.
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="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 <a rel="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>.

View File

@@ -0,0 +1,448 @@
"""
Corrigé du TP Détecteur de Plagiat
"""
import random
import string
# =============================================================================
# PARTIE 1 : Recherche naïve
# =============================================================================
def recherche_naive(texte, motif):
"""
Recherche un motif dans un texte avec l'algorithme naïf.
:param texte: (str) Le texte dans lequel chercher
:param motif: (str) Le motif à rechercher
:return: (int) Position de la première occurrence, ou -1 si non trouvé
"""
n = len(texte)
m = len(motif)
for j in range(n - m + 1):
i = 0
while i < m and texte[j + i] == motif[i]:
i += 1
if i == m:
return j
return -1
def recherche_naive_toutes(texte, motif):
"""
Trouve toutes les occurrences d'un motif dans un texte.
:param texte: (str) Le texte dans lequel chercher
:param motif: (str) Le motif à rechercher
:return: (list) Liste des positions de toutes les occurrences
"""
n = len(texte)
m = len(motif)
positions = []
for j in range(n - m + 1):
i = 0
while i < m and texte[j + i] == motif[i]:
i += 1
if i == m:
positions.append(j)
return positions
def recherche_naive_compteur(texte, motif):
"""
Recherche naïve avec compteur de comparaisons.
:return: (tuple) (position, nombre_de_comparaisons)
"""
n = len(texte)
m = len(motif)
comparaisons = 0
for j in range(n - m + 1):
i = 0
while i < m:
comparaisons += 1
if texte[j + i] != motif[i]:
break
i += 1
if i == m:
return (j, comparaisons)
return (-1, comparaisons)
# =============================================================================
# PARTIE 2 : Algorithme de Boyer-Moore
# =============================================================================
def construire_table(motif):
"""
Construit la table des dernières occurrences pour Boyer-Moore.
:param motif: (str) Le motif à analyser
:return: (dict) Dictionnaire {caractère: dernière_position}
"""
table = {}
for i in range(len(motif)):
table[motif[i]] = i
return table
def boyer_moore(texte, motif):
"""
Recherche un motif avec l'algorithme de Boyer-Moore (règle du mauvais caractère).
:param texte: (str) Le texte dans lequel chercher
:param motif: (str) Le motif à rechercher
:return: (int) Position de la première occurrence, ou -1 si non trouvé
"""
n = len(texte)
m = len(motif)
if m == 0:
return 0
# Pré-traitement : table des dernières occurrences
table = construire_table(motif)
# Recherche
s = 0 # Décalage du motif par rapport au texte
while s <= n - m:
j = m - 1 # On commence par la fin du motif
# Comparaison de droite à gauche
while j >= 0 and motif[j] == texte[s + j]:
j -= 1
if j < 0:
return s # Motif trouvé à la position s
else:
# Calcul du décalage
char_texte = texte[s + j]
if char_texte in table:
decalage = j - table[char_texte]
if decalage < 1:
decalage = 1
else:
decalage = j + 1
s += decalage
return -1 # Motif non trouvé
def boyer_moore_compteur(texte, motif):
"""
Boyer-Moore avec compteur de comparaisons.
:return: (tuple) (position, nombre_de_comparaisons)
"""
n = len(texte)
m = len(motif)
comparaisons = 0
if m == 0:
return (0, 0)
table = construire_table(motif)
s = 0
while s <= n - m:
j = m - 1
while j >= 0:
comparaisons += 1
if motif[j] != texte[s + j]:
break
j -= 1
if j < 0:
return (s, comparaisons)
else:
char_texte = texte[s + j]
if char_texte in table:
decalage = j - table[char_texte]
if decalage < 1:
decalage = 1
else:
decalage = j + 1
s += decalage
return (-1, comparaisons)
# =============================================================================
# PARTIE 3 : Comparaison des performances
# =============================================================================
def generer_texte(longueur, alphabet="abcdefghijklmnopqrstuvwxyz"):
"""Génère un texte aléatoire de la longueur spécifiée."""
return ''.join(random.choice(alphabet) for _ in range(longueur))
def benchmark(texte, motif):
"""Compare les performances des deux algorithmes."""
_, comparaisons_naive = recherche_naive_compteur(texte, motif)
_, comparaisons_bm = boyer_moore_compteur(texte, motif)
print(f"Texte: {len(texte)} caractères, Motif: '{motif}' ({len(motif)} car.)")
print(f" Naïf: {comparaisons_naive} comparaisons")
print(f" Boyer-Moore: {comparaisons_bm} comparaisons")
if comparaisons_naive > 0:
gain = (comparaisons_naive - comparaisons_bm) / comparaisons_naive * 100
print(f" Gain: {gain:.1f}%")
print()
# =============================================================================
# PARTIE 4 : Détecteur de plagiat
# =============================================================================
class Document:
"""Représente un document textuel."""
def __init__(self, titre, contenu):
"""
Initialise un document.
:param titre: (str) Titre du document
:param contenu: (str) Contenu textuel
"""
self.titre = titre
self.contenu = contenu
def normaliser(self):
"""
Normalise le contenu : minuscules, suppression de la ponctuation.
:return: (str) Contenu normalisé
"""
# Convertir en minuscules
texte = self.contenu.lower()
# Supprimer la ponctuation
texte_normalise = ""
for car in texte:
if car.isalnum() or car.isspace():
texte_normalise += car
# Remplacer les espaces multiples par un seul espace
while " " in texte_normalise:
texte_normalise = texte_normalise.replace(" ", " ")
return texte_normalise.strip()
def __repr__(self):
return f"Document('{self.titre}', {len(self.contenu)} caractères)"
class DetecteurPlagiat:
"""Détecte les passages plagiés entre documents."""
def __init__(self, taille_minimum=20):
"""
Initialise le détecteur.
:param taille_minimum: (int) Taille minimale d'un passage plagié
"""
self.documents = []
self.taille_minimum = taille_minimum
def ajouter_document(self, document):
"""Ajoute un document à la base."""
self.documents.append(document)
def rechercher_plagiat(self, document_suspect):
"""
Recherche des passages plagiés dans un document.
:param document_suspect: (Document) Document à analyser
:return: (list) Liste des plagiats détectés
"""
resultats = []
texte_suspect = document_suspect.normaliser()
for doc_reference in self.documents:
texte_reference = doc_reference.normaliser()
# Extraire les passages du document de référence
passages = self.extraire_passages(texte_reference, self.taille_minimum)
for passage in passages:
position = boyer_moore(texte_suspect, passage)
if position != -1:
# Vérifier si ce plagiat n'a pas déjà été détecté
deja_detecte = False
for r in resultats:
if r['source'] == doc_reference.titre:
# Vérifier le chevauchement
if abs(r['position'] - position) < self.taille_minimum:
deja_detecte = True
break
if not deja_detecte:
resultats.append({
'source': doc_reference.titre,
'passage': passage,
'position': position
})
return resultats
def extraire_passages(self, texte, taille):
"""
Extrait tous les passages de taille donnée d'un texte.
:param texte: (str) Le texte source
:param taille: (int) Taille des passages
:return: (list) Liste des passages
"""
passages = []
for i in range(len(texte) - taille + 1):
passage = texte[i:i + taille]
if passage not in passages: # Éviter les doublons
passages.append(passage)
return passages
def afficher_resultats(self, resultats):
"""Affiche les résultats de la détection."""
if len(resultats) == 0:
print("Aucun plagiat détecté.")
else:
print(f"{len(resultats)} plagiat(s) détecté(s) :\n")
for i, r in enumerate(resultats, 1):
print(f"Plagiat #{i}")
print(f" Source: '{r['source']}'")
print(f" Passage: '{r['passage'][:50]}...'")
print(f" Position dans le suspect: {r['position']}")
print()
# =============================================================================
# PARTIE 5 : Améliorations (Bonus)
# =============================================================================
def calculer_similarite(doc1, doc2, taille_passage=20):
"""
Calcule un score de similarité entre deux documents.
:param doc1: (Document) Premier document
:param doc2: (Document) Second document
:param taille_passage: (int) Taille des passages à comparer
:return: (float) Pourcentage de similarité
"""
texte1 = doc1.normaliser()
texte2 = doc2.normaliser()
# Extraire les passages du premier document
passages = []
for i in range(len(texte1) - taille_passage + 1):
passages.append(texte1[i:i + taille_passage])
# Compter les passages présents dans le second document
passages_communs = 0
for passage in passages:
if boyer_moore(texte2, passage) != -1:
passages_communs += 1
if len(passages) == 0:
return 0.0
return (passages_communs / len(passages)) * 100
# =============================================================================
# TESTS
# =============================================================================
if __name__ == "__main__":
print("=" * 60)
print("TEST 1 : RECHERCHE NAÏVE")
print("=" * 60)
print(f"recherche_naive('bonjour tout le monde', 'tout') = {recherche_naive('bonjour tout le monde', 'tout')}")
print(f"recherche_naive('abracadabra', 'abra') = {recherche_naive('abracadabra', 'abra')}")
print(f"recherche_naive('python', 'java') = {recherche_naive('python', 'java')}")
print(f"\nrecherche_naive_toutes('abracadabra', 'abra') = {recherche_naive_toutes('abracadabra', 'abra')}")
print(f"recherche_naive_toutes('aaaa', 'aa') = {recherche_naive_toutes('aaaa', 'aa')}")
print(f"\nrecherche_naive_compteur('abcdefghij', 'hij') = {recherche_naive_compteur('abcdefghij', 'hij')}")
print("\n" + "=" * 60)
print("TEST 2 : BOYER-MOORE")
print("=" * 60)
print(f"construire_table('EXEMPLE') = {construire_table('EXEMPLE')}")
print(f"construire_table('abracadabra') = {construire_table('abracadabra')}")
print(f"\nboyer_moore('voici un exemple de texte', 'exemple') = {boyer_moore('voici un exemple de texte', 'exemple')}")
print(f"boyer_moore('abracadabra', 'abra') = {boyer_moore('abracadabra', 'abra')}")
print(f"boyer_moore('hello world', 'xyz') = {boyer_moore('hello world', 'xyz')}")
print("\n" + "=" * 60)
print("TEST 3 : COMPARAISON DES PERFORMANCES")
print("=" * 60)
# Cas 1 : Texte aléatoire avec motif présent
texte_test = generer_texte(1000)
motif_test = texte_test[500:505] # Motif présent au milieu
benchmark(texte_test, motif_test)
# Cas 2 : Motif absent
benchmark(texte_test, "zzzzz")
# Cas 3 : Cas défavorable pour l'algorithme naïf
texte_defavorable = "a" * 100
motif_defavorable = "a" * 10 + "b"
benchmark(texte_defavorable, motif_defavorable)
print("=" * 60)
print("TEST 4 : DÉTECTEUR DE PLAGIAT")
print("=" * 60)
# Documents originaux (base de référence)
doc1 = Document("Cours Python", """
Python est un langage de programmation interprété, multi-paradigme et
multiplateformes. Il favorise la programmation impérative structurée,
fonctionnelle et orientée objet. Python est doté d'un typage dynamique fort.
""")
doc2 = Document("Cours Java", """
Java est un langage de programmation orienté objet créé par Sun Microsystems.
Il est conçu pour être portable et sécurisé. Java utilise une machine virtuelle
pour exécuter le bytecode compilé.
""")
# Document suspect
suspect = Document("Devoir élève", """
Python est un langage de programmation interprété, multi-paradigme et
multiplateformes. C'est mon langage préféré car il est simple à apprendre.
Il favorise la programmation impérative structurée.
""")
# Détection
detecteur = DetecteurPlagiat(taille_minimum=30)
detecteur.ajouter_document(doc1)
detecteur.ajouter_document(doc2)
print(f"\nDocument suspect : {suspect}")
print(f"Base de référence : {len(detecteur.documents)} documents\n")
resultats = detecteur.rechercher_plagiat(suspect)
detecteur.afficher_resultats(resultats)
print("=" * 60)
print("TEST 5 : SIMILARITÉ")
print("=" * 60)
similarite = calculer_similarite(doc1, suspect, taille_passage=20)
print(f"Similarité entre '{doc1.titre}' et '{suspect.titre}': {similarite:.1f}%")
similarite2 = calculer_similarite(doc2, suspect, taille_passage=20)
print(f"Similarité entre '{doc2.titre}' et '{suspect.titre}': {similarite2:.1f}%")

View File

@@ -0,0 +1,130 @@
# Exercices — Recherche textuelle
## 1. QCM
1. Quelle est la complexité de la recherche d'un caractère dans un texte de longueur n ?
a. O(1)
b. O(n)
c. O(n²)
2. Dans l'algorithme de recherche naïve, si le texte a une longueur n et le motif une longueur m, combien de positions de départ sont testées au maximum ?
a. n
b. n - m
c. n - m + 1
3. Quelle est la particularité de l'algorithme de Boyer-Moore par rapport à la recherche naïve ?
a. Il compare les caractères de gauche à droite
b. Il compare les caractères de droite à gauche
c. Il ne fait aucune comparaison
4. À quoi sert le pré-traitement du motif dans l'algorithme de Morris-Pratt ?
a. À trier les caractères du motif
b. À calculer les décalages optimaux après un échec
c. À compter le nombre de caractères différents
5. Dans le pire cas, quelle est la complexité de l'algorithme de recherche naïve ?
a. O(n)
b. O(m)
c. O(n × m)
---
## 2. Questions de cours
### Question 1
On considère le texte `"abracadabra"` et le motif `"abra"`.
a. Combien de positions de départ sont testées par l'algorithme naïf ?
b. À quelles positions le motif est-il trouvé ?
### Question 2
Expliquez pourquoi l'algorithme de Boyer-Moore peut être plus efficace que la recherche naïve, notamment quand le motif est long.
### Question 3
On considère le code suivant :
```python
def recherche(texte, motif):
n = len(texte)
m = len(motif)
for j in range(n - m + 1):
i = 0
while i < m and texte[j + i] == motif[i]:
i = i + 1
if i == m:
return j
return -1
```
a. Que renvoie `recherche("bonjour", "jour")` ?
b. Que renvoie `recherche("bonjour", "soir")` ?
c. Modifiez la fonction pour qu'elle renvoie **toutes** les positions où le motif apparaît (sous forme de liste).
---
## 3. Exercices de programmation
### Exercice 1 : Compter les occurrences
Écrire une fonction `compter_occurrences(texte, motif)` qui renvoie le nombre de fois où le motif apparaît dans le texte.
```python
>>> compter_occurrences("abracadabra", "a")
5
>>> compter_occurrences("abracadabra", "abra")
2
```
### Exercice 2 : Recherche insensible à la casse
Écrire une fonction `recherche_insensible(texte, motif)` qui effectue une recherche en ignorant les majuscules/minuscules.
```python
>>> recherche_insensible("Bonjour tout le Monde", "monde")
16
>>> recherche_insensible("PYTHON", "python")
0
```
### Exercice 3 : Analyse de complexité
On exécute la recherche naïve avec le texte `"aaaaaaaaaa"` (10 fois 'a') et le motif `"aaab"`.
a. Combien de comparaisons sont effectuées au total ?
b. Expliquez pourquoi ce cas est défavorable pour l'algorithme naïf.
### Exercice 4 : Table des dernières occurrences (Boyer-Moore)
Pour l'algorithme de Boyer-Moore, on construit une table indiquant la dernière position de chaque caractère dans le motif.
Pour le motif `"EXEMPLE"`, construire cette table.
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="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 <a rel="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>.

View File

@@ -6,7 +6,7 @@
> En informatique, il est peut-être utile et interessant de determiner 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 entrainer pour des chiffres et des lettres...
> 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.
@@ -27,16 +27,16 @@ def recherche(texte, motif):
for car in texte:
if car == motif:
return True
return False
return False
```
Si une des valeur 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 bouclle n'est pas interrompue, la fonction renvoie ***False***.
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***).
```python
def recherche (texte, motif):
for i in range (len(texte)):
def recherche(texte, motif):
for i in range(len(texte)):
if texte[i] == motif:
return i
```
@@ -47,7 +47,7 @@ Le coût de cette recherche est linéaire en la longueur de la chaîne, en effet
### 2. Recherche naïve d'un motif dans un texte
> On parle de recherche naïve car c'est l'une des premières idées qui peut venir à l'esprit
> On parle de recherche naïve car c'est l'une des premières idées qui peut venir à l'esprit.
- On recherche la présence du premier caractère du motif dans le texte
- Si on le trouve, on vérifie si les caractères suivants du motif coïncident avec ceux du texte
@@ -61,9 +61,9 @@ Cet algorithme est implémenté dans le programme suivant, où nous pouvons remp
def recherche(texte, motif):
n = len(texte)
m = len(motif)
for j in range(n - m +1):
for j in range(n - m + 1):
i = 0
while i < m and texte [j + i] == motif [i]:
while i < m and texte[j + i] == motif[i]:
i = i + 1
if i == m :
return j
@@ -85,7 +85,7 @@ Si *n* est la longueur du texte et *m* la longueur du motif recherché, alors la
En effet la recherche du premier caractère du motif s'arrête lorsqu'il ne reste plus assez de place dans le texte pour placer ce motif.
Dans le pire des cas, la boucle interne *while* est parcourue au plus *m* fois, pour tester chaque caractère du motif.
> 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 *m<sup>2</sup> + m = (n<sup>2</sup> + 2n) / 4* .
> 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²)**.
@@ -95,8 +95,8 @@ def recherche2(texte, motif):
n = len(texte)
i = 0
j = 0
while i < m and j < n :
if motif [i] != texte[j]:
while i < m and j < n:
if motif[i] != texte[j]:
j = j - i + 1
i = 0
@@ -135,7 +135,7 @@ Après six comparaisons de caractères, nous avons un échec (lettres en gras).
| Motif | | **a** | b | c | a | b | c | | | | |
| Indice i | | 0 | 1 | 2 | 3 | 4 | 5 | | | | |
Ici, c'est un échec dès la première compraison, il faut donc à nouveau nous décaler vers la droite.
Ici, c'est un échec dès la première comparaison, il faut donc à nouveau nous décaler vers la droite.
| Indice j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| -------- | ---- | ---- | ----- | ---- | ---- | ----- | ---- | ---- | ---- | ---- | ---- |
@@ -153,7 +153,7 @@ Idem ici, on redécale.
| Motif | | | | a | b | **c** | | | | | |
| Indice i | | | | 0 | 1 | 2 | 3 | 4 | 5 | | |
Ici, echec à la troisième comparaison. On décale vers la droite.
Ici, échec à la troisième comparaison. On décale vers la droite.
| Indice j | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| -------- | ---- | ---- | ---- | ---- | ----- | ----- | ---- | ---- | ---- | ---- | ---- |
@@ -181,7 +181,7 @@ Il nous aura donc fallu dix-huit comparaisons pour trouver le motif. Si le derni
>
> 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 controler les mêmes caractères plusieurs fois.
> 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.
Reprenons le texte "***abcababcabc***" et le motif "***abcabc***"
@@ -209,7 +209,7 @@ Le motif est donc décalé de trois unités vers la droite.
Les comparaisons commencent donc au troisième caractère pour lequel nous avons un échec ici.
Il n'est pas interessant 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.
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.
@@ -224,23 +224,40 @@ Le nombre total de comparaisons est de treize au lieu de dix-huit pour le progra
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."""
m = len(motif)
s = [-1] * m # Tableau des décalages
j = -1
for i in range(1, m):
while j >= 0 and motif[j + 1] != motif[i]:
j = s[j]
if motif[j + 1] == motif[i]:
j += 1
s[i] = j
return s
```
```python
def morris_pratt(texte, motif):
m = len (motif)
m = len(motif)
n = len(texte)
i = 0
j = 0
sol = [] #tableau pour stocker les solutions
s = traitement (motif) #modification du programme naïf recherche
while i < m and j < n :
if i >= 0 and motif[i] != texte[j] :
sol = [] # Tableau pour stocker les solutions
s = traitement(motif) # Pré-traitement du motif
while i < m and j < n:
if i >= 0 and motif[i] != texte[j]:
i = s[i]
else:
i += 1
j += 1
if i >= m : #Si le motif est présent
sol.append(j-m) # La position dans le texte est ajoutée à la liste
i = 0 # Une nouvelle recherche commence après le motif trouvé
if i >= m: # Si le motif est présent
sol.append(j - m) # La position dans le texte est ajoutée à la liste
i = 0 # Une nouvelle recherche commence après le motif trouvé
return sol
```
@@ -262,9 +279,9 @@ Pour la valeur de i, qui permet d'effectuer le bon décalage, on effectue une fo
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 d'obtient par un pré-traitement du motif. La différence fondamentale avec ce que l'on a vu précédémment, 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***.
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 articulièrement intéresssant : à la premiere 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.
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.
@@ -333,7 +350,56 @@ Il nous faudra ici six comparaisons finales pour affirmer la présence du motif
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.
#### Implémentation simplifiée
Voici une implémentation de l'algorithme de Boyer-Moore utilisant uniquement la règle du mauvais caractère :
```python
def boyer_moore(texte, motif):
"""Recherche un motif dans un texte avec Boyer-Moore (règle du mauvais caractère)."""
n = len(texte)
m = len(motif)
if m == 0:
return 0
# Pré-traitement : table des dernières occurrences
derniere_occurrence = {}
for i in range(m):
derniere_occurrence[motif[i]] = i
# Recherche
s = 0 # Décalage du motif par rapport au texte
while s <= n - m:
j = m - 1 # On commence par la fin du motif
# Comparaison de droite à gauche
while j >= 0 and motif[j] == texte[s + j]:
j -= 1
if j < 0:
return s # Motif trouvé à la position s
else:
# Calcul du décalage
char_texte = texte[s + j]
if char_texte in derniere_occurrence:
decalage = j - derniere_occurrence[char_texte]
if decalage < 1:
decalage = 1
else:
decalage = j + 1
s += decalage
return -1 # Motif non trouvé
```
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="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 <a rel="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>.

View File

@@ -0,0 +1,360 @@
# TP : Détecteur de Plagiat
## Contexte
Vous travaillez pour une startup EdTech qui développe un outil anti-plagiat pour les établissements scolaires. Votre mission : implémenter le moteur de recherche textuelle qui permettra de détecter les passages copiés entre différents documents.
Les outils comme **Turnitin**, **Compilatio** ou **Plag.fr** utilisent des algorithmes similaires à ceux que vous allez implémenter.
---
## Objectifs
- Implémenter l'algorithme de recherche naïve
- Implémenter l'algorithme de Boyer-Moore simplifié
- Comparer les performances des algorithmes
- Construire un détecteur de plagiat fonctionnel
---
## Partie 1 : Recherche naïve
### 1.1. Implémentation de base
Implémenter la fonction `recherche_naive(texte, motif)` qui renvoie la première position du motif dans le texte, ou `-1` si le motif n'est pas trouvé.
```python
def recherche_naive(texte, motif):
"""
Recherche un motif dans un texte avec l'algorithme naïf.
:param texte: (str) Le texte dans lequel chercher
:param motif: (str) Le motif à rechercher
:return: (int) Position de la première occurrence, ou -1 si non trouvé
"""
# À compléter
pass
```
**Tests** :
```python
>>> recherche_naive("bonjour tout le monde", "tout")
8
>>> recherche_naive("abracadabra", "abra")
0
>>> recherche_naive("python", "java")
-1
```
### 1.2. Toutes les occurrences
Modifier votre fonction pour créer `recherche_naive_toutes(texte, motif)` qui renvoie la liste de **toutes** les positions où le motif apparaît.
```python
def recherche_naive_toutes(texte, motif):
"""
Trouve toutes les occurrences d'un motif dans un texte.
:param texte: (str) Le texte dans lequel chercher
:param motif: (str) Le motif à rechercher
:return: (list) Liste des positions de toutes les occurrences
"""
# À compléter
pass
```
**Tests** :
```python
>>> recherche_naive_toutes("abracadabra", "abra")
[0, 7]
>>> recherche_naive_toutes("aaaa", "aa")
[0, 1, 2]
>>> recherche_naive_toutes("hello", "xyz")
[]
```
### 1.3. Compteur de comparaisons
Créer une version `recherche_naive_compteur(texte, motif)` qui renvoie un tuple `(position, nb_comparaisons)` pour analyser les performances.
```python
def recherche_naive_compteur(texte, motif):
"""
Recherche naïve avec compteur de comparaisons.
:return: (tuple) (position, nombre_de_comparaisons)
"""
# À compléter
pass
```
**Test** :
```python
>>> recherche_naive_compteur("abcdefghij", "hij")
(7, 10) # Trouvé en position 7 après 10 comparaisons
```
---
## Partie 2 : Algorithme de Boyer-Moore
### 2.1. Table des dernières occurrences
L'algorithme de Boyer-Moore utilise une table indiquant la dernière position de chaque caractère dans le motif.
Implémenter `construire_table(motif)` :
```python
def construire_table(motif):
"""
Construit la table des dernières occurrences pour Boyer-Moore.
:param motif: (str) Le motif à analyser
:return: (dict) Dictionnaire {caractère: dernière_position}
"""
# À compléter
pass
```
**Tests** :
```python
>>> construire_table("EXEMPLE")
{'E': 6, 'X': 1, 'M': 3, 'P': 4, 'L': 5}
>>> construire_table("abracadabra")
{'a': 10, 'b': 8, 'r': 9, 'c': 4, 'd': 6}
```
### 2.2. Implémentation de Boyer-Moore
Implémenter `boyer_moore(texte, motif)` en utilisant la règle du "mauvais caractère" :
```python
def boyer_moore(texte, motif):
"""
Recherche un motif avec l'algorithme de Boyer-Moore (règle du mauvais caractère).
:param texte: (str) Le texte dans lequel chercher
:param motif: (str) Le motif à rechercher
:return: (int) Position de la première occurrence, ou -1 si non trouvé
"""
# À compléter
pass
```
**Rappel de l'algorithme** :
1. Comparer le motif de **droite à gauche**
2. En cas d'échec sur un caractère `c` du texte :
- Si `c` est dans le motif, décaler pour aligner `c` avec sa dernière occurrence
- Sinon, décaler le motif entièrement après `c`
**Tests** :
```python
>>> boyer_moore("voici un exemple de texte", "exemple")
9
>>> boyer_moore("abracadabra", "abra")
0
>>> boyer_moore("hello world", "xyz")
-1
```
### 2.3. Version avec compteur
Créer `boyer_moore_compteur(texte, motif)` pour comparer avec la recherche naïve.
---
## Partie 3 : Comparaison des performances
### 3.1. Générateur de texte
Créer une fonction qui génère un texte aléatoire pour les tests :
```python
import random
def generer_texte(longueur, alphabet="abcdefghijklmnopqrstuvwxyz"):
"""Génère un texte aléatoire de la longueur spécifiée."""
# À compléter
pass
```
### 3.2. Benchmark
Comparer les deux algorithmes sur différents cas :
```python
def benchmark(texte, motif):
"""Compare les performances des deux algorithmes."""
_, comparaisons_naive = recherche_naive_compteur(texte, motif)
_, comparaisons_bm = boyer_moore_compteur(texte, motif)
print(f"Texte: {len(texte)} caractères, Motif: '{motif}' ({len(motif)} car.)")
print(f" Naïf: {comparaisons_naive} comparaisons")
print(f" Boyer-Moore: {comparaisons_bm} comparaisons")
print(f" Gain: {comparaisons_naive - comparaisons_bm} comparaisons économisées")
```
**Tests à réaliser** :
1. Texte de 1000 caractères, motif de 5 caractères présent
2. Texte de 1000 caractères, motif de 5 caractères absent
3. Cas défavorable : texte `"aaa...a"` et motif `"aaab"`
4. Cas favorable pour Boyer-Moore : alphabet riche et long motif
---
## Partie 4 : Détecteur de plagiat
### 4.1. Classe Document
Créer une classe représentant un document texte :
```python
class Document:
"""Représente un document textuel."""
def __init__(self, titre, contenu):
"""
Initialise un document.
:param titre: (str) Titre du document
:param contenu: (str) Contenu textuel
"""
# À compléter
pass
def normaliser(self):
"""
Normalise le contenu : minuscules, suppression de la ponctuation.
:return: (str) Contenu normalisé
"""
# À compléter
pass
def __repr__(self):
return f"Document('{self.titre}', {len(self.contenu)} caractères)"
```
### 4.2. Détecteur de plagiat
Créer la classe principale :
```python
class DetecteurPlagiat:
"""Détecte les passages plagiés entre documents."""
def __init__(self, taille_minimum=20):
"""
Initialise le détecteur.
:param taille_minimum: (int) Taille minimale d'un passage plagié
"""
self.documents = []
self.taille_minimum = taille_minimum
def ajouter_document(self, document):
"""Ajoute un document à la base."""
# À compléter
pass
def rechercher_plagiat(self, document_suspect):
"""
Recherche des passages plagiés dans un document.
:param document_suspect: (Document) Document à analyser
:return: (list) Liste des plagiats détectés
"""
# À compléter
pass
def extraire_passages(self, texte, taille):
"""
Extrait tous les passages de taille donnée d'un texte.
:param texte: (str) Le texte source
:param taille: (int) Taille des passages
:return: (list) Liste des passages
"""
# À compléter
pass
```
### 4.3. Test du détecteur
```python
# Documents originaux (base de référence)
doc1 = Document("Cours Python", """
Python est un langage de programmation interprété, multi-paradigme et
multiplateformes. Il favorise la programmation impérative structurée,
fonctionnelle et orientée objet.
""")
doc2 = Document("Cours Java", """
Java est un langage de programmation orienté objet créé par Sun Microsystems.
Il est conçu pour être portable et sécurisé.
""")
# Document suspect
suspect = Document("Devoir élève", """
Python est un langage de programmation interprété, multi-paradigme et
multiplateformes. C'est mon langage préféré car il est simple à apprendre.
""")
# Détection
detecteur = DetecteurPlagiat(taille_minimum=30)
detecteur.ajouter_document(doc1)
detecteur.ajouter_document(doc2)
resultats = detecteur.rechercher_plagiat(suspect)
for r in resultats:
print(r)
```
**Sortie attendue** :
```
Plagiat détecté !
Source: 'Cours Python'
Passage: 'python est un langage de programmation interprété, multi-paradigme et multiplateformes'
Position dans le suspect: 0
```
---
## Partie 5 : Améliorations (Bonus)
### 5.1. Score de similarité
Ajouter une méthode `calculer_similarite(doc1, doc2)` qui renvoie un pourcentage de similarité entre deux documents.
### 5.2. Interface utilisateur
Créer une interface en ligne de commande permettant :
- D'ajouter des documents depuis des fichiers
- De vérifier un nouveau document
- D'afficher un rapport détaillé
### 5.3. Optimisation
Implémenter une version utilisant des **n-grammes** (séquences de n mots) pour une détection plus robuste au reformulation.
---
## Barème indicatif
| Partie | Points |
|--------|--------|
| Partie 1 : Recherche naïve | 4 |
| Partie 2 : Boyer-Moore | 5 |
| Partie 3 : Comparaison | 3 |
| Partie 4 : Détecteur | 6 |
| Partie 5 : Bonus | 2 |
| **Total** | **20** |
---
Auteur : Florian Mathieu
Licence CC BY NC
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/4.0/"><img alt="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 <a rel="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>.