ajout de tous les cours et TP préparés cet été
This commit is contained in:
187
Recherche_textuelle/CORRIGE.md
Normal file
187
Recherche_textuelle/CORRIGE.md
Normal 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>.
|
||||
448
Recherche_textuelle/Corrige_TP_Detecteur_Plagiat.py
Normal file
448
Recherche_textuelle/Corrige_TP_Detecteur_Plagiat.py
Normal 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}%")
|
||||
130
Recherche_textuelle/EXERCICES.md
Normal file
130
Recherche_textuelle/EXERCICES.md
Normal 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>.
|
||||
@@ -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>.
|
||||
|
||||
|
||||
|
||||
|
||||
360
Recherche_textuelle/TP_Detecteur_Plagiat.md
Normal file
360
Recherche_textuelle/TP_Detecteur_Plagiat.md
Normal 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>.
|
||||
Reference in New Issue
Block a user