Exercices

Nous allons réaliser de plusieurs manières l’exercice suivant: établir des statistiques sur la fréquence de chaque caractère dans un fichier texte.

Pour simplifier:

  • On considèrera que tous les fichiers sont codés avec le jeu ISO-8859-1 (donc pas forcément le mode de codage par défaut de la plate-forme)

  • On ne tiendra pas compte des équivalences usuelles (qui voudraient par ex. que e et é soient décomptés ensembles).

Utilisation de lambda-expression

Voici un début de code simple pour ouvrir un fichier texte:

public static void main(String[] args) throws Exception {
        String fileName = args[0] ;
        // plusieurs simplifications ici
        // pas de repertoire dans getPath
        Path path = FileSystems.getDefault().getPath(fileName) ;
        // simplification : pas de specifications du codage du fichier texte (à eviter!)
        BufferedReader bufr = Files.newBufferedReader(path, Charset.forName("ISO-8859-1"));
        ....

à partir de ce code:

  • créer une HashMap qui conservera le nombre d’occurences pour un caractère donné

  • lire chaque ligne du texte et pour chaque ligne récupérer les caractères un à un. Mettre à jour la table des statistiques au moyen de la méthode merge de HahsMap

  • en fin de lecture, créer une liste de Map.Entry et la trier au moyen de Collections.sort : la fonction de tri doit ordonner selon le nombre d’occurence.

Proposition de solution : une première version

Comparaisons de techniques de parcours

L’objectif de l’exercice est de comparer les performances de deux parcours de données.

On veut savoir combien de caractères peuvent être utilisés comme élément d’identifiant java: utiliser Character.isJavaIdentifierPart(int codePoint).

on va donc générer une liste de nombres (codePoints) de 2 à Integer.MAX_VALUE et on va regarder si le nombre correspond à une partie d’identifiant.

  • première technique: faire une boucle classique

  • deuxième technique: gérer un Stream généré par IntStream.range(2, Integer.MAX_VALUE)

Comparer avec différentes options (comme parallel()+).

Les statistiques de caractères dans un fichier (version avec stream)

Reprendre l’exercice des statistiques de caractères dans un fichier.

Initialiser votre lecture par : Files.lines(path, Charset.forName("ISO-8859-1"))

chercher à coder l’ensemble avec des Streams

Exemples

Utilisation de lambda-expression

import java.io.BufferedReader;
import java.nio.charset.Charset;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

public class CharStats {
    public static void main(String[] args) throws Exception {
        String fileName = args[0] ;
        // plusieurs simplifications ici
        // pas de repertoire dans getPath
        Path path = FileSystems.getDefault().getPath(fileName) ;
        // simplification : pas de spécifications du codage du fichier texte (à eviter!)
        BufferedReader bufr = Files.newBufferedReader(path, Charset.forName("ISO-8859-1"));
        Map<Character,Integer> map = new HashMap<>() ;
        String line ;
        // parcours naif: on peut faire mieux avec des streams
        while(null != (line = bufr.readLine())) {
            for( int ix = 0 ; ix < line.length(); ix++) {
                // autoboxing!
                Character character = line.charAt(ix) ;
                // autoboxing encore!
                map.merge(character,1,(x,y) -> x+y );
            }
        }
        Set<Map.Entry<Character,Integer>> set = map.entrySet() ;
        System.out.println(set);
        List<Map.Entry<Character,Integer>> list = new ArrayList<>() ;
        list.addAll(set) ;
        Collections.sort(list, (x,y)-> x.getValue().compareTo(y.getValue())) ;
        System.out.println(list);
    }
}

Comparaisons de techniques de parcours

import java.util.stream.IntStream;


public class TestJavaChars {
    public static void main(String[] args) {
        long time = System.currentTimeMillis() ;
        int count = 0 ;
        for(int ix=2; ix < Integer.MAX_VALUE; ix++) {
            //curiosité: certains Emojis sont rejetés par ce test
            // alors qu'ils sont acceptés par le compilateur: le mystère reste entier!
            if(Character.isJavaIdentifierPart(ix)){
                count++ ;
            }
        }
        System.out.println(" count " + count);
        System.out.println(System.currentTimeMillis() -time);
        time = System.currentTimeMillis() ;
        long number =
                IntStream.range(2, Integer.MAX_VALUE).
                        parallel().
                        filter(Character::isJavaIdentifierPart).
                        //sorted().
                        //peek(x-> { System.out.println( "\\u"+Integer.toHexString(x) + " : " +
                         //Arrays.toString(Character.toChars(x))) ; }).

                                count() ;
        ;
        System.out.println("found " + number);
        System.out.println(System.currentTimeMillis() -time);

    }
}
import java.nio.charset.Charset;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.HashMap;
import java.util.IntSummaryStatistics;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;

/**
 * Code en hommage à Robert Escarpit,
 * inventeur de la littératronique,
 * en mémoire de sa présentation (à l'université Poldave) de la thèse sur la fréquence du "e" muet
 * chez les écrivains militaires français !
 *
 */

public class StreamJavaChars {
    public static void main(String[] args) throws Exception{
        String fileName = args[0] ;
        // plusieurs simplifications ici
        // pas de repertoire dans getPath
        Path path = FileSystems.getDefault().getPath(fileName) ;
        // pour être plus général une Map<Integer,Integer> prenant un codepoint comme clef
        // aurait été plus général
         Map<Character, Integer> map = new HashMap<>() ;
        /* */
        long numberOfChar = Files.lines(path, Charset.forName("ISO-8859-1")).
                flatMapToInt(String::chars).
                  //un peu juste pour des caractères qui sortent de la plage char
                  // collect (Collectors.toMap : incompréhensible à ce stade
                peek(thatInt-> map.merge((char)thatInt,1, (x,y) -> x+y)).
                count() ;
        map.entrySet().stream().
                sorted((x,y)-> x.getValue().compareTo(y.getValue())).
                forEach(System.out::println) ;
        System.out.println("total count" + numberOfChar);
        /*  autre version  (obscure! Ce genre de code doit être soigneusement documenté!
         en fait il vaut mieux s'en abstenir! )*/
         IntSummaryStatistics stats = Files.lines(path, Charset.forName("ISO-8859-1")).
                 // chaque ligne donne lieu à un flot de "ints"
                flatMapToInt(String::chars).
                // mais on préfère un flot de "Character" (noter l'autoboxing)
                mapToObj(x-> (char) x).
                // on crée une Map <Character,Integer>: clef est le caractère dans le flot
                 // la valeur initiale est 1; la mise à jour se fait par une incrémentation
                collect(Collectors.toMap(Function.identity() ,ch -> 1 , (x, y) -> x+1)).
                // dont on extrait le contenu
                entrySet().
                 // qu'on fourni à un flot de Map.Entry
                 stream().
                 // trié par le nombre d'occurences
                 sorted((x,y)-> x.getValue().compareTo(y.getValue())).
                 // on imprime
                 peek(System.out::println).
                 // et on établit des statistiques
                 collect(Collectors.summarizingInt(Map.Entry::getValue)) ;
        System.out.println(stats);
               /* */

    }
}