diff --git a/mphf.png b/mphf.png new file mode 100644 index 0000000000000000000000000000000000000000..f6cc3bd47d535596ce940f235d3855d4b9765fd7 Binary files /dev/null and b/mphf.png differ diff --git a/tp_2_miso_dict.py b/tp_2_miso_dict.py index 98d2a0c91f569e81c4b9ba67b1cf7bf2a35c8f82..2fb1d598541f695dfe1de06bd905325116c5ef6a 100644 --- a/tp_2_miso_dict.py +++ b/tp_2_miso_dict.py @@ -7,10 +7,26 @@ import sys ###### PARTIE 2 ###### -def experiment_load_factor(load_factors): +def experiment_load_factor(load_factors : list): """ Étude du facteur de charge + Parameters + ---------- + load_factors : list + Une liste de facteurs de charge + Returns + ------- + lists + Les temps d'insertion de clefs + Les nombres de réallocations de mémoire + Les tailles de mémoire occupée par le dictionnaire pour chaque facteur de charge """ + insertion_times = [] + num_resizes = [] + sizes = [] + for factor in load_factors : + dictio = {} + # num_elements = .......... QUESTION 2 PARTIE 2 return [],[],[] def experiment_longest(): diff --git a/tp_2_miso_mphf.py b/tp_2_miso_mphf.py index ebc3d3328dd4c33b1c29a5692f95b319518eb291..3aee1c40ac8708692f56e988965a85ce36f39a7f 100644 --- a/tp_2_miso_mphf.py +++ b/tp_2_miso_mphf.py @@ -39,42 +39,64 @@ def construction_mphf(set_kmer, n, gamma=2, nb_niveaux=3): for _ in range(nb_niveaux): if len(set_kmer_courant) > 0: l = len(set_kmer_courant) + if l==0 : + break #Evite une division par 0 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) + hachage = abs(hash(kmer)) # 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 - + # sinon, écrire le hash à l'adresse dans le tableau principal + adresse = hachage % (gamma * l) + if tableau_principal[adresse] != -1: + collision.add(kmer) + else: + tableau_principal[adresse] = hachage tableaux.append(tableau_principal) # expliquer + # Chaque niveau de hachage produit un nouveau tableau de hachage. + # Donc on enregistre le tableau dans tableaux pour garder une trace des niveaux. set_kmer_courant = collision.copy() # expliquer + # On hache à nouveau les k-mers qui ont causé des collisions. collision = set() # expliquer + # On réinitialise collision pour le prochain niveau de hachage, pour pas que les valeurs dont on n'a plus besoin s'accumulent. + if not set_kmer_courant: # Arrête la boucle si plus de collisions + break # Construction de la MPHF mphf = [] grand_tableau = [] for tableau in tableaux: grand_tableau.extend(tableau) # expliquer + # Après avoir fait tous les hachages, on regroupe les tableaux finaux de hachage dans un seul tableau. + grand_tableau_dict = {val: i for i, val in enumerate(grand_tableau)} - rangs = [] max_rang = 0 i = 0 for kmer in set_kmer: - pass # compléter: # hacher le kmer + hache = abs(hash(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 + if hache in grand_tableau_dict : + index = grand_tableau_dict[hache] + rang = grand_tableau[:index].count(hache) + mphf.append([hache, rang]) + max_rang = max(max_rang, 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]) - + max_rang += 1 # On attribue un nouveau rang unique à chaque élément en collision. + h = abs(hash(kmer)) # Recalcul du hash, on veut uniquement des entiers positifs. + mphf.append([h, max_rang]) # On ajoute le k-mer à mphf + # On fait ces étapes car les collisions n'ont pas pu être placées avant, + # on leur attribue un rang plus grand pour ne pas perturber l'ordre précédent + if not mphf: + print("⚠️ Attention : MPHF vide, vérifiez les données en entrée.") + return mphf @@ -98,36 +120,46 @@ def 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 + hache = abs(hash(kmer)) + for h, rang in mphf : + if h == hache : + return rang + return -1 - 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 +def create_hash_table(set_kmer, n): + """ + Crée une table de hachage à partir d'un ensemble de k-mers et d'une 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 + 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 + """ + mphf = construction_mphf(set_kmer, n) # créer la mphf pour les kmers + # initialiser un tableau de taille n (une liste) + tableau = [None]*n + # écrire les kmers aux adresses du tableau données par la mphf + for kmer in set_kmer : + adresse = get_hash_mphf(mphf, kmer) + if adresse == -1: + print(f"Erreur : Impossible de trouver l'adresse pour {kmer} (MPHF renvoie -1)") + continue + if 0 <= adresse < n: + tableau[adresse]=kmer + return tableau, mphf # retourner le tableau et la mphf def generer_kmers(n, k): @@ -149,10 +181,12 @@ def compare_taille(n_max, fichier_sortie): for n in range(100, n_max, 1000): set_kmer = generer_kmers(n, k) - tableau, mphf = create_hash_table(set_kmer,n) + 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 ? + table_size.append(sys.getsizeof(tableau)+sys.getsizeof(mphf)) # pourquoi ici on ne mesure pas juste la taille en mémoire du tableau ? + #Car sys.getsizeof(tableau) ne mesure que la liste elle-même, pas les objets stockés dedans. + # On veut mesurer la mémoire totale du système de hachage, pas seulement la liste. dict_size.append(sys.getsizeof(set_kmer)) plt.plot(n_values, table_size, label='Table avec MPHF') @@ -166,4 +200,10 @@ def compare_taille(n_max, fichier_sortie): plt.close() # dé-commenter quand vous êtes prêts, expliquer les résultats -#compare_taille(10000,"mphf.png") +compare_taille(10000,"mphf.png") + +# Sur le graphe obtenu, on observe que la taille de la table de hachage du dictionnaire augmente par paliers, +# alors que celle de la MPHF augmente de manière linéaire, et beaucoup plus progressivement. +# Cela est cohérent, en effet le dictionnaire va allouer plus d'espace dès que le nombre d'éléments atteint un seuil. +# Quant à la MPHF, elle minimise la mémoire en évitant les collisions. +