diff --git a/Readme.md b/Readme.md new file mode 100644 index 0000000000000000000000000000000000000000..c66afd82f9b56ffd28631c8efd87208dd6e36278 --- /dev/null +++ b/Readme.md @@ -0,0 +1,67 @@ +# TP Hachage + + + + +# Partie 1 - Réaliser une fonction de hachage minimale parfaite + +On travaillera sur `tp_2_miso_mphf.py`. + +Tout d'abord, voyons ensemble l'idée de la construction de la MPHF. +Puis suivez les commentaires dans le code à compléter. +Vous devez +- finaliser la fonction pour construire la MPHF (`construction_mphf`), +- écrire la fonction pour obtenir le hash d'un élément par la MPHF (`get_hash_mphf`), +- écrire la fonction pour créer une table de hachage avec cette mphf (`create_hash_table`) + +Ecrivez vos réponses et commentaires dans ce document. + +Puis décommenter `compare_taille` à la fin et expliquer les résultats. + +Bonus : faites varier `nb_niveaux` et `gamma`, voyez quelle influence ils peuvent avoir. + +# Partie 2 - Analyse de performance de dictionnaires en Python + +On travaillera sur `tp_2_miso_dict.py` + +A la fin de cette partie, en lançant `python tp_2_miso_dict.py` on doit obtenir 4 graphiques. + +## Introduction + +Nous allons analyser la performance de dictionnaires `dict` en Python en fonction de différents facteurs : que le facteur de charge (loading factor), +le temps d'insertion et la taille de la mémoire occupée. Vous pourrez utiliser matplotlib et numpy pour visualiser les résultats. + +## Étude du facteur de charge + +Fonction `experiment_load_factor`: + +0. La fonction `experiment_load_factor` doit être définie pour prendre en entrée une liste de facteurs de charge (`load_factors`) et renvoyer les temps +- d'insertion de clefs, +- les nombres de réallocations de mémoire (quand le dictionnaire est ré-écrit en mémoire pour agrandir sa taille) +- et les tailles de mémoire occupées par le dictionnaire pour chaque facteur de charge. + +1. Initialisation des listes pour stocker les résultats. +Initialiser dans la fonction `experiment_load_factor` des listes `insertion_times`, `num_resizes` et `sizes` seront utilisées pour stocker les trois résultats décrits au dessus. + +2. La fonction `experiment_load_factor` doit boucler sur chaque facteur de charge de la liste en entrée `load_factors`. Créer un dictionnaire d'abord vide à chaque fois. + +3. Initialiser les variables `num_elements`, `start_time`, `num_resize` et `last_size` pour respectivement mesurer le nombre d'éléments, le temps d'insertion et le nombre de réallocations de mémoire. + +4. Insérer les éléments dans le dictionnaire et mesurer le temps d'insertion pour chaque élément, vérifier le nombre de réallocations mémoire (utiliser par exemple `time.time()` et `sys.getsizeof()` pour mesurer le temps et la taille du dictionnaire avant et après chaque insertion). + +5. Pour un facteur de charge donné, stocker les résultats dans les listes `insertion_times`, `num_resizes` et `sizes`. + +## Deuxième étude + +6. A quoi sert la fonction `experiment_longest` ? + +## Visualisation des résultats + +7. Créez quatre graphiques au format png ou pdf : + +- Un graphique du temps d'insertion en fonction du facteur de charge (obtenu question 5) +- Un graphique du nombre de réallocations de mémoire en fonction du facteur de charge (obtenu question 5) +- Un graphique de la taille de mémoire occupée en fonction du nombre d'éléments (obtenu question 5) +- Un histogramme des fréquences des temps d'insertions discrétisés (code fourni, remplacer la liste vide par la bonne entrée) + +10. Commentez vos résultats. diff --git a/slides_hash.pdf b/slides_hash.pdf new file mode 100644 index 0000000000000000000000000000000000000000..14c5b9bfa6eae0bc85a8906f50d9497b415cd48b Binary files /dev/null and b/slides_hash.pdf differ diff --git a/tp_2_miso_dict.py b/tp_2_miso_dict.py new file mode 100644 index 0000000000000000000000000000000000000000..98d2a0c91f569e81c4b9ba67b1cf7bf2a35c8f82 --- /dev/null +++ b/tp_2_miso_dict.py @@ -0,0 +1,59 @@ +import matplotlib.pyplot as plt +import numpy as np +import time +import sys + + + +###### PARTIE 2 ###### + +def experiment_load_factor(load_factors): + """ + Étude du facteur de charge + """ + return [],[],[] + +def experiment_longest(): + """ + TODO: que fait cette fonction + """ + d = {} + insertion_times = [] + + for i in range(10000): + key = str(i) + value = i + start_time = time.time() + d[key] = value + insertion_time = time.time() - start_time + insertion_times.append(insertion_time) + frequencies = np.histogram(insertion_times)[0] + return frequencies + +def visualisation(load_factors, insertion_times, num_resizes, sizes, frequencies): + """ + Visualisation des résultats + """ + # Temps d'insertion en fonction du facteur de charge + + # Nombre de réallocations de mémoire en fonction du facteur de charge + + # Taille de mémoire occupée en fonction du nombre d'éléments + + # Deuxième étude + f = list() + plt.figure(figsize=(10, 6)) + plt.bar(range(len(f)), f) + plt.xlabel('Temps d\'insertion (s)') + plt.ylabel('Fréquence') + plt.title('Histogramme des fréquences des temps d\'insertions') + plt.yscale('log') + xticks = np.logspace(-6, 1, 3) + xtick_labels = [f'{x:.1e}' for x in xticks] + plt.xticks(xticks, xtick_labels) + plt.savefig('histogramme.png') + +load_factors = [0.01, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0] +insertion_times, num_resizes, sizes = experiment_load_factor(load_factors) +frequencies = experiment_longest() +visualisation(load_factors, insertion_times, num_resizes, sizes, frequencies) diff --git a/tp_2_miso_mphf.py b/tp_2_miso_mphf.py new file mode 100644 index 0000000000000000000000000000000000000000..ebc3d3328dd4c33b1c29a5692f95b319518eb291 --- /dev/null +++ b/tp_2_miso_mphf.py @@ -0,0 +1,169 @@ +import matplotlib.pyplot as plt +import numpy as np +import time +import sys +import random + + +###### PARTIE 1 ###### + +def construction_mphf(set_kmer, n, gamma=2, nb_niveaux=3): + + """ + Construit une fonction de hachage minimale parfaite (MPHF) pour un ensemble de k-mers. + + Parameters: + set_kmer (set): Ensemble de k-mers. + n (int): Taille de l'ensemble de k-mers. + gamma (int): Facteur de réduction de la taille de la table de hachage. Default: 2. + nb_niveaux (int): Nombre de niveaux de réduction de la taille de la table de hachage. Default: 3. + + Returns: + mphf (list): Table de hachage minimale parfaite. + + Examples: + >>> set_kmer = {str(i) for i in range(10)} + >>> n = 10 + >>> mphf = construction_mphf(set_kmer, n) + >>> len(mphf) == n + True + >>> all(0 <= mphf[i] < n for i in range(n)) + True + >>> len(mphf) == n + True + """ + # Initialisation + set_kmer_courant = set_kmer.copy() + tableaux = [] + collision = set() + for _ in range(nb_niveaux): + if len(set_kmer_courant) > 0: + l = len(set_kmer_courant) + tableau_principal = [-1] * (gamma * l) + for kmer in set_kmer_courant: + pass # compléter + # hacher le k-mer (attention, hash() peut rendre des entiers signés, nous voulons des entiers positifs) + # récupérer l'adresse + # si le tableau principal est déjà rempli à l'adresse: + # mettre le kmer dans collision() + #sinon, écrire le hash à l'adresse dans le tableau principal + + tableaux.append(tableau_principal) # expliquer + set_kmer_courant = collision.copy() # expliquer + collision = set() # expliquer + + # Construction de la MPHF + mphf = [] + grand_tableau = [] + for tableau in tableaux: + grand_tableau.extend(tableau) # expliquer + + rangs = [] + max_rang = 0 + i = 0 + for kmer in set_kmer: + pass # compléter: + # hacher le kmer + # si le hash est dans le grand_tableau + # récupérer son index + # récupérer son rang (utiliser la fonction count()) + # ajouter à la mphf [h, rang] + # mettre à jour max_rang + + for kmer in set_kmer_courant: #gestion des collisions: expliquer les 3 lignes du dessous + max_rang += 1 + h = abs(hash(kmer)) + mphf.append([h, max_rang]) + + return mphf + + +def get_hash_mphf(mphf, kmer): + """ + Calcule le hash d'un k-mer à l'aide d'une fonction de hachage minimale parfaite (MPHF). + + Parameters: + mphf (list): Table de hachage minimale parfaite. + kmer (str): K-mer à hasher. + + Returns: + int: Hash du k-mer. + + Examples: + >>> set_kmer = {str(i) for i in range(10)} + >>> n = 10 + >>> mphf = construction_mphf(set_kmer, n) + >>> kmer = "5" + >>> hash_mphf = get_hash_mphf(mphf, kmer) + >>> 0 <= hash_mphf < n + True + """ + pass # TODO modifier + +def create_hash_table(set_kmer, n): + """ + Crée une table de hachage à partir d'un ensemble de k-mers et d'une mphf + + Parameters: + set_kmer (set): Ensemble de k-mers. + n (int): Taille de la table de hachage. + + Returns: + list: Table de hachage créée à partir des k-mers + mphf: la mphf + + Examples: + >>> set_kmer = {'ATCG', 'TGCA', 'GCTA'} + >>> n = 10 + >>> tableau = create_hash_table(set_kmer, n) + >>> len(tableau) == n + True + >>> all(kmer in tableau for kmer in set_kmer) + True + """ + pass # TODO modifier + # créer la mphf pour les kmers + # initialiser un tableau de taille n (une liste) + # écrire les kmers aux adresses du tableau données par la mphf + # retourner le tableau et la mphf + + + + +def generer_kmers(n, k): + ''' + genere un set de n k-mers + ''' + kmers = set() + while len(kmers) < n: + kmer = ''.join(random.choice('ATCG') for _ in range(k)) + kmers.add(kmer) + return kmers + + +def compare_taille(n_max, fichier_sortie): + n_values = [] + table_size = [] + dict_size = [] + k = 21 + + for n in range(100, n_max, 1000): + set_kmer = generer_kmers(n, k) + tableau, mphf = create_hash_table(set_kmer,n) + + n_values.append(n) + table_size.append(sys.getsizeof(tableau)+sys.getsizeof(mphf)) # pourquoi ici on ne mesure pas juste la taille en mémoire du tableau ? + dict_size.append(sys.getsizeof(set_kmer)) + + plt.plot(n_values, table_size, label='Table avec MPHF') + plt.plot(n_values, dict_size, label='Dict') + plt.xlabel('n') + plt.xticks(n_values, [str(x) for x in n_values], rotation=45) + plt.ylabel('Taille (octets)') + plt.title('Évolution de la taille de la table de hachage avec MPHF et du dict') + plt.legend() + plt.savefig(fichier_sortie) + plt.close() + +# dé-commenter quand vous êtes prêts, expliquer les résultats +#compare_taille(10000,"mphf.png")