Title: Conception%20et%20impl
1Conception 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)
2Introduction
- 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.
3La 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.
4Caracté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
5Les 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
6Problè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?
7Problè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.
8Algorithme 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.
9Algorithme 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 w1 et w2 avec les 2
premiers mots. - Imprimer w1 et w2
- Boucle
- Sélectionner aléatoirement w3, lun des
successeurs du préfixe w1w2 dans le texte. - Imprimer w3.
- Remplacer w1 et w2 par w2 et w3.
- Répéter la boucle.
10Algorithme 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éfixes Suffixes 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 and mystified. obvious. Show (end)
11Algorithme 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.
12Choix 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.
13Choix 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 100
000 mots en entrée pour générer chaque mot.
14Choix 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.
15Choix 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.
16Choix 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.
17Choix 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.
18Construction 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 10000 / 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.
19Structure de données en C
- typedef struct State State
- typedef struct Suffix Suffix
- struct State / préfixe
liste de suffixes / - char prefNPREF / 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 statetabNHASH / table de hachage
détats /
20La structure de données
21Né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 sNPREF)
-
- unsigned int h
- unsigned char p
- int i
- h 0
- for (i 0 i lt NPREF i)
- for (p (unsigned char ) si p ! \0
p) - h MULIPLIER h p
- return h NHASH
22Né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 prefixNPREF, int create)
-
- int i, h
- State sp
- h hash(prefix)
- for (sp statetabh sp ! NULL sp
sp-gtnext) - for (i 0 i lt NPREF i)
- if (strcmp(prefixi, sp-gtprefixi)
! 0) - break
- if (i NPREF) / il est trouvé /
- return sp
-
- if ( create)
- sp (State ) malloc(sizeof(State))
- for (i 0 i lt NPREF i)
- sp-gtprefi prefixi
23La fonction de recherche
- Elle neffectue aucune copie des chaînes
entrantes lorsquelle crée un nouvel état. - Stocke des pointeurs dans sp-gtpref.
- Les fonctions utilisant lookup doivent garantir
que les données ne seront pas écrasées.
24Construction de la table de hachage
- / build lit lentrée, construit la table des
préfixes / - void build (char prefixNPREF, FILE f)
-
- char buf100, fmt10
- /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))
25La 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.
26La fonction add
- / add ajoute un mot à la liste suffix, met à
jour prefix / - void add (char prefixNPREF, 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(prefix0)) - prefixNPREF-1 suffix
27La 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-gtword suffix
- suf-gtnext sp-gtsuf
- sp-gtsuf suf
-
28Note
- 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.
29Gé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)
30La fonction generate
- / generate produit la sortie, un mot par ligne
/ - void generate(int nwords)
-
- State sp
- Suffix suf
- char prefixNPREF, w
- int i, nmatch
- for (i 0 i lt NPREF i) / réinitialise le
préfixe initial / - prefixi NONWORD
- for (i 0 i lt nwords i)
- sp lookup(prefix, 0)
- nmatch 0
- for (suf sp-gtsuf suf ! NULL suf
suf-gtnext) - if (rand() nmatch 0) / prob
1/nmatch / - w suf-gtword
- if (strcmp(w, NONWORD) 0)
- break
- printf(s\n,w)
- memmove(prefix, prefix1, (NPREF-1)
sizeof(prefix0))
31La fonction generate (2)
- 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.
32Programme principal
- / markov main génération de texte aléatoire
avec une chaîne de Markov / - int main (void)
-
- int i, nwords MAXGEN
- char prefixNPREF / préfixe dentrée
courant / - for (i0 i lt NPREF i) / définition du
préfixe initial / - prefixi NONWORD
- build(prefix, stdin)
- add(prefix, NONWORD)
- generate(nwords)
- return 0
33Implé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
34Implé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.
35Implé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)
36Implé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.
37Classe 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)
-
-
38Classe 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()
-
39Classe 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)
-
40Classe 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)
41Classe 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 lt 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
42Classe 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 lt n i)
- pref.addElement(str)
-
-
43Classe 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 lt pref.size() i)
- h MULTIPLIER h pref.elementAt(i).hashC
ode() - return h
44Classe 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 lt pref.size() i)
- if (!pref.elementAt(i).equals(p.pref.e
lementAt(i))) - return false
- return true
-
- // fin de la classe Prefix
45Implé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.
46Implé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.
47Implé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 ltintgt, vector ltstringgt
- deque ltintgt, deque ltstringgt
- map ltint, vector ltstringgt gt
48Implémentation C - Main
- Les déclarations
- typedef dequeltstringgt Prefix
- mapltPrefix, vectorltstringgt gt statetab //
prefixe -gtsuffixes - 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 lt NPREF i) //
initialisation du préfixe - add(prefix,NONWORD)
- build(prefix,cin)
- add(prefix,NONWORD)
- generate(nwords)
- return 0
-
49Implé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 gtgt 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)
- statetabprefix.push_back(s)
- prefix.pop_front()
-
- prefic.push_back(s)
-
- // statetabprefix effectue une opération de
recherche
50Implémentation C - generate
- // generate produit une sortie, un mot par ligne
- void generate(int nwords)
-
- Prefix prefix
- int i
- for (i 0 i lt NPREF i) // réinitialise le
préfixe initial - add(prefix, NONWORD)
- for (i 0 i lt nwords i)
- vectorltstringgt suf statetabprefix
- const string w sufrand() suf.size()
- if (w NONWORD)
- break
- cout ltlt w ltlt "\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
51Performances
- 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.
52Performances (suite)
250 Mhz R100000 Mips (sec) 400 Mhz Pentium II (sec) Ligne de code source
C 0,36 0,30 150
Java 4,9 9,2 105
C/STL/deque 2,6 11,2 70
C/STL/List 1,7 1,5 70
Awk 2,2 2,1 20
Perl 1,8 1,0 18
- Problème avec deque sous Windows.
53Performance
- 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.
54Leç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.