#include <iostream>  // cin, cout et cerr
#include <fstream>   // ifstream
#include <sstream>   // istringstream
#include <iterator>  // istream_iterator
#include <string>    // Chaines de caractères de la STL.
#include <map>       // Tableaux associatifs de la STL.
#include <vector>    // Vecteurs de la STL.
#include <cctype>    // ispunct(...)
#include <algorithm> // Algorithmes de la STL.
#include <numeric>   // Algorithmes numériques, comme accumulate(...).

using namespace std ;

// Taille minimum des mots.
const size_t TAILLE = 4 ;

// Type de la paire (mot, nombre d'occurrences du mot).
typedef pair<string, size_t> Paire_dict ;

// Type du map `dict', mot et nombre d'occurrences du mot.
typedef map<string, size_t> Dict ;

// La variable `dict' est globale pour être vue de la fonction `remplir_dict'.
Dict dict ;

// Vecteur des différentes distributions calculées dans le projet.
vector<size_t> distribution ;

// Les caractères particuliers de notre belle langue sont inchangés, les caractères de
// de ponctuation sont remplacés par un espace ; ceux qui restent sont mis en minuscule.
inline char
recoder (const char & arg)
{
	static string bizarre("ÆæŒœÀàÁáÂâÇçÈèÉéÊêËëÎîÏïÔôÖöÙùÛû") ;
	if ( bizarre.find(arg) != string::npos ) {
		return arg ; }
	else if ( ispunct(arg) ) {
		return ' ' ; }
	else {
		return tolower(arg) ; }
}
// Seuls les mots de plus de `TAILLE' lettres sont inclus dans le map `dict'.
inline void
remplir_dict(const string & arg)
{
	if ( arg.size() > TAILLE ) {
		++ dict[arg] ; }
}
// Fonction de comparaison pour trier en ordre décroissant sur le nombre d'occurrences.
inline bool
cmp_trier_occurrences(const Paire_dict & arg1, const Paire_dict & arg2)
{
	return arg1.second > arg2.second ;
}
// Fonction de comparaison pour le calcul du nombre d'occurrences maximum.
bool
cmp_max_occurrences(const Paire_dict & max, const Paire_dict & arg)
{
	return max.second < arg.second ;
}
// Fonction pour établir la distribution par occurrence.
inline void
incrementer_par_occurrences(const Paire_dict & arg)
{
	++ distribution[arg.second] ;
}
// Fonction de comparaison pour le calcul de la taille maximale d'un mot.
bool
cmp_max_taille(const Paire_dict & max, const Paire_dict & arg)
{
	return max.first.size() < arg.first.size() ;
}
// Fonction pour établir la distribution par taille.
inline void
incrementer_par_taille(const Paire_dict & arg)
{
	++ distribution[arg.first.size()] ;
}

int
main()
{
	{
		// La chaîne de caractères `tout' contient la totalité du fichier. La syntaxe 
		// `string tout(...,...) ;' ne convient pas : c'est la déclaration de la fonction 
		// `total' qui retourne une `string'. Les parenthèses qui ont été rajoutées ne
		// sont donc pas surnuméraires. L'utilisation d'un `istream_iterator' ne convient 
		// pas parce que le flux d'entrée est mis en forme (suppression des espaces, etc.).
		string tout((istreambuf_iterator<char>(cin)), istreambuf_iterator<char>()) ;

		// Transformation de toute la chaîne en recodant ses caractères au moyen de la 
		// fonction `recoder'.
		transform(tout.begin(), tout.end(), tout.begin(), recoder) ;

		istringstream mots(tout) ; // La chaîne devient un fichier en lecture en mémoire.
		// Remplissage du dict ; en entrée, les mots qui proviennent d'un itérateur 
		// qui extrait les mots du fichier `mots', en sortie le dict renseigné par 
		// la fonction `remplir'.
		for_each(istream_iterator<string>(mots), istream_iterator<string>(), remplir_dict) ;
		if ( dict.empty() ) {
			cerr << "Le texte ne contient aucun mot de plus de " << TAILLE << " lettres." ;
			exit(1) ; }
	} // Sortie du bloc : `tout' et `mots' sont détruits.

	// Impression des vingt premiers mots qui reviennent le plus souvent.
	// Les paires (mots, nombre d'occurrences) du dict sont recopiés dans un
	// vecteur.
	vector< Paire_dict > compte ;
	copy(dict.begin(), dict.end(), back_inserter(compte)) ;
	// Le vecteur est trié sur le nombre d'occurrences grâce à la fonction
	// `cmp_trier_occurrences'.
	sort(compte.begin(), compte.end(), cmp_trier_occurrences) ;
	cout << "Les vingt premiers mots de " << TAILLE+1 << " lettres et plus qui "
		"reviennent le plus souvent." << endl ;
	for ( size_t i = 0 ; i < min(compte.size(), size_t(20)) ; ++ i ) {
		cout << compte[i].second << ' ' << compte[i].first << endl ; }

	// Distribution de la fréquence de l'apparition des mots.
	// Tout d'abord, on recherche la plus grande fréquence d'apparition.
	size_t max = max_element(dict.begin(), dict.end(), cmp_max_occurrences)->second ;
	// On ajuste la dimension du vecteur et on initialise chaque élément de celui-ci.
	distribution.assign(max+1, 0) ;
	// On renseigne `distribution'.
	for_each(dict.begin(), dict.end(), incrementer_par_occurrences) ;
	cout << "Distribution de la fréquence de l'apparition des mots de " << TAILLE+1 <<
		" lettres et plus." << endl ;
	size_t borne_sup = min(max+1, size_t(21)) ;
	for ( size_t i = 1 ; i < borne_sup ; ++ i ) {
		if ( distribution[i] != 0 ) {
			cout << i << '\t' << distribution[i] << endl ; } }
	size_t total = accumulate(distribution.begin()+borne_sup, distribution.end(), 0) ;
	if ( total != 0 ) {
		cout << "de " << borne_sup << " à " << max << '\t' << total << endl ; }

	// Distribution de la fréquence de la longueur des mots ; traitement comparable.
	max = max_element(dict.begin(), dict.end(), cmp_max_taille)->first.size() ;
	distribution.clear() ; distribution.assign(max+1, 0) ;
	for_each(dict.begin(), dict.end(), incrementer_par_taille) ;
	cout << "Distribution de la fréquence de la longueur des mots de " << TAILLE+1 <<
		" lettres et plus." << endl ;
	for ( size_t i = TAILLE+1 ; i <= max ; ++ i ) {
		if ( distribution[i] != 0 ) {
			cout << i << '\t' << distribution[i] << endl ; } }
	return 0 ;
}
