Archives de catégorie : snippet

Anagrammes

En passant par une page de documentation, je me suis attardé sur std::next_permutation. Ça m’a tout de suite donné des idées d’anagrammes. En effet, de pouvoir aussi facilement boucler irréfragablement sur toutes les permutations des lettres d’un mot m’a  suscité l’irrépressible envie de créer un petit détecteur d’anagrammes.

Évidemment, pour détecter un anagramme, il faut d’abord et avant tout avoir une base de données de mots valides pour une langue donnée. Il se trouve que j’ai une telle base de données.  En effet, il y a déjà une vingtaine d’années, j’avais réalisé une petite page web d’aide aux mots-croisés. Pour ce faire j’avais contacté, via un forum, un cruciverbiste-internaute qui avait eu la grande gentillesse de me faire parvenir ce fichier par courriel. C’était un gros fichier à l’époque: presque 3 Mo. En fait, à l’origine c’était même 26 fichiers allant de A.txt à Z.txt. Cruciverbiste-internaute des années ’90 qui se reconnait dans cette histoire, je te remercie encore une fois!

Le corpus en tant que tel date un peu: le mot courriel, par exemple, n’y apparaît même pas. Quoi qu’il en soit, avec ses 280000 entrées composées de noms propres, de substantifs et d’adjectif au singulier comme au  pluriel, de verbes conjugués à toutes les formes, c’est suffisant pour tenter quelques expériences significatives.

En tout cas, c’est amplement suffisant pour tester une première version d’un détecteur d’anagramme. Une version pas très robuste mais qui a le mérite de tester l’utilisation de std::next_permutation.

La première version lit la base de données en insérant dans un std::vector> tous les mots rencontrées. Le vecteur est triée pour permettre une recherche rapide avec std::binary_search

Une fonction ressemblant à ça pourrait par exemple faire l’affaire (j’assume un compilateur C++11 ou plus qui garantit que les objets comme la base de données ne sont pas retournés par copie sur la pile mais bien en utilisant le mécanisme de Copy Elision).

typedef vector<std::string> AnagramDB ;

AnagramDB GetAnagramDB(const std::string& filename) 
{
	std::vector<std::string> words;
	std::ifstream datafile(filename);

    std::string str;
    while (datafile >> str )
	{
		words.push_back(str);
    }

    std::sort(words.begin(), words.end());
	return words;
}
Lecture de la base de données et insertion dans un vecteur trié

Avec un vecteur ainsi trié, on peut rechercher un mot avec grosso-modo la même efficacité que dans un arbre binaire. En imaginant qu’on boucle sur les différentes permutations des lettres d’un mot, on voudra tester pour chaque combinaison si le mot est présent  ou pas dans la base de données. Quelque chose comme ça.

bool isInDatabase(const AnagramDB& database, const std::string& str)
{
	return std::binary_search(database.cbegin(), database.cend(), str);
}
Utilisation de binary_search pour trouver un mot dans une liste triée

Une fois qu’on a une base de données chargée et un mécanisme pour tester si un mot (une combinaison de lettres) est valide (donc présent dans le base de données), il ne reste plus qu’à obtenir un mot à tester, boucler sur toutes ses permutations et vérifier pour chaque anagramme si il correspond à un mot valide dans la langue française (ici représentée par notre fichier de base de données).

On pourrait par exemple lire en boucle sur stdin pour obtenir des mots, qu’on testerait un à la fois. Pour chacun des mots, la liste des anagrammes valides serait envoyée sur stdout, et ainsi de suite. On remarque que pour initier les permutations, le mot est tout d’abord trié en ordre lexicographique, de manière à commencer par le « premier » anagramme.

Ça ressemblerait donc à ça.

int main(int argc, char** argv)
{
	if(argc<=1) 
		return 0;
	std::string str = argv[1];
    auto db = GetAnagramDB(str);
	std::cout << "Word to look for: "; 
	while(std::cin >> str ) 
	{
		std::sort(str.begin(), str.end());
		int num = 0;
		while(std::next_permutation(str.begin(), str.end()) )
		{
			if( isInDatabase(str) ) 
			{
				std::cout << str << std::endl;; 
				num++;
			}
		}
			
		std::cout << "Found : " << num << std::endl; 
		std::cout << "Word to look for: "; 
	 }

	return 0;
}
Boucle naïve pour la recherche d'anagrammes valides basé sur std::next_permutation

Cette boucle et ce programme fonctionne parfaitement comme il se doit. Par contre il souffre de quelques faiblesses:

  • Le nombre de permutations est N! ; pour un mot de 10 lettres, on a déjà plus de 3,5 millions de combinaisons à tester (3628800)
  • Les casses minuscule et majuscules sont considérées comme étant des lettres différentes
  • Les accents et autres diacritiques génèrent des lettres différentes

