Markov Text Generation
Page web ISIMA
Fichier .org source
Défi de programmation - Markov Text Generation

1. Introduction
Le but de l’exercice est de, à partir d’une phrase donnée, repérer les combinaisons de mots possibles et d’ensuite générer une phrase commençant par une suite de mots donnée, la phrase est générée avec les combinaisons trouvées plus tôt.
2. Approche utilisée
2.1. Description synoptique
A partir de la phrase seed
(la phrase que l’on veut compléter procéduralement), on regarde les d
premiers mots (d est la depth donnée), et on regarde dans la phrase d’exemple les mots qui peuvent suivre.
Si plusieurs mots peuvent suivre on en choisi un aléatoirement.
On répète l’opération jusqu’à avoir une phrase de longueur souhaitée.
2.2. Algorithme
Fonction getWord
:
Prend en entrée une phrase
, un index
, une chaîne de caractères result
, et renvoie le mot numéro index
de la phrase dans result
. Avec index=0
, result
est égal au 1er mot de phrase
.
Count
= 0
wordIndex
= -1
Pour chaque caractères de phrase
:
Count
= 0Tant que le caractère n’est pas un espace :
- Incrémenter
Count
- Passer au caractère suivant
Fin tant que
- Incrémenter
- Si
Count
> 0 :
- Incrémenter wordIndex
- Si
wordIndex
=index
:
Pour chaque
Count
précédents caractères :
- Ajouter le caractère à
result
Fin pour
- Ajouter le caractère à
- Sortir de la fonction
- Incrémenter wordIndex
Fin pour
Fonction wordCount
:
Prend en entrée une phrase
et renvoie le nombre de mot dans cette phrase. Il y a nombre d'espaces + 1
mots dans la phrase.
Count
= 0
Pour chaque caractères de phrase
:
- Si le caractère est un espace :
- Incrémenter
Count
- Incrémenter
Fin pour
Renvoyer Count
+ 1
Fonction map
:
Prend en entrée une phrase
, une depth
, une clé inKey
composé de depth
mots et une valeur de retour inValue
, renvoie dans inValue
le tableau des mots qui suivent la clé à chaque fois que celle-ci est trouvée dans la phrase. La fonction renvoie le nombre de fois où la clé apparait.
Index
= 0
Pour chaque mots (noté mot1
) de la phrase, excluant les depth derniers mots :
- variable
key
: une chaîne de caractères contenantmot1
- variable
value
: une chaîne de caractères vide Pour chaque
depth
-1 suivant mots (notémot2
) :
key
=key
+ « » +mot2
Fin pour
value = mot suivant lesdepth
mots
- Si
key
etinKey
sont identiques :
- Ajouter
value
au tableauinValue
- Incrémenter
Index
- Ajouter
Fin pour
Renvoyer Index
Fonction principale :
t
: le texte d’entrée
d
: la depth souhaitée
l
: la longueur du texte de sortie souhaitée
s
: le début du texte de sortie
Boucle de d
jusqu’à l
:
- variable
key
contenant les depth précédents mots - variable
possibleValues
: tableau de chaîne de caractères - mise à jour de
possibleValues
avec la fonctionmap
- num = valeur de retour de
map
- index = valeur aléatoire entre 0 et num
s
=s
+ « » + élémentindex
depossibleValues
Fin pour
Afficher s
3. Résolution
3.1. Programme
#include <stdlib.h> #include <stdio.h> #include <string.h> #define M 1001 int random_seed = 0; int pick_option_index(int num_of_options ) { random_seed += 7; return random_seed % num_of_options; } // Récupère le n-ième mot de la phrase void getWord(char phrase[], int index, char result[]) { int i, j, count; int len = strlen(phrase); int wordIndex = -1; for (i = 0; i < len; ++i) { count = 0; while (i < len && phrase[i] != ' ') { ++count; ++i; } if (count > 0) { ++wordIndex; if (wordIndex == index) { for (j = i - count; j < i; ++j) { result[j - (i - count)] = phrase[j]; } result[count] = '\0'; return; } } } result[0] = '\0'; } // Compte le nombre de mot dans la phrase (=nbr d'espaces + 1) int wordCount(char phrase[]) { int count = 0; int n = strlen(phrase); for (int i = 0; i<n; i++) { if (phrase[i] == ' ') { count++; } } return count+1; } // A partir d'une chaine de caractere, récupère le mot qui suit // liste de mots si la chaine d'entrée est présente plusieurs fois int map(char phrase[], int depth, char inKey[], char inValue[M][M]) { int index = 0; int max = wordCount(phrase); for (int i=0; i<max-depth; ++i) { char key[M]; char value[M]; getWord(phrase, i, key); for (int j=1; j<depth; j++) { char tmp[M]; getWord(phrase, i+j, tmp); sprintf(key, "%s %s", key, tmp); } getWord(phrase, i+depth, value); if (strcmp(inKey, key) == 0) { strcpy(inValue[index], value); ++index; } } return index; } int main() { // Entrées du programme char spc[1] = {' '}; char t[1001]; // text scanf("%[^\n]", t); // int d; // depth scanf("%d", &d); // int l; // lenght scanf("%d", &l); fgetc(stdin); // char s[1001]; // seed text scanf("%[^\n]", s); // // A partir du texte d'entrée, regarde le mot suivant possible // Recommence jusqu'à avoir la longueur de phrase souhaitée char key[M], tmp[M]; int num; for (int i=d; i<l; ++i) { getWord(s, i-d, tmp); strcpy(key, tmp); char possiblesValues[M][M] = {}; for (int j=1; j<d; ++j) { getWord(s, i-(d-j), tmp); sprintf(key, "%s %s", key, tmp); } num = map(t, d, key, possiblesValues); sprintf(s, "%s %s", s, possiblesValues[pick_option_index(num)]); } printf("%s", s); return 0; }
3.2. Test
A partir du test suivant (markovText.txt) :
one fish is good and no fish is bad and that is it 1 4 one
A partir du texte donnée et d’un texte seed, le programme génère la suite du texte :
one fish is good
4. Comparaison avec une autre solution
A partir de cette solution externe :
#include <stdio.h> #include <string.h> char t[1001], s[1001], dict[500][50][50]; int d, l, ndict, options[500]; int pick(int i){ static int seed = 0; return (seed += 7) % i; } int dictcmp(int a, int b){ for(int i = 0; i < d; i++) if(strcmp(dict[a][i], dict[b][i])) return 1; return 0; } int dictfind(){ int j = 0; for(; dictcmp(j, ndict); j++); return j; } char* read(char **p){ char *q = NULL; for(int i = 0, n; i < d; i++){ if(sscanf(*p, "%s%n", dict[ndict][i], &n) != 1) return NULL; *p += n; if(q == NULL) q = *p; } return q; } void makedict(char *p){ char *q = read(&p); if(q == NULL) return; int j = dictfind(); if(sscanf(p, "%s", dict[j][d + options[j]++]) != 1) return; if(j == ndict) ndict++; makedict(q); } int main(){ scanf("%[^\n]", t); scanf("%d%d\n", &d, &l); scanf("%[^\n]", s); makedict(t); char *p, *p2, *p3 = s; for(;;){ p2 = p3; p3 = read(&p3); if(p3 == NULL) break; p = p2; p3++; l--; } l -= d - 1; while(l--){ p = read(&p); int j = dictfind(); strcat(s, " "); strcat(s, dict[j][d + pick(options[j])]); } puts(s); }
Cette solution propose la création d’un dictionnaire associant un clé (une chaîne de caractères contenant d
mots) à une valeur (une liste de mots). Des fonctions permettent de la lecture et écriture du dictionnaire.
Le programme lit d’abord la phrase afin de construire le dictionnaire et ensuite reconstruit une phrase à partir du dictionnaire construit.
5. Bilan
J’ai compris assez rapidement le principe du problème posé, mais j’ai trouvé l’implémentation plus compliquée que ce que je ne pensais et m’a demandé beaucoup de temps. Je suis conscient que ma solution n’est pas la meilleure d’un point de vue efficacité mais elle fonctionne et j’ai eu du mal à implémenter une solution plus efficace.