361 lines
9.6 KiB
Markdown
361 lines
9.6 KiB
Markdown
# 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>.
|