Dans le prochain épisode sur les anagrammes, on améliorera tout ça en repensant la base de données et en évitant les multiples permutations. On s’affranchira aussi des problèmes de casse et de diacritiques.

Comment enlever les accents des caractères en C++

Souvent lorsqu’on manipule du texte, on cherche à retrouver la forme de base des caractères, c’est-à-dire sans les signes diacritiques. Ce peut être utile pour générer une liste en ordre alphabétique de mots par exemple (on notera que dans l’ordre lexicographique des dictionnaires français, le E, et le É ont la même valeur).

Cet exercice peut être assez complexe car fortement dépendant de l’encodage des caractères (UTF-8, ASCII étendu, etc.) et de la définition donnée à « forme de base« . En effet, d’une langue à l’autre, cette notion peut varier et les diacritiques dans certaines langues donne une lettre à part entière. Par exemple en espagnol, la lettre ñ est une lettre à part qui est classée après le n.

Le remplacement des accents dans une application multi-langues est une tâche complexe et pour se faire, le mieux est d’être aidé par des bibliothèques comme ICU.

En revanche, pour tous les cas relativement fréquents où les caractères sont simplement codés sur un octet en utilisant l’ASCII étendu avec l’encodage ISO-Latin-1, on pourrait utiliser le petit bout de code suivant (inspiré d’une réponse sur Stack Overflow).

char RemoveAccent(char ch) 
{
	static const std::map<char, char> tr =
	{
		{ 'À', 'A' },
		{ 'Á', 'A' },
		{ 'Â', 'A' },
		{ 'Ã', 'A' },
		{ 'Ä', 'A' },
		{ 'Å', 'A' },
		
		{ 'Æ', 'E' },

		{ 'Ç', 'C' },

		{ 'È', 'E' },
		{ 'É', 'E' },
		{ 'Ê', 'E' },
		{ 'Ë', 'E' },

		{ 'Ì', 'I' },
		{ 'Í', 'I' },
		{ 'Î', 'I' },
		{ 'Ï', 'I' },

		{ 'Ð', 'D' },

		{ 'Ñ', 'N' },

		{ 'Ò', 'O' },
		{ 'Ó', 'O' },
		{ 'Ô', 'O' },
		{ 'Õ', 'O' },
		{ 'Ö', 'O' },

		{ '×', 'x' },

		{ 'Ø', '0' },

		{ 'Ù', 'U' },
		{ 'Ú', 'U' },
		{ 'Û', 'U' },
		{ 'Ü', 'U' },

		{ 'Ý', 'Y' },
		{ 'Þ', 'P' },
		{ 'ß', 'S' },

		{ 'à', 'a' },
		{ 'á', 'a' },
		{ 'â', 'a' },
		{ 'ã', 'a' },
		{ 'ä', 'a' },
		{ 'å', 'a' },

		{ 'æ', 'e' },

		{ 'ç', 'c' },

		{ 'è', 'e' },
		{ 'é', 'e' },
		{ 'ê', 'e' },
		{ 'ë', 'e' },

		{ 'ì', 'i' },
		{ 'í', 'i' },
		{ 'î', 'i' },
		{ 'ï', 'i' },

		{ 'ð', 'd' },

		{ 'ñ', 'n' },

		{ 'ò', 'o' },
		{ 'ó', 'o' },
		{ 'ô', 'o' },
		{ 'õ', 'o' },
		{ 'ö', 'o' },

		{ '÷', '/' },

		{ 'ø', '0' },

		{ 'ù', 'u' },
		{ 'ú', 'u' },
		{ 'û', 'u' },
		{ 'ü', 'u' },

		{ 'ý', 'y' },
		{ 'þ', 'p' },
		{ 'ß', 's' }
	};
	
	auto it = tr.find(ch);
	if (it != tr.end())
		return it->second;
	return ch;
}
RemoveAccent()

Chronomètre basique

On a souvent besoin d’un chronomètre multiplateforme. Celle-ci est 100% inline et ne dépend que de ctime (aka time.h). Un simple #include suffit à l’utiliser un peu partout. La classe étant basée sur clock(), la précision n’est pas garantie. Mais c’est souvent amplement suffisant.

Le fichier est disponible ici: chrono.h

#ifndef __CHRONO_H__
#define __CHRONO_H__

#include <ctime>

/**
 * \namespace	Chrono 
 *
 * \brief	A small set of utilities encapsulating timing functions 
**/
namespace Chrono 
{

/**
* \class	Stopwatch
*
* \brief	A stopwatch
*
* \details	Allows to create a stopwatch, start, stop and getting time in various formats.
**/
class Stopwatch
{
public:
 /**
 * \fn	Stopwatch::Stopwatch();
 *
 * \brief	Default constructor. 
 *
**/
inline
Stopwatch();

