diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000000000000000000000000000000000000..d5459c5924e1a50da05683740f0501ed2aa22a29 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +tp_2_miso_dict.py \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml index dc9ea4906e15825848ae951155a4381c08529eaf..db8786c06ed9719181cd25ebe2615cc99c8490eb 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,4 +1,7 @@ <?xml version="1.0" encoding="UTF-8"?> <project version="4"> - <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10" project-jdk-type="Python SDK" /> + <component name="Black"> + <option name="sdkName" value="Python 3.12" /> + </component> + <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.12" project-jdk-type="Python SDK" /> </project> \ No newline at end of file diff --git a/.idea/tp2_hachage.iml b/.idea/tp2_hachage.iml index 039314de6c082718b0c4495bd64f8df7279e477f..a1045faca8f1312bd08dc93f6b22ccacfb876b67 100644 --- a/.idea/tp2_hachage.iml +++ b/.idea/tp2_hachage.iml @@ -2,7 +2,7 @@ <module type="PYTHON_MODULE" version="4"> <component name="NewModuleRootManager"> <content url="file://$MODULE_DIR$" /> - <orderEntry type="inheritedJdk" /> + <orderEntry type="jdk" jdkName="Python 3.12" jdkType="Python SDK" /> <orderEntry type="sourceFolder" forTests="false" /> </component> <component name="PyDocumentationSettings"> diff --git a/tp_2_miso_dict.py b/tp_2_miso_dict.py index 2fb1d598541f695dfe1de06bd905325116c5ef6a..e5f8eb18019cc008dc2ab80d078c475c67ad8709 100644 --- a/tp_2_miso_dict.py +++ b/tp_2_miso_dict.py @@ -2,6 +2,7 @@ import matplotlib.pyplot as plt import numpy as np import time import sys +import statistics #pour faire la moyenne des temps d'insertion pour chaque facteur de charge @@ -21,13 +22,38 @@ def experiment_load_factor(load_factors : list): Les nombres de réallocations de mémoire Les tailles de mémoire occupée par le dictionnaire pour chaque facteur de charge """ + # Initialisation insertion_times = [] num_resizes = [] sizes = [] + for factor in load_factors : dictio = {} - # num_elements = .......... QUESTION 2 PARTIE 2 - return [],[],[] + num_resize=0 + last_size = sys.getsizeof(dictio) + num_elements = int(factor*100) + tempsecoules = [] + + for i in range(num_elements) : + cle = 'cle'+str(i) + start_time = time.time() + dictio[cle] = i + end_time = time.time() + tempsecoule = end_time - start_time + tempsecoules.append(tempsecoule) + current_size = sys.getsizeof(dictio) + + if current_size > last_size : + num_resize += 1 + last_size = current_size + + size = sys.getsizeof(dictio) + insertion_time = statistics.mean(tempsecoules) + insertion_times.append(insertion_time) + num_resizes.append(num_resize) + sizes.append(size) + + return insertion_times, num_resizes, sizes def experiment_longest(): """ @@ -50,24 +76,42 @@ def visualisation(load_factors, insertion_times, num_resizes, sizes, frequencies """ Visualisation des résultats """ + num_elements = [] + for factor in load_factors : + num_elements.append(factor*100) # Temps d'insertion en fonction du facteur de charge - + plt.plot(load_factors, insertion_times) + plt.xlabel('Facteur de charge') + plt.xticks(load_factors, [str(x) for x in load_factors], rotation=45) + plt.ylabel("Temps d'insertion (secondes)") + plt.title("Temps d'insertion en fonction du facteur de charge") + plt.savefig("temps_d_insertion.png") # Nombre de réallocations de mémoire en fonction du facteur de charge - + plt.plot(load_factors, num_resizes) + plt.xlabel('Facteur de charge') + plt.xticks(load_factors, [str(x) for x in load_factors], rotation=45) + plt.ylabel("Nombre de réallocations de mémoire") + plt.title("Nombre de réallocations de mémoire en fonction du facteur de charge") + plt.savefig("nombre_reallocations.png") # Taille de mémoire occupée en fonction du nombre d'éléments - + plt.plot(sizes, num_elements) + plt.xlabel("Nombre d'éléments") + plt.xticks(num_elements, [str(x) for x in num_elements], rotation=45) + plt.ylabel("Taille de mémoire occupée (octets)") + plt.title("Taille de mémoire occupée en fonction du nombre d'éléments") + plt.savefig("taille_memoire.png") # Deuxième étude - f = list() + f = list(frequencies) 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') + xticks = np.logspace(-6, 1, 3) + xtick_labels = [f'{x:.1e}' for x in xticks] + plt.xticks(xticks, xtick_labels) + plt.savefig('deuxieme_etude.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) diff --git a/tp_2_miso_mphf.py b/tp_2_miso_mphf.py index 3aee1c40ac8708692f56e988965a85ce36f39a7f..37c883388f2a6c34e33421695d9feef3c17416b1 100644 --- a/tp_2_miso_mphf.py +++ b/tp_2_miso_mphf.py @@ -8,8 +8,7 @@ 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: @@ -32,76 +31,76 @@ def construction_mphf(set_kmer, n, gamma=2, nb_niveaux=3): >>> 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) - if l==0 : - break #Evite une division par 0 - tableau_principal = [-1] * (gamma * l) - for kmer in set_kmer_courant: - # 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 - 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)} - - max_rang = 0 - i = 0 - for kmer in set_kmer: - # 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 # 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 + # 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) + if l == 0: + break # Evite une division par 0 + tableau_principal = [-1] * (gamma * l) + for kmer in set_kmer_courant: + # 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 + 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)} + + max_rang = 0 + i = 0 + for kmer in set_kmer: + # 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 # 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 def get_hash_mphf(mphf, kmer): - """ + """ Calcule le hash d'un k-mer à l'aide d'une fonction de hachage minimale parfaite (MPHF). Parameters: @@ -120,15 +119,15 @@ def get_hash_mphf(mphf, kmer): >>> 0 <= hash_mphf < n True """ - hache = abs(hash(kmer)) - for h, rang in mphf : - if h == hache : - return rang - return -1 + hache = abs(hash(kmer)) + for h, rang in mphf: + if h == hache: + return rang + return -1 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: @@ -148,59 +147,61 @@ def create_hash_table(set_kmer, n): >>> 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 + 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): - ''' + ''' 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 + 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 ? - #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') - 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() + 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 ? + # 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') + 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") +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.