janvier
2012
La question rhétorique du jour :
Qui n’a jamais demandé de l’aide sur un forum ou un chat?
Lors qu’on pose une question sur un forum ou un chat, on a peut-être notre réponse. Mais, car mais il y a, mais il manque beaucoup d’information, de conseil, d’explication. Tout ce qui fait la différence entre une réponse et une bonne réponse pour moi.
C’est pour cela que je vais prendre aujourd’hui un cas concret et lui donnée une bonne réponse au quêteur et à vous.
Et réaliser en 3 étapes un bon code !
Le cas provient du chat.developpez.com du pseudo « marwazmandar » le 8 janvier 2012.
Je vous introduit le sujet :
Dans le langage Java, réaliser un programme qui lit un fichier et compte le nombre occurrence de chaque mots.
Enregistrez les mots et leur nombres d’occurrence dans un nouveau fichier.
C’est un bête exercice de programmation pour apprendre le Java, donc.
Étape 1 : États des lieux du code et mise au propre
Pour ce cas, nous allons partir de cette explication et du code fournit par le quêteur en début de discutions :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | package chat.marwazmandar.wordcount.firstversion; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StreamTokenizer; /** * * @author marwa */ public class Main { public static String []lire_doc(String lex){ int a,j=1; String [] tab_mot=new String [10]; try{ InputStream ips=new FileInputStream (lex); InputStreamReader ipsr=new InputStreamReader (ips); BufferedReader br=new BufferedReader(ipsr); StreamTokenizer st =new StreamTokenizer(br); while((a=st.nextToken())!=StreamTokenizer.TT_EOF) { if(a==StreamTokenizer.TT_WORD){ tab_mot[j]=st.sval; j++;} }}catch(Exception e){System.out.println(e.getMessage());} return tab_mot; } public static void main(String[] args) throws IOException { String [] tab=new String [10]; tab=lire_doc("c:\\indexation\\marwa.txt"); for (int i=0;i<tab.length;i++){System.out.println(tab[i]);} }} |
Dans une premier temps, on va faire un bilan des choses positives et des choses négatives :
- Le document de travail n’est pas indenté
- Les noms de variables ne sont pas clair
- Le code fonctionne
Note : J’aurai d’autres remarque au cours du développement.
Si on met au propre le code, on obtient :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | package chat.marwazmandar.wordcount.firstversion; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StreamTokenizer; /** * * @author marwa */ public class Main { public static String[] lire_doc(String path) { int ttypeToken, indexTabMot = 1; String[] tabMot = new String[10]; try { InputStream inputStream = new FileInputStream(path); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); StreamTokenizer streamTokenizer = new StreamTokenizer(bufferedReader); while ((ttypeToken = streamTokenizer.nextToken()) != StreamTokenizer.TT_EOF) { if (ttypeToken == StreamTokenizer.TT_WORD) { tabMot[indexTabMot] = streamTokenizer.sval; indexTabMot++; } } } catch (Exception e) { System.out.println(e.getMessage()); } return tabMot; } public static void main(String[] args) throws IOException { String[] tab = new String[10]; tab = lire_doc("c:\\indexation\\marwa.txt"); for (int i = 0; i < tab.length; i++) { System.out.println(tab[i]); } } } |
Maintenant qu’on a un document de travail propre, on va voir ce que ça donne en exécution :
null
je
suis
marwa
zmandar
marwa
zmandar
habite
à
nabeul
On a donc la liste des mots affiché dans la console.
On remarque la présence d’un null au début. Celui-ci est présent car l’index « indexTabMot » commence à 1 et non à 0.
On voit aussi que tout est dans des tableaux de taille fixe. Ce qui peu et va poser problème à un moment ou à un autre.
Étape 2 : Modification et Améliorations
Je recommande l’utilisation de « List » à la place de tableau dès que la taille est variable.
On s’occupe donc de deux problème à la fois !
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | package chat.marwazmandar.wordcount.firstversion; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StreamTokenizer; import java.util.ArrayList; import java.util.List; /** * * @author Patrick Kolodziejczyk */ public class Main { public static List readFile(String path) { List worldList = new ArrayList(); try { InputStream inputStream = new FileInputStream(path); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); StreamTokenizer streamTokenizer = new StreamTokenizer(bufferedReader); // On déclare là où la variable est utilisé et à une valeur ayant du sense dans le contexe int ttypeToken = StreamTokenizer.TT_EOF; while ((ttypeToken = streamTokenizer.nextToken()) != StreamTokenizer.TT_EOF) { if (ttypeToken == StreamTokenizer.TT_WORD) { worldList.add(streamTokenizer.sval); } } } catch (Exception e) { System.out.println(e.getMessage()); } return worldList; } public static void main(String[] args) throws IOException { // On déclare à la valeur voulu List worldList = readFile("C:\\devel\\developpez\\chat.marwazmandar.wordcount\\res\\marwa.txt"); for (String word : worldList) { System.out.println(word); } } } |
Notez que je passe tout le code en anglais, c’est une habitude à prendre. L’API est en anglais, on code en anglais.
Les commentaire sont en français seulement pour le cadre de l’exemple.
Il est toujours important de partir de la logique du quêteur, on va donc pour suivre le travail.
On va ajouter deux nouvelles méthodes l’une pour compter l’autre pour enregistrer.
Je vais aussi ajouter une méthode pour sélectionner le fichier, pour rendre le tout utilisable par tous.
Pour cela il va nous falloir, plusieurs choses :
Savoir gérer une « Map » :
FAQ : Structure de données
Savoir créer et écrire dans un fichier :
FAQ : Comment écrire à la fin d’un fichier ?
Dans le code je vais subdiviser ces taches en fonction. Pour pouvoir le réutiliser si nécessaire.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 | package chat.marwazmandar.wordcount.firstversion; import java.io.BufferedReader; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StreamTokenizer; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.swing.JFileChooser; /** * * @author Patrick Kolodziejczyk */ public class Main { public static List readFile(String path) { List wordList = new ArrayList(); try { InputStream inputStream = new FileInputStream(path); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); StreamTokenizer streamTokenizer = new StreamTokenizer(bufferedReader); // On déclare là où la variable est utilisé et à une valeur ayant du sense dans le contexe int ttypeToken = StreamTokenizer.TT_EOF; while ((ttypeToken = streamTokenizer.nextToken()) != StreamTokenizer.TT_EOF) { if (ttypeToken == StreamTokenizer.TT_WORD) { wordList.add(streamTokenizer.sval); } } } catch (Exception e) { System.out.println(e.getMessage()); } return wordList; } public static void main(String[] args) throws IOException { // On déclare à la valeur voulu List wordList = readFile(selectFile()); Map map = countWordList(wordList); writeCountedWordIntoFile(map); } private static String selectFile() { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showOpenDialog(null); if(returnVal == JFileChooser.APPROVE_OPTION) { return chooser.getSelectedFile().getAbsolutePath(); } return null; } private static Map countWordList(List wordList) { Map wordCounted = new HashMap(); for (String wordToCount : wordList) { if(!wordCounted.containsKey(wordToCount)){ Integer count = countWordInList(wordToCount,wordList); wordCounted.put(wordToCount, count); } } return wordCounted; } private static Integer countWordInList(String wordToCount, List wordList) { int count = 0 ; for (String word : wordList) { if(word.equals(wordToCount)){ count++; } } return new Integer(count); } private static void writeCountedWordIntoFile(Map map){ FileWriter writer = null; try{ writer = new FileWriter("stats.txt", false); for (String key : map.keySet()) { String text = key+" : "+map.get(key)+"\n"; writer.write(text,0,text.length()); } writer.flush(); writer.close(); }catch(IOException ex){ ex.printStackTrace(); } } } |
Le code fonction, le quêteur a enfin ce qu’il voulait. Mais, on ne doit pas s’arrêter ici. Pas en si bon chemin !
Étape 3 : Refonte du code
C’est un bon code, il fonctionne, mais pourquoi n’est il pas « Bon ».
Il ne se base pas sur une programmation orienté objet. Tout est en statique.
On va donc tout reprendre avec une interface IWordCounter :
1 2 3 4 5 6 7 8 9 10 |
Deux fonctions saveCountInFile pour deux comportements :
- Enregistrer le compte de tous les mots lu par l’instance du compter
- Enregistrer la liste fournit
D’un point de vue découpage fonctionne cela devrait-être fait par une autre classe que le compteur. Un classe « FileCountReporter » par exemple.
Je vous laisse voir ma bonne solution au problème :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | package chat.marwazmandar.wordcount; import java.io.File; import javax.swing.JFileChooser; public class MyMain { private static File selectFile() { JFileChooser chooser = new JFileChooser(); int returnVal = chooser.showOpenDialog(null); if(returnVal == JFileChooser.APPROVE_OPTION) { return chooser.getSelectedFile(); } return null; } public static void main(String[] args) { WordCounter myCounter = new WordCounter(); File file = selectFile(); myCounter.countWordInFile(file); myCounter.saveCountInFile(new File("report.txt")); } } |
1 2 3 4 5 6 7 8 9 10 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | package chat.marwazmandar.wordcount; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.StreamTokenizer; import java.util.HashMap; import java.util.Map; public class WordCounter implements IWordCounter { private Map mapCountReccord = new HashMap(); @Override public Map countWordInFile(File file) { Map countMap = new HashMap(); try { InputStream inputStream = new FileInputStream(file); InputStreamReader inputStreamReader = new InputStreamReader(inputStream); BufferedReader bufferedReader = new BufferedReader(inputStreamReader); StreamTokenizer streamTokenizer = new StreamTokenizer(bufferedReader); // On déclare là où la variable est utilisé et à une valeur ayant du sense dans le contexe int ttypeToken = StreamTokenizer.TT_EOF; while ((ttypeToken = streamTokenizer.nextToken()) != StreamTokenizer.TT_EOF) { if (ttypeToken == StreamTokenizer.TT_WORD) { this.addWordInMap(countMap,streamTokenizer.sval); } } } catch (Exception e) { System.out.println(e.getMessage()); } this.addCountInMapReccord(countMap); return countMap; } private void addCountInMapReccord(Map countMap) { for (String word : countMap.keySet()) { if(this.mapCountReccord.containsKey(word)){ Integer count = countMap.get(word) + this.mapCountReccord.get(word); this.mapCountReccord.put(word, count); }else{ this.mapCountReccord.put(word, countMap.get(word)); } } } private void addWordInMap(Map countMap, String word) { if(countMap.containsKey(word)){ Integer count = countMap.get(word) + 1; countMap.put(word, count); }else{ countMap.put(word, new Integer(1)); } } @Override public void saveCountInFile(File file) { this.saveCountInFile(this.mapCountReccord, file); } @Override public void saveCountInFile(Map map, File file) { FileWriter writer = null; try{ writer = new FileWriter(file, false); for (String key : map.keySet()) { String text = key+" : "+map.get(key)+"\n"; writer.write(text,0,text.length()); } writer.flush(); writer.close(); }catch(IOException ex){ ex.printStackTrace(); } } } |
Bien que, je suis pour le code commenté. Je reste conviencu qu’un code doit être lisible sans.
Les conseils :
Une bonne documentation est toujours utile.
En Java on a la Javadoc !
http://docs.oracle.com/javase/6/docs/api/
La prochaine version est bientôt un état de fait aussi :
http://docs.oracle.com/javase/7/docs/api/
Ce qui n’est pas utile est indispensable.
Respecter une convention de dommage ou une structure dans son programme n’est pas utile et c’est indispensable.
Je vous conseil la lecture de ce pdf sur les convention pour Java :
ftp://ftp-developpez.com/cyberzoide/java/JavaStyle.pdf
De même une bonne documentation est importante ! Ce n’est pas parce qu’un document est écrire par une personne compétente qu’il est à jour.
Par exemple, le quêteur du jour utilise le document suivant :
Cours Java
J’ai contacté l’auteur :
« Le document en question était encore en jdk 1.4, mais je l’ai mis à jour ; je vais mettre en ligne la nouvelle version, et je vous la communique (elle est basée essentiellement sur le jdk 1.5) » S. Rosmorduc
Je mettrai à jour ce billet si j’ai la nouvelle version.
Un code donne en réponse sur un forum ne fonctionne pas forcement en particulier sur les forums non spécialisés.
Les soucres d’informations utilisées
Les soucres d’informations utilisées :
http://docs.oracle.com/javase/6/docs/api/java/io/InputStreamReader.html
http://docs.oracle.com/javase/6/docs/api/java/io/BufferedReader.html
http://docs.oracle.com/javase/6/docs/api/java/io/StreamTokenizer.html
http://docs.oracle.com/javase/6/docs/api/java/io/InputStream.html
http://java.developpez.com/faq/java/?page=sommaire
http://docs.oracle.com/javase/6/docs/api/java/lang/Integer.html