 /**
 * \fn	void Stopwatch::Start();
 *
 * \brief	Starts the time counter. 
 *
**/
inline
void Start();

  /**
 * \fn	void Stopwatch::Stop();
 *
 * \brief	Stops the time counter. 
 *
**/
inline
void Stop();

  /**
 * \fn	void Stopwatch::Reset();
 *
 * \brief	Resets the time counter to 0 sec.
 *
**/
inline
void Reset();

  /**
 * \fn	double  Stopwatch::GetTimeInMilliseconds();
 *
 * \brief	Gets the time in milliseconds. 
 *
 * \return	The time in milliseconds. 
 *
**/
inline
double  GetTimeInMilliseconds();

 /**
 * \fn	double Stopwatch::GetTimeInSeconds();
 *
 * \brief	Gets the time in seconds. 
 *
 * \return	The time in seconds. 
 *
**/
inline
double  GetTimeInSeconds();

  /**
 * \fn	double Stopwatch::GetTimeInMinutes();
 *
 * \brief	Gets the time in minutes. 
 *
 * \return	The time in minutes. 
 *
**/
inline
double  GetTimeInMinutes();

  /**
 * \fn	double Stopwatch::GetTimeInHours();
 *
 * \brief	Gets the time in hours. 
 *
 * \return	The time in hours. 
 *
**/
inline
double  GetTimeInHours();

  /**
 * \fn	void Stopwatch::GetTime(int &aHours, int &aMinutes, int &aSeconds, int &aMilliseconds);
 *
 * \brief	Gets a time in a formated way
 *
 * \param [in,out]	aHours			a in hours. 
 * \param [in,out]	aMinutes		a in minutes. 
 * \param [in,out]	aSeconds		a in seconds. 
 * \param [in,out]	aMilliseconds	a in milliseconds. 
 *
**/

inline
void    GetTime(int &aHours, int &aMinutes, int &aSeconds, int &aMilliseconds);

private :
  unsigned int mStart;
  unsigned int mAccumulator;
  bool mStopped;
  };
}

inline
Chrono::Stopwatch::Stopwatch() : mAccumulator(0), mStopped(true)
{
}

inline
void Chrono::Stopwatch::Start()
{
  if(!mStopped)
   return;

  mStart = clock() - mAccumulator;
 mStopped = false;
}

inline
void Chrono::Stopwatch::Stop()
{
  if(mStopped)
   return;

  mAccumulator = clock() - mStart;
  mStopped = true;
}

inline
void Chrono::Stopwatch::Reset()
{
  Stop();
  mAccumulator = 0;
}

inline
double Chrono::Stopwatch::GetTimeInMilliseconds()
{
  return GetTimeInSeconds() * 1000.0;
}

inline
double Chrono::Stopwatch::GetTimeInSeconds()
{
  unsigned int lTime;
  if(mStopped)
   lTime = mAccumulator;
  else
   lTime = clock() - mStart;

  return lTime / (double)CLOCKS_PER_SEC;
}

inline
double Chrono::Stopwatch::GetTimeInMinutes()
{
  return GetTimeInSeconds() / 60.0;
}

inline
double Chrono::Stopwatch::GetTimeInHours()
{
  return GetTimeInSeconds() / 3600.0;
}

inline
void Chrono::Stopwatch::GetTime(int &aHours, int &aMinutes, int &aSeconds, int &aMilliseconds)
{
  double lRemainder = GetTimeInSeconds();

  aHours = (int)(lRemainder / 3600.0);
  lRemainder -= aHours;

  aMinutes = (int)(lRemainder / 60.0);
  lRemainder -= aMinutes;

  aSeconds = (int)lRemainder;
  lRemainder -= aSeconds;

  aMilliseconds = (int)lRemainder;
}

#endif // __CHRONO_H__
Contenu du fichier chrono.cpp

Écart type en une passe

On a souvent besoin de calculer quelques statistiques sur un ensemble de données. Si calculer la moyenne en une passe est trivial, le calcul de l’écart type demande un peu d’attention. Ci-dessous, une solution que je poste comme pense-bête.

Le snippet calcule aussi les valeurs min et max.

Code snippet :

std::vector<int> vec;   // Container with values
uint64_t count  = 0 ;     
uint64_t accu   = 0 ;     
uint64_t accu2  = 0 ;
uint64_t min    = -1;
uint64_t max    = 0 ;

for_each(begin(vec), end(vec) [&](int x)
{
    accu += x;
    accu2 +=  x * x;
    min = (std::min)(min, x);
    max = (std::max)(max, x);
    ++count;
});

float average = accu/float(count);
float stdev = sqrt(accu2*count - accu*accu)/count;
Écart type en une passe