La présentation est en train de télécharger. S'il vous plaît, attendez

La présentation est en train de télécharger. S'il vous plaît, attendez

Conception et implémentation de logiciels (avec des exemples de B. Kernighan et R. Pike « La programmation en pratique ») Vladimir Makarenkov (Université

Présentations similaires


Présentation au sujet: "Conception et implémentation de logiciels (avec des exemples de B. Kernighan et R. Pike « La programmation en pratique ») Vladimir Makarenkov (Université"— Transcription de la présentation:

1 Conception et implémentation de logiciels (avec des exemples de B. Kernighan et R. Pike « La programmation en pratique ») Vladimir Makarenkov (Université du Québec à Montréal)

2 2 Introduction La conception est lactivité qui permet de rattacher les spécifications au codage et au débogage. La conception est un problème vicieux. La conception de structures de données est le point crucial dans le processus de création dun programme. La conception sous entend lélaboration dune solution conceptuelle satisfaisant aux besoins plutôt que la mise en œuvre de la solution.

3 3 La conception Est un processus sans rigueur. Est une affaire de négociation et de priorité. Suppose des restrictions. Nest pas déterministe. Est un processus heuristique. Est une science nouvelle.

4 4 Caractéristiques dune bonne conception Complexité minimale Facilité de maintenance Couplage modéré Extensibilité Réutilisabilité Haut niveau de fan-in Niveau de fan-out faible ou moyen Portabilité Dépouillement Stratification Technique standard

5 5 Les techniques de conception Litération Diviser pour conquérir Les approches : – ascendante : partir du simple vers le complexe; – descendante : partir du complexe vers le simple. Le prototypage expérimental La conception collaborative

6 6 Problème Générer du texte aléatoire se lisant bien. En émettant des lettres ou des mots aléatoires, le résultat naura aucun sens. Pour obtenir de meilleurs résultats, il faut utiliser un modèle statistique mieux structuré : –fréquence dapparition de phrases entières; –où trouver ce genre de statistiques?

7 7 Problème Prendre un gros texte et létudier en détail. On peut utiliser tout texte et générer à partir de ce modèle un texte aléatoire possédant des statistiques similaires à loriginal. Entrée/Sortie. Algorithme de la chaîne de Markov.

8 8 Algorithme de la chaîne de Markov Entrée: une séquence de phrases qui se chevauche. Lalgorithme divise chaque phrase en deux parties : –Un préfixe composé de plusieurs mots. –Un suffixe dun seul mot qui suit le préfixe. Il émet des phrases en sortie en sélectionnant aléatoirement le suffixe qui suit le préfixe conformément aux statistiques.

9 9 Algorithme de la chaîne de Markov (2) Des phrases de trois mots fonctionnent bien. Un préfixe de deux mots est employé pour sélectionner le suffixe : Initialiser les variables w 1 et w 2 avec les 2 premiers mots. Imprimer w 1 et w 2 Boucle: Sélectionner aléatoirement w 3, lun des successeurs du préfixe w 1 w 2 dans le texte. Imprimer w 3. Remplacer w 1 et w 2 par w 2 et w 3. Répéter la boucle.

10 10 Algorithme de la chaîne de Markov (3) Exemple: Show your flowcharts and conceal your tables and I will be mystified. Show your tables and your flowcharts will be obvious. (end). PréfixesSuffixes qui suivent Show your your flowcharts flowcharts and flowcharts will your tables will be be mystified. be obvious. flowcharts tables and will conceal be and mystified. obvious. Show (end)

11 11 Algorithme de la chaîne de Markov (4) Lalgorithme commencera par imprimer Show your pour choisir aléatoirement soit flowcharts soit tables. Sil sélectionne tables, le mot suivant sera and. Se poursuit jusquà ce que suffisamment de sorties aient été générées ou que la marque (end) devienne suffixe.

12 12 Choix dune structure de données Combien dentrées voulons nous traiter ? A quelle vitesse le programme devrait sexécuter ? Lalgorithme doit voir toutes les entrées avant de commencer à générer la sortie. Il faut stocker la totalité des entrées sous une forme quelconque.

13 13 Choix de structure de données Première possibilité. Lire les entrées et les stocker sous forme dune longue chaîne de caractères. Pour faciliter la décomposition des entrées en mot, nous pouvons stocker les mots sous forme de tableaux de pointeurs de mots. Problème: cela signifie quil faut balayer mots en entrée pour générer chaque mot.

14 14 Choix de structure de données (2) Seconde possibilité. Stocker les mots uniques en entrée. Stocker en même temps les endroits où ces mots apparaissent dans le texte. –Permet de faciliter la localisation des mots qui suivent. Possibilité demployer une table de hachage. Il faut pouvoir rapidement localiser tous les suffixes.

15 15 Choix de structure de données (3) Besoin dune structure de données qui représente mieux un préfixe et les suffixes associés. Le programme comportera deux passages : –un passage en entrée pour construire la structure de données représentant les locutions; –un passage en sortie qui se sert de la structure de données pour générer la sortie aléatoire. Lors des deux passages il faudra rechercher un préfixe rapidement : –dans celui dentrée pour mettre à jour ses suffixes; –dans celui de sortie pour sélectionner aléatoirement un des suffixes possibles. Cela suggère une table de hachage dont les clés sont des préfixes et les valeurs sont les suffixes correspondants.

16 16 Choix de structure de données (4) Nous prendrons un préfixe de deux mots. Le nombre de mots du préfixe ne doit pas affecter la construction. Le programme doit pouvoir manipuler toutes les longueurs de préfixe. État : Le préfixe et lensemble de tous ses suffixes possibles. Que se passe-t-il si une locution apparaît deux fois « je veux lire »? –Représenter cela en plaçant 2 fois « lire » dans la liste des suffixes correspondant; –ou une seule fois avec un compteur associé défini à 2. –La première représentation est plus facile car ajouter un suffixe ne nécessite pas de vérifier dabord sil y est.

17 17 Choix de structure de données (5) Il faut choisir comment représenter les mots. Stocker sous forme de chaîne de caractères individuelles. –Mais cela exige de nombreuses répétitions car certains mots apparaissent plusieurs fois dans un texte. Utiliser une seconde table de hachage de mots uniques. Cela accélérerait également le hachage des préfixes. –Possibilité de comparer des pointeurs au lieu de caractères individuels. –Les chaînes de caractères uniques possèdent des adresses uniques.

18 18 Construction de la structure de données en C Définir des constantes enum { NPREF = 2, /* nombre de mots pour le préfixe */ NHASH = 4093,/* taille de la table de hachage état */ MAXGEN = /* maximum de mot générés */ }; Le préfixe peut être stocké sous forme dun tableau de mots. Les éléments de la table de hachage seront représentés sous forme dun type de données State, en associant la liste de suffixes au préfixe.

19 19 Structure de données en C typedef struct State State; typedef struct Suffix Suffix; struct State { /* préfixe + liste de suffixes */ char *pref[NPREF]; /* mots préfixes */ Suffix *suf; /* liste de suffixes */ State *next; /* le suivant dans la table de hachage */ }; struct Suffix { /* liste des suffixes */ char *word; /* suffixe */ Suffix *next; /* le suivant dans la liste des suffixes */ }; State *statetab[NHASH]; /* table de hachage détats */

20 20 La structure de données

21 21 Nécessité dune fonction de hachage /* hash: calcule la valeur de hachage pour le tableau de chaînes de caractères NPREF */ enum {MULTIPLIER = 31}; unsigned int hash (char *s[NPREF]) { unsigned int h; unsigned char *p; int i; h = 0; for (i = 0; i < NPREF; i++) for (p = (unsigned char *) s[i]; *p != \0; p++) h = MULIPLIER * h + *p; return h % NHASH; }

22 22 Nécessité dune fonction de recherche /* lookup: recherche le préfixe; le crée si nécessaire */ /* retourne le pointeur sil est présent ou créé; NULL sinon */ /* la création ne fait pas de strdup ainsi les chaînes ne changeront pas par la suite */ State * lookup (char *prefix[NPREF], int create) { int i, h; State *sp; h = hash(prefix); for (sp = statetab[h]; sp != NULL; sp = sp->next) { for (i = 0; i < NPREF; i++) if (strcmp(prefix[i], sp->prefix[i]) != 0) break; if (i == NPREF) /* il est trouvé */ return sp; } if ( create) { sp = (State *) malloc(sizeof(State)); for (i = 0; i < NPREF; i++) sp->pref[i] = prefix[i]; sp->suf = NULL; sp->next = statetab[h]; statetab[h] = sp; } return sp; }

23 23 La fonction de recherche Elle neffectue aucune copie des chaînes entrantes lorsquelle crée un nouvel état. Stocke des pointeurs dans sp->pref[]. Les fonctions utilisant lookup doivent garantir que les données ne seront pas écrasées.

24 24 Construction de la table de hachage /* build: lit lentrée, construit la table des préfixes */ void build (char *prefix[NPREF], FILE *f) { char buf[100], fmt[10]; /*crée une chaîne de caractères formatée; les %s pourraient dépasser la capacité de buf */ sprintf(fmt,%%ds,sizeof(buf) - 1); while (fscanf(f,fmt, buf) != EOF) add(prefix, estrdup(buf)); }

25 25 La fonction de construction Lappel particulier à sprintf contourne un problème irritant se posant avec fscanf: –fscanf avec le format % lira le mot suivant délimité par une espace; –donc le mot peut être très long (inhabituel). Solution: créer dynamiquement la chaîne de caractères nécessaires à sprintf. La fonction passe prefix et une copie du mot en entrée à add qui ajoute une nouvelle entrée à la table de hachage.

26 26 La fonction add /* add: ajoute un mot à la liste suffix, met à jour prefix */ void add (char *prefix[NPREF], char *suffix) { State *sp; sp = lookup(prefix,1); /* créer si pas trouvé */ addsuffix(sp, suffix); /* mettre à jour prefix */ memmove(prefix, prefix + 1, (NPREF - 1) * sizeof(prefix[0])); prefix[NPREF-1] = suffix; }

27 27 La fonction add (2) Lappel à memmove est une expression idiomatique pour effectuer des suppressions dans un tableau. Déplace la mémoire pour laisser la place pour un nouveau mot. Utilise la méthode addsuffix. /* addsuffix: ajoute un nouveau suffix à létat courant. suffix ne doit pas changer. */ void addsuffix (State *sp, char *suffix) { Suffix *suf; suf = (Suffix *) emalloc(sizeof(Suffix)); suf->word = suffix; suf->next = sp->suf; sp->suf = suf; }

28 28 Note add: exécute la tâche générale consistant à ajouter un suffixe à un préfixe. addsuffix: traite laction spécifique de limplémentation consistant à ajouter un mot à une liste de suffixes. Le détail dimplémentation daddsuffix peut changer. Donc cest nécessaire den faire une fonction à part, bien quelle ne soit appelée quune seule fois.

29 29 Générer des données en sortie Avec un préfixe donné, on sélectionne lun de ses suffixes au hasard, on limprime, puis on avance le préfixe. Il faut connaître la manière de démarrer et arrêter lalgorithme : –démarrer avec les mots du premier préfixe; –utiliser un marqueur de fin. On peut rajouter un mot dont on est sûr quil napparaîtra dans aucune entrée (NONWORD). char NONWORD[] = \n; /* ne peut apparaître comme un mot réel */ build(prefix,stdin); add(prefix,NONWORD);

30 30 La fonction generate /* generate: produit la sortie, un mot par ligne */ void generate(int nwords) { State *sp; Suffix *suf; char *prefix[NPREF], *w; int i, nmatch; for (i = 0; i < NPREF; i++) /* réinitialise le préfixe initial */ prefix[i] = NONWORD; for (i = 0; i < nwords; i++) { sp = lookup(prefix, 0); nmatch = 0; for (suf = sp->suf; suf != NULL; suf = suf->next) if (rand() % ++nmatch == 0) /* prob = 1/nmatch */ w = suf->word; if (strcmp(w, NONWORD) == 0) break; printf(%s\n,w); memmove(prefix, prefix+1, (NPREF-1) * sizeof(prefix[0])); prefix[NPREF-1] = w; }

31 31 Produit un mot par ligne de sortie. Avec lemploi des chaînes de caractères NONWORD initiale et finale, generate commence et se termine correctement. Permet de sélectionner un élément au hasard lorsque nous ne connaissons pas le nombre total déléments. La variable nmatch fait le décompte du nombre de correspondance au fur et à mesure de la lecture de la liste. Les premières valeurs de Suffix seront les premiers mots du document. La fonction generate (2)

32 32 Programme principal /* markov main: génération de texte aléatoire avec une chaîne de Markov */ int main (void) { int i, nwords = MAXGEN; char *prefix[NPREF]; /* préfixe dentrée courant */ for (i=0; i < NPREF; i++) /* définition du préfixe initial */ prefix[i] = NONWORD; build(prefix, stdin); add(prefix, NONWORD); generate(nwords); return 0; }

33 33 Implémentation en C Donne au programmeur un contrôle complet sur limplémentation. Les programmes tendent à être rapides. Le programmeur C doit faire le gros du travail : –allocation et libération de la mémoire; –création des tables de hachage et des listes chaînées…

34 34 Implémentation en JAVA Java est un langage orienté objet. Il incite à faire particulièrement attention aux interfaces et composantes du programme. Il possède une bibliothèque plus riche que celle de C : –un objet Vector qui fournit un tableau dynamique pour tout type dobjets; –la classe Hashtable qui permet de stocker et dextraire des valeurs dun type particulier à laide dune clé; –etc. Dans notre exemple, les vecteurs de chaînes de caractères constituent le choix idéal pour gérer les préfixes et les suffixes.

35 35 Implémentation en JAVA (2) Il nest pas nécessaire dexpliciter le type State car Hashtable relie implicitement les préfixes au suffixes. La classe Hashtable fournit : –une méthode de mise en place pour stocker une paire de valeur de clé; –une méthode de récupération pour extraire la valeur dune clé. Hashtable h = new Hashtable(); h.put(key, value); Sometype v = (Sometype) h.get(key);

36 36 Implémentation en JAVA (3) Notre implémentation contient 3 classes : –Prefix: pour gérer les mots du préfixe; –Chain: qui lit les données en entrée, construit la table de hachage et génère la sortie; –Markov: qui représente linterface publique contenant le main.

37 37 Classe Markov (en Java) class Markov { static final int MAXGEN = 10000; //maximum de mots générés public static void main(String [] args) throws IOException { Chain chain = new Chain(); int nwords = MAXGEN; chain.build(System.in); chain.generate(nwords); } …

38 38 Classe Chain - variables class Chain { static final int NPREF = 2; // taille de préfixe static final String NONWORD = "\n"; // mot qui ne peut apparaître Hashtable statetab = new Hashtable(); // clé = Prefix, valeur = Vector suffixe Prefix prefix = new Prefix(NPREF,NONWORD); // préfixe initial Random rand = new Random(); …

39 39 Classe Chain – build (suite) // build: construit la table des State à partir de lentrée void build(InputStream in) throws IOException { StreamTokenizer st = new StreamTokenizer(in); st.resetSyntax(); // annule les règles par défaut st.wordChars(0, Character.MAX_VALUE); // parcourir tous les caractères st.whitespace(0, ); // exclure les caractères jusquà lespace while (st.nextToken() != st.TT_EOF) add(st.sval); add(NONWORD); } …

40 40 Classe Chain – add (suite 2) // add: ajoute le mot à la liste des suffixes // Met à jour le préfixe void add(String word) { Vector suf = (Vector) statetab.get(prefix); if (suf == null) { suf = new Vector(); statetab.put(new Prefix(prefix),suf); } suf.addElement(word); prefix.pref.removeElementAt(0); prefix.pref.addElement(word); }

41 41 Classe Chain – generate (fin) // generate : génère les mots en sortie void generate(int nwords) { prefix = new Prefix(NPREF, NONWORD); for (int i = 0; i < nwords; i++) { Vector v = (Vector) statetab.get(prefix); int r = Math.abs(rand.nextInt()) % v.size(); String suf = (String) v.elementAt(r); if (suf.equals(NONWORD)) break; System.out.println(suf); prefix.pref.removeElementAt(0); prefix.pref.addElement(suf); } } // fin de la classe Chain

42 42 Classe Prefix – variables et constructeurs class Prefix { public static final int MULTIPLIER = 31; // pour hashCode public Vector pref; // mots adjacents de NPREFS à partir de lentrée // Constructeur de Prefix: duplique les préfixes existants Prefix(Prefix p) { pref = (Vector) p.pref.clone(); } // Constructeur de prefix: n copies de str Prefix(int n, String str) { pref = new Vector(); for (int i = 0; i < n; i++) pref.addElement(str); } …

43 43 Classe Prefix – hashCode (suite) // hashCode: génère le hachage à partir de tous les mots préfixes public int hashCode() { int h = 0; for (int i = 0; i < pref.size(); i++) h = MULTIPLIER * h + pref.elementAt(i).hashCode(); return h; }

44 44 Classe Prefix – equals (fin) // equals: compare deux préfixes pour les mots identiques. public boolean equals(Object o) { Prefix p = (Prefix) o; for (int i = 0; i < pref.size(); i++) if (!pref.elementAt(i).equals(p.pref.elementAt(i))) return false; return true; } } // fin de la classe Prefix

45 45 Implémentation Java Programme plus concis que en C. Java propose une meilleure séparation des fonctionnalités. Java fournit des outils qui facilitent limplémentation de certaines fonctions (ex : Vector). Pour utiliser Hashtable, nous avons toujours besoin décrire les méthodes hashCode et equals.

46 46 Implémentation C++ Le programme écrit en C est également un programme C++. Un usage approprié de C++ consiste à définir des classes pour les objets du programme. Il est possible dutiliser la bibliothèque standard STL (Standard Template Library). Le standard ISO du C++ inclut la STL.

47 47 Implémentation C++ - STL La STL fournit: –des conteneurs tels que: des vecteurs, des listes, des ensembles; –une famille dalgorithmes fondamentaux de recherche, de tri, dinsertion et de suppression. Elle fonctionne en employant les patrons (template). Les conteneurs sont exprimés sous forme de patrons C++ qui sont instanciés pour des types de données spécifiques : –vector, vector –deque, deque –map >

48 48 Implémentation C++ - Main Les déclarations: typedef deque Prefix; map > statetab; // prefixe ->suffixes Lutilisation de cette structure savère plus pratique que celle du C ou même du Java, parce quil nest pas nécessaire de fournir une fonction hash ou equals. // main : génération de texte aléatoire avec une chaîne de Markov int main(void) { int nwords = MAXGEN; Prefix prefix; // préfixe dentrée courant for (int i = 0; i < NPREF; i++) // initialisation du préfixe add(prefix,NONWORD); build(prefix,cin); add(prefix,NONWORD); generate(nwords); return 0; }

49 49 Implémentation C++ - build et add // build: lit les mots en entrée, construit la table des états void build(Prefix& prefix,istream& in) { string buf; while (in >> buf) add(prefix, buf); } // add: ajoute un mot à la liste des suffixes, met à jour le préfixe void add(Prefix& prefix, const string& s) { if (prefix.size() ==NPREF) { statetab[prefix].push_back(s); prefix.pop_front(); } prefic.push_back(s); } // statetab[prefix]: effectue une opération de recherche

50 50 Implémentation C++ - generate // generate: produit une sortie, un mot par ligne void generate(int nwords) { Prefix prefix; int i; for (i = 0; i < NPREF; i++) // réinitialise le préfixe initial add(prefix, NONWORD); for (i = 0; i < nwords; i++) { vector & suf = statetab[prefix]; const string& w = suf[rand() % suf.size()]; if (w == NONWORD) break; cout << w << "\n"; prefix.pop_front(); // avance prefix.push_back(w); } // Cette version est plus compact que le code C mais il y a un prix à payer // au niveau de la vitesse dexécution.

51 51 Performances Comparaison des différentes implémentations pour un texte comprenant : –42685 mots; –5238 mots distincts; –22482 préfixes; –suffisamment de répétitions de phrases pour quune liste de suffixes possède plus de 400 mots. Le livre des psaumes de la King James Bible. Les versions C et C++ ont été compilées avec des compilateurs optimisés, tandis que lexécution Java dispose de compilateurs de type just-in-time.

52 52 Performances (suite) 250 Mhz R Mips (sec) 400 Mhz Pentium II (sec) Ligne de code source C0,360,30150 Java4,99,2105 C++/STL/deque2,611,270 C++/STL/List1,71,570 Awk2,22,120 Perl1,81,018 Problème avec deque sous Windows.

53 53 Performance Les langages de haut niveau donnent des programmes plus lents que ceux de bas niveau. Des bibliothèques standards comme la STL C++ ou les tableaux associatifs peuvent conduire à du code plus compact et du temps de développement plus court. Comment estimer la perte de contrôle et le volume du programme lorsque le code fourni par le système devient important ? Au fur et à mesure que les outils deviennent compliqués, ils deviennent moins compréhensibles et plus difficiles à maîtriser.

54 54 Leçons Lorsque tout fonctionne, les environnements de programmation riches en ressources (Java, C++ -STL) peuvent être productifs, mais lorsquils échouent, les possibilités de recours sont faibles. Il est important de choisir des algorithmes et des structures de données simples. Il ne faut pas reconstruire la roue. Il est difficile de concevoir complètement un programme et de limplémenter par la suite : –Utiliser des processus itératifs.


Télécharger ppt "Conception et implémentation de logiciels (avec des exemples de B. Kernighan et R. Pike « La programmation en pratique ») Vladimir Makarenkov (Université"

Présentations similaires


Annonces Google