#!/usr/bin/python3 # -*- coding: utf-8 -*- # pylint: disable=invalid-name, global-statement """ TP AP1 Licence SESI 1ère année Univ. Lille 1 analyse_tris.py TP4 - Evaluation empirique des tris http://www.fil.univ-lille1.fr/~L1S2API/CoursTP/tp4_tri.html """ __author__ = 'PREUD\'HOMME Geoffrey & BEAUSSART Jean-loup' __date_creation__ = 'Tue, 10 Mar 2015 10:26:41 +0100' from random import randint affichage = __name__ == '__main__' # La variable affichage définit si on doit répondre aux questions du TP. De cette manière, ces # dernières sont affichées et calculées uniquement si le programme principal est lancé, ce qui # nous permet de réutiliser des fonctions de ce fichier dans `analyse_en_moyenne.py` sans avoir # d'affichage et de calculs non-nécessaires à son fonctionnement. On aurait pu aussi séparer # fonctions et questions, mais pour la facilité de la correction nous avons préféré qu'il en soit # ainsi. def partie(nom): """ str → ∅ Affiche le nom de la partie en cours """ assert isinstance(nom, str) if affichage: print('\n', nom, '=' * len(nom), sep='\n') def section(nom): """ str → ∅ Affiche le nom de la section en cours """ assert isinstance(nom, str) if affichage: print('\n', nom, '-' * len(nom), sep='\n') def question(numero): """ str → ∅ Affiche le numéro de la question en cours """ assert isinstance(numero, int) if affichage: print('\n***', 'Question', numero, '***') def reponse(texte): """ str → ∅ Affiche la réponse à la question. """ assert isinstance(texte, str) if affichage: print(texte) partie("Prérequis") def comp(x, y): """ parametres x , y de même type et comparables valeur renvoyee : int -1 si xy action : incrémente le compteur CU : aucune """ global compteur compteur = compteur + 1 if x < y: return -1 elif x == y: return 0 else: return 1 def select_min(l, a, b): """ list, int, int → int Renvoie l'indicde d'un élément minimal de la tranche l[a:b] CU : l est une liste de longueur n, d'éléments homogènes ordonnables, et a et b deux indices tels que 0 ≤ a < b < n """ assert 0 <= a < b <= len(l) imin = a for i in range(a + 1, b + 1): if comp(l[i], l[imin]) == -1: imin = i return imin def tri_selection(l): """ list → ∅ La liste l est trié (selon l'algorithme du tri par sélection du minimum) CU : l est une liste de longueur n, homogène, d’éléments ordonnables """ assert isinstance(l, list) n = len(l) for i in range(n - 1): imin = select_min(l, i, n - 1) l[i], l[imin] = l[imin], l[i] def tri_insertion_base(l, n): """ list, int → ∅ n est un indice de l tel que l[0:n] soit une liste triée. La fonction déplace l'élément de rang n de telle sorte que l[0:i+1] soit triée CU : n est un entier < len(l) et l est une liste, dont les éléments sont comparables, triée jusqu'à l'indice n-1. """ assert isinstance(n, int) and isinstance(l, list) and 0 <= n < len(l) aux = l[n] k = n while k >= 1 and comp(l[k - 1], aux) == 1: l[k] = l[k - 1] k -= 1 l[k] = aux def tri_insertion(l): """ list → ∅ La liste l est trié (selon l'algorithme du tri par insertion) CU : l est une liste de longueur n, homogène, d’éléments ordonnables """ assert isinstance(l, list) for i in range(1, len(l)): tri_insertion_base(l, i) partie("Travail à réaliser") section("Préliminaires") question(1) def liste_croissante(n): """ int → list(int) Retourne la liste des entiers compris entre 0 et n-1, rangés dans l'ordre croissant CU : n est un entier positif """ assert isinstance(n, int) and n >= 0 return list(range(n)) question(2) def liste_decroissante(n): """ int → list(int) Retourne la liste des entiers compris entre 0 et n-1, rangés dans l'ordre décroissant CU : n est un entier positif """ assert isinstance(n, int) and n >= 0 return list(range(n - 1, -1, -1)) question(3) def liste_alea(n, a, b): """ int, int, int → list(int) Renvoie une liste de longueur n comprenant des entiers compris entre a et b choisis au hasard. CU : n entier positif, a et b entiers, a ≤ b """ assert isinstance(n, int) and isinstance( a, int) and isinstance(b, int) and n >= 0 and a <= b return [randint(a, b) for _ in range(n)] section("Compter les comparaisons") question(1) compteur = 0 tri_selection(liste_alea(100, -5000, 5000)) reponse('{} comparaisons ont été faite pour cette liste.'.format(compteur)) question(2) def tri_et_compte(trieur, l): """ str, list → (list, int) Trie la liste l avec la fonction de triage trieur passée en paramètre, renvoie la liste triée et le nombre de comparaisons effectuées CU : trieur est une fonction, l est une liste """ assert callable(trieur) and isinstance(l, list) global compteur compteur = 0 ltrie = trieur(l[:]) # On fait une copie l return (ltrie, compteur) partie("Analyse du tri par sélection") def afficher_tableau(donnees): """ list[list] → ∅ Affiche donnees sous forme d'un tableau x / y CU : donnees est une liste contenant des listes de même longueurs """ assert isinstance(donnees, list) taillesColonnes = [max([len(str(donnees[y][x])) for y in range( len(donnees))]) for x in range(len(donnees[0]))] barres = ['─' * l for l in taillesColonnes] print('┌' + '┬'.join(barres) + '┐') for y in range(len(donnees)): print('│{}│'.format('│'.join(["{:>{taille}}" .format(donnees[y][x], taille=taillesColonnes[x]) for x in range(len(donnees[0]))]))) if y == 0: print('├' + '┼'.join(barres) + '┤') print('└' + '┴'.join(barres) + '┘') question(1) if affichage: tableau = [['nb ', 'croissante ', 'aléatoire ', 'decroissante']] for nb in range(1, 101): tableau.append([nb, tri_et_compte(tri_selection, liste_croissante(nb))[1], tri_et_compte( tri_selection, liste_alea(nb, 0, 500))[1], tri_et_compte(tri_selection, liste_decroissante(nb))[1]]) afficher_tableau(tableau) question(2) partie("Analyse du tri par insertion") question(1) if affichage: tableau = [tableau[0]] for nb in range(1, 101): tableau.append([nb, tri_et_compte(tri_insertion, liste_croissante(nb))[1], tri_et_compte( tri_insertion, liste_alea(nb, 0, 500))[1], tri_et_compte(tri_insertion, liste_decroissante(nb))[1]]) afficher_tableau(tableau) section("Dans le meilleur des cas") question(1) reponse("Le meilleur des cas est lorsque la liste est déjà triée.") question(2) if affichage: reponse("Résultat théorique établi : c_{tri-insert}(n)=n-1") tableau = [['i', 'coût comtpé', 'coût théor.', 'fidèle']] tousFideles = True for nb in range(1, 101): c_compte = tri_et_compte(tri_insertion, liste_croissante(nb))[1] c_theor = nb - 1 fidele = c_compte == c_theor if not fidele: tousFideles = False tableau.append([nb, c_compte, c_theor, "Oui" if fidele else "Non"]) afficher_tableau(tableau) reponse("Les résultats comptés {} tous fidèles aux résultats théoriques." .format("sont" if tousFideles else "ne sont pas")) section("Dans le pire des cas") question(1) reponse("Le pire des cas est lorsque la liste est triée dans l'ordre inverse.") question(2) if affichage: reponse("Résultat théorique établi : c_{tri-insert}(n)=n(n-1)/2") tableau = [tableau[0]] tousFideles = True for nb in range(1, 101): c_compte = tri_et_compte(tri_insertion, liste_decroissante(nb))[1] c_theor = nb * (nb - 1) // 2 fidele = c_compte == c_theor if not fidele: tousFideles = False tableau.append([nb, c_compte, c_theor, "Oui" if fidele else "Non"]) afficher_tableau(tableau) reponse("Les résultats comptés {} tous fidèles aux résultats théoriques." .format("sont" if tousFideles else "ne sont pas")) section("En moyenne") question(1) if affichage: t1 = tri_et_compte(tri_insertion, liste_alea(100, -5000, 5000))[1] t2 = tri_et_compte(tri_insertion, liste_alea(100, -5000, 5000))[1] reponse("Le nombre de comparaisons de la première liste est {t1}, celui de la deuxième liste \ est {t2}.\nLes nombres de comparaisons pour ces deux tris sont {res}.".format(t1=t1, t2=t2, \ res=("identiques (mais c'est un coup de chance)" if t1 == t2 else "différents"))) question(1) def nbre_moyen_tri_insertion(m, n): """ int, int → float Calcule la moyenne du nombre de comparaisons pour trier par insertion un échantillion de taille m de listes choisies au hasard de longueur n. CU : m et n sont des entiers positifs """ assert isinstance(m, int) and isinstance(n, int) and 0 <= m and 0 <= n compTotal = 0 for _ in range(m): compTotal += tri_et_compte(tri_insertion, liste_alea(n, -5000, 5000))[1] return compTotal / m question(2) reponse("Voir le fichier `analyse_en_moyenne.py`.") question(3) question(4) reponse("Je vous invite à éxecuter la commande `make tri_insertion_moyen.txt` pour obtenir ce \ fichier.") section("Avec Gnuplot") question(1) reponse("Graphiquement, on trouve que cette fonction a pour expression 0,287 × x² - 2 × x + 1\n\ Cette fonction correspond à la commande suivante : \n\ gnuplot> plot 'tri_insertion_moyen.txt', 0.287*x**2-2*x+1 with lines\n\ On peut aussi trouver des valeurs plus précises en faisant une regression polynôminale des données \ avec la fonction polyfit de numpy. Je vous invite à lancer la commande suivante :\n\ python3 analyse_en_moyenne.py --brut --poly --graph\n\ Je vous invite aussi à éxecuter la commande suivante qui permet de générer le graphique Gnuplot :\n\ make tri_insertion_moyen.png")