www.gecif.net  

Génération de questions pour QCM grâce à Prolog

 

Principe de base de la génération de QCM avec Prolog :

On part d'un fichier texte au format question;réponse :

la tension;en volt
le courant;en ampère
la puissance;en watt
l'énergie;en joule
la résistance;en ohm
la longueur;en mètre

On désire obtenir une série de questions de la forme "En quelle unité se mesure la tension ?" (réponse : en volt) et de la forme "Quelle grandeur physique se mesure en volt ?" (réponse : la tension), et ce en utilisant chacune des 6 lignes du fichier question;réponse dans les deux sens.

Chaque question devra proposer systématiquement la bonne réponse ainsi que 3 mauvaises réponses prises parmi les 5 autres réponses, en utilisant toutes les combinaisons possibles.

Pour simplifier en un premier temps appelons a,b,c,d,e et f les 6 lignes du fichiers question;réponse :

a
b
c
d
e
f

La question f sera posée plusieurs fois en proposant à chaque fois 3 mauvaises réponses prise dans l'ensemble [a,b,c,d,e].

Demandons à Prolog toutes les listes à 3 éléments prises dans la liste à 5 éléments [a,b,c,d,e], et ce sans répétitions. La réponse est une liste de 10 listes :

[a,b,c]
[a,b,d]
[a,b,e]
[a,c,d]
[a,c,e]
[a,d,e]
[b,c,d]
[b,c,e]
[b,d,e]
[c,d,e]

La question f sera donc posée 10 fois en proposant systématiquement la réponse f ainsi que les 3 mauvaises réponses indiquées par Prolog.

En ajoutant f au début de chacune des 10 listes on obtient les 10 questions à poser, avec la bonne réponse en tête de liste et avec le roulement des 3 mauvaises réponses en queue de liste :

[f,a,b,c]
[f,a,b,d]
[f,a,b,e]
[f,a,c,d]
[f,a,c,e]
[f,a,d,e]
[f,b,c,d]
[f,b,c,e]
[f,b,d,e]
[f,c,d,e]

Chaque liste à 4 éléments doit donner un paragraphe à 4 lignes. En utilisant les listes générées par Prolog le fichier question;réponse d'origine doit donc être converti en :

la longueur;en mètre
la tension;en volt
le courant;en ampère
la puissance;en watt

la longueur;en mètre
la tension;en volt
le courant;en ampère
l'énergie;en joule

la longueur;en mètre
la tension;en volt
le courant;en ampère
la résistance;en ohm

la longueur;en mètre
la tension;en volt
la puissance;en watt
l'énergie;en joule

la longueur;en mètre
la tension;en volt
la puissance;en watt
la résistance;en ohm

la longueur;en mètre
la tension;en volt
l'énergie;en joule
la résistance;en ohm

la longueur;en mètre
le courant;en ampère
la puissance;en watt
l'énergie;en joule

la longueur;en mètre
le courant;en ampère
la puissance;en watt
la résistance;en ohm

la longueur;en mètre
le courant;en ampère
l'énergie;en joule
la résistance;en ohm

la longueur;en mètre
la puissance;en watt
l'énergie;en joule
la résistance;en ohm

Pour convrtir le fichier de départ possédant 6 lignes question;réponse en ce fichier possédant 10 paragraphes de 4 lignes chacun il faut utiliser la requête suivante, composée d'une expression régulière de recherche et d'un motif de remplacement.

Expression régulière de recherche (sélectionne les 6 lignes en les nomant $1 à $6) :

(.*)
(.*)
(.*)
(.*)
(.*)
(.*)

Pour obtenir cette première expression régulière automatiquement il suffit de remplacer chaque ligne du fichier d'origine par (.*). Dans le cas d'un petit fichier on peut biensûr obtenir cette expression régulière par une succession de copier/coller. Dans le cas d'un fichier possédant plusieurs centaines de lignes on duplique le fichier puis en utilisant les expressions régulière on remplace (.*)\n (chaque ligne) par (.*)\n. Cela permet par exemple d'obtenir 100 fois la ligne (.*) à partir d'un fichier de 100 lignes (sans utiliser de boucle for ni de compter le nombre de copier/coller à faire).

Motif de remplacement (réorganise les lignes en paragraphe en suivant les listes générés par Prolog) :

$6
$1
$2
$3

$6
$1
$2
$4

$6
$1
$2
$5

$6
$1
$3
$4

$6
$1
$3
$5

$6
$1
$4
$5

$6
$2
$3
$4

$6
$2
$3
$5

$6
$2
$4
$5

$6
$3
$4
$5

Pour obtenir le motif de remplacement automatiquement il faut remplacer dans les listes de Prolog :

a par $1
b par $2
c par $3
d par $4
e par $5
f par $6

On obtient :

[$6,$1,$2,$3]
[$6,$1,$2,$4]
[$6,$1,$2,$5]
[$6,$1,$3,$4]
[$6,$1,$3,$5]
[$6,$1,$4,$5]
[$6,$2,$3,$4]
[$6,$2,$3,$5]
[$6,$2,$4,$5]
[$6,$3,$4,$5]

Remarque : si les éléments de départ dans la liste en Prolog sont $1 $2 $3 $4 $5 et $6 à la place de a b c d e et f on obtient directement cette liste.

Ensuite dans cette liste générée par Prolog il faut remplacer :

[ par rien
] par retour à la ligne
, (virgule) par retour à la ligne

On obtient alors automatiquement le motif de remplacement.

Après remplacement, le fichier d'origine des question;réponse est converti en :

f
a
b
c

f
a
b
d

f
a
b
e

f
a
c
d

f
a
c
e

f
a
d
e

f
b
c
d

f
b
c
e

f
b
d
e

f
c
d
e

Pour connaître les 10 listes des mauvaises réponses de la question e on demande à Prolog toutes les listes à 3 éléments prises dans la liste à 5 éléments [a,b,c,d,f].

Puis on ajoute e au début de chacun des 10 listes obtenues.

Pour connaitre les 10 listes des mauvaises réponses de la question d on demande à Prolog toutes les listes à 3 éléments prises dans la liste à 5 éléments [a,b,c,f,e].

Pour connaitre les 10 listes des mauvaises réponses de la question c on demande à Prolog toutes les listes à 3 éléments prises dans la liste à 5 éléments [a,b,f,d,e].

Pour connaitre les 10 listes des mauvaises réponses de la question b on demande à Prolog toutes les listes à 3 éléments prises dans la liste à 5 éléments [a,f,c,d,e].

Pour connaitre les 10 listes des mauvaises réponses de la question a on demande à Prolog toutes les listes à 3 éléments prises dans la liste à 5 éléments [f,b,c,d,e].

Mais l'idéal est que Prolog nous génère directement les 60 listes (10 listes commençant par chacune des lettres).

En utilisant flatten et select on peut obtenir les 60 listes d'un coup :

findall(L3,(select(X, [$1,$2,$3,$4,$5,$6], L1),liste(3,L1,L2),flatten([[X],L2],L3)),L4),afficher_liste(L4).

et le résultat est :

[$1,$2,$3,$4]
[$1,$2,$3,$5]
[$1,$2,$3,$6]
[$1,$2,$4,$5]
[$1,$2,$4,$6]
[$1,$2,$5,$6]
[$1,$3,$4,$5]
[$1,$3,$4,$6]
[$1,$3,$5,$6]
[$1,$4,$5,$6]
[$2,$1,$3,$4]
[$2,$1,$3,$5]
[$2,$1,$3,$6]
[$2,$1,$4,$5]
[$2,$1,$4,$6]
[$2,$1,$5,$6]
[$2,$3,$4,$5]
[$2,$3,$4,$6]
[$2,$3,$5,$6]
[$2,$4,$5,$6]
[$3,$1,$2,$4]
[$3,$1,$2,$5]
[$3,$1,$2,$6]
[$3,$1,$4,$5]
[$3,$1,$4,$6]
[$3,$1,$5,$6]
[$3,$2,$4,$5]
[$3,$2,$4,$6]
[$3,$2,$5,$6]
[$3,$4,$5,$6]
[$4,$1,$2,$3]
[$4,$1,$2,$5]
[$4,$1,$2,$6]
[$4,$1,$3,$5]
[$4,$1,$3,$6]
[$4,$1,$5,$6]
[$4,$2,$3,$5]
[$4,$2,$3,$6]
[$4,$2,$5,$6]
[$4,$3,$5,$6]
[$5,$1,$2,$3]
[$5,$1,$2,$4]
[$5,$1,$2,$6]
[$5,$1,$3,$4]
[$5,$1,$3,$6]
[$5,$1,$4,$6]
[$5,$2,$3,$4]
[$5,$2,$3,$6]
[$5,$2,$4,$6]
[$5,$3,$4,$6]
[$6,$1,$2,$3]
[$6,$1,$2,$4]
[$6,$1,$2,$5]
[$6,$1,$3,$4]
[$6,$1,$3,$5]
[$6,$1,$4,$5]
[$6,$2,$3,$4]
[$6,$2,$3,$5]
[$6,$2,$4,$5]
[$6,$3,$4,$5]

On obtient un fichier du style :

la tension;en volt
le courant;en ampère
la puissance;en watt
l'énergie;en joule

la tension;en volt
la résistance;en ohm
la longueur;en mètre
l'énergie;en joule

la tension;en volt
la résistance;en ohm
la longueur;en mètre
le courant;en ampère

la tension;en volt
l'énergie;en joule
la puissance;en watt
le courant;en ampère

la tension;en volt
le courant;en ampère
la résistance;en ohm
la longueur;en mètre

la tension;en volt
l'énergie;en joule
le courant;en ampère
la résistance;en ohm

la tension;en volt
la puissance;en watt
le courant;en ampère
la résistance;en ohm

la tension;en volt
la puissance;en watt
l'énergie;en joule
la résistance;en ohm

Il faut maintenant enlever le premier champs des 3 dernières lignes de chaque paragraphe afin de n'en garder que les réponses, et diviser la première question en 2 lignes.

Expression régulière de recherche qui détecte un paragraphe :

(.*);(.*)
(.*);(.*)
(.*);(.*)
(.*);(.*)

Motif de remplacement qui n'utilise qu'un champ sur deux parmi les mauvaises réponses :

quest("En quelle unité se mesure $1 ?//a");
rep("(o) $2");
rep("( ) $4");
rep("( ) $6");
rep("( ) $8");

Et on obtient :

quest("En quelle unité se mesure la tension ?//a");
rep("(o) en volt");
rep("( ) en ampère");
rep("( ) en watt");
rep("( ) en joule");

quest("En quelle unité se mesure la tension ?//a");
rep("(o) en volt");
rep("( ) en ohm");
rep("( ) en mètre");
rep("( ) en joule");

quest("En quelle unité se mesure la tension ?//a");
rep("(o) en volt");
rep("( ) en ohm");
rep("( ) en mètre");
rep("( ) en ampère");

quest("En quelle unité se mesure la tension ?//a");
rep("(o) en volt");
rep("( ) en joule");
rep("( ) en watt");
rep("( ) en ampère");

quest("En quelle unité se mesure la tension ?//a");
rep("(o) en volt");
rep("( ) en ampère");
rep("( ) en ohm");
rep("( ) en mètre");

quest("En quelle unité se mesure la tension ?//a");
rep("(o) en volt");
rep("( ) en joule");
rep("( ) en ampère");
rep("( ) en ohm");

quest("En quelle unité se mesure la tension ?//a");
rep("(o) en volt");
rep("( ) en watt");
rep("( ) en ampère");
rep("( ) en ohm");

quest("En quelle unité se mesure la tension ?//a");
rep("(o) en volt");
rep("( ) en watt");
rep("( ) en joule");
rep("( ) en ohm");

Cas concret en partant d'un fichier question;réponse de 12 lignes :

En partant d'un fichier de 12 lignes (donc 12 questions), et en proposant pour chaque question la bonne réponse ainsi que 3 mauvaises réponses prises parmi les 11 autres questions en utilisant toutes les combinaisons, chaque question sera posée 165 fois. Le fichier de 12 lignes génère donc 12x165=1980 questions. Si on pose également les questions réciproques, alors le fichier de 12 lignes génèrera 2x1980=3960 questions.

Le fichier de départ est le suivant (12 termes associés à leur définition):

la sensibilité;le coefficient directeur de la fonction de transfert d'un capteur
la linéarité;la faculté d'un capteur de posséder une fonction de transfert en forme de droite
la fidélité;la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée
la précision;l'écart maximal entre la valeur de sortie mesurée et la valeur idéale attendue d'un capteur
une L.D.R.;un capteur de lumière
une C.T.N.;un capteur de température
un anémomètre;un capteur mesurant la vitesse du vent
une jauge de contrainte;un capteur de force
un accéléromètre;un capteur mesurant l'accélération
un inclinomètre;un capteur mesurant l'inclinaison
un I.L.S.;un interrupteur qui se ferme à proximité d'un aimant
un détecteur inductif;un capteur de proximité détectant la présence d'un objet métallique

Demandons à Prolog les 1980 listes possibles :

findall(L3,(select(X, [$1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12], L1),liste(3,L1,L2),flatten([[X],L2],L3)),L4),afficher_liste(L4).

Il nous donne les 1980 listes (165 listes commencent par $1, 165 listes commencent par $2, 165 listes commencent par $3, etc.) :

[$1,$2,$3,$4]
[$1,$2,$3,$5]
[$1,$2,$3,$6]
[$1,$2,$3,$7]
[$1,$2,$3,$8]
[$1,$2,$3,$9]
[$1,$2,$3,$10]
[$1,$2,$3,$11]
[$1,$2,$3,$12]
[$1,$2,$4,$5]
[$1,$2,$4,$6]
[$1,$2,$4,$7]
[$1,$2,$4,$8]
[$1,$2,$4,$9]
[$1,$2,$4,$10]
[$1,$2,$4,$11]
[$1,$2,$4,$12]
[$1,$2,$5,$6]
[$1,$2,$5,$7]
[$1,$2,$5,$8]
[$1,$2,$5,$9]
[$1,$2,$5,$10]
[$1,$2,$5,$11]
[$1,$2,$5,$12]
[$1,$2,$6,$7]
[$1,$2,$6,$8]
[$1,$2,$6,$9]
[$1,$2,$6,$10]
[$1,$2,$6,$11]
[$1,$2,$6,$12]

etc.

Convertissons ces listes en motif de remplacement (supression du crochet ouvrant, et remplacement de la virgule et du crochet fermant par un retour à la ligne) :

$1
$2
$3
$4

$1
$2
$3
$5

$1
$2
$3
$6

$1
$2
$3
$7

$1
$2
$3
$8

$1
$2
$3
$9

$1
$2
$3
$10

$1
$2
$3
$11
etc.

Réorganisons les 12 lignes du fichier de départ en remplaçant par le motif de remplacement précédent l'expression régulière suivante (12 fois (.*)\n) :

(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)

On obtient alors un fichier à 1980 paragraphes de 4 lignes chacun :

la sensibilité;le coefficient directeur de la fonction de transfert d'un capteur
la linéarité;la faculté d'un capteur de posséder une fonction de transfert en forme de droite
la fidélité;la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée
la précision;l'écart maximal entre la valeur de sortie mesurée et la valeur idéale attendue d'un capteur

la sensibilité;le coefficient directeur de la fonction de transfert d'un capteur
la linéarité;la faculté d'un capteur de posséder une fonction de transfert en forme de droite
la fidélité;la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée
une L.D.R.;un capteur de lumière

la sensibilité;le coefficient directeur de la fonction de transfert d'un capteur
la linéarité;la faculté d'un capteur de posséder une fonction de transfert en forme de droite
la fidélité;la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée
une C.T.N.;un capteur de température

la sensibilité;le coefficient directeur de la fonction de transfert d'un capteur
la linéarité;la faculté d'un capteur de posséder une fonction de transfert en forme de droite
la fidélité;la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée
un anémomètre;un capteur mesurant la vitesse du vent

la sensibilité;le coefficient directeur de la fonction de transfert d'un capteur
la linéarité;la faculté d'un capteur de posséder une fonction de transfert en forme de droite
la fidélité;la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée
une jauge de contrainte;un capteur de force

etc.

Il ne reste plus qu'à mettre en forme les questions. Pour cela on recherche l'expression régulière suivante détectant un paragraphe de 4 lignes de la forme question;réponse :

(.*);(.*)
(.*);(.*)
(.*);(.*)
(.*);(.*)

Et on le remplace par le motif de remplacement suivant qui pose la première question avec sa réponse et y ajoute 3 mauvaises réponses :

quest("Qu'appelle-t-on $1 ?//a");
rep("(o) $2");
rep("( ) $4");
rep("( ) $6");
rep("( ) $8");

Et voici le résultat final (1980 questions où on demande d'associer un mot à une définition parmi 4) :

quest("Qu'appelle-t-on la sensibilité ?//a");
rep("(o) le coefficient directeur de la fonction de transfert d'un capteur");
rep("( ) la faculté d'un capteur de posséder une fonction de transfert en forme de droite");
rep("( ) la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée");
rep("( ) l'écart maximal entre la valeur de sortie mesurée et la valeur idéale attendue d'un capteur");

quest("Qu'appelle-t-on la sensibilité ?//a");
rep("(o) le coefficient directeur de la fonction de transfert d'un capteur");
rep("( ) la faculté d'un capteur de posséder une fonction de transfert en forme de droite");
rep("( ) la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée");
rep("( ) un capteur de lumière");

quest("Qu'appelle-t-on la sensibilité ?//a");
rep("(o) le coefficient directeur de la fonction de transfert d'un capteur");
rep("( ) la faculté d'un capteur de posséder une fonction de transfert en forme de droite");
rep("( ) la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée");
rep("( ) un capteur de température");

quest("Qu'appelle-t-on la sensibilité ?//a");
rep("(o) le coefficient directeur de la fonction de transfert d'un capteur");
rep("( ) la faculté d'un capteur de posséder une fonction de transfert en forme de droite");
rep("( ) la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée");
rep("( ) un capteur mesurant la vitesse du vent");

quest("Qu'appelle-t-on la sensibilité ?//a");
rep("(o) le coefficient directeur de la fonction de transfert d'un capteur");
rep("( ) la faculté d'un capteur de posséder une fonction de transfert en forme de droite");
rep("( ) la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée");
rep("( ) un capteur de force");

etc.

Si on veut les questions réciproques des précédentes on commence par inverser les champs dans le fichier de départ afin de convertir le fichier question;réponse en réponse;question. Pour cela on recherche l'expresion régulière (.*);(.*) et on la remplace par le motif $2;$1 :

le coefficient directeur de la fonction de transfert d'un capteur;la sensibilité
la faculté d'un capteur de posséder une fonction de transfert en forme de droite;la linéarité
la faculté d'un capteur de délivrer toujours la même valeur en sortie pour la même valeur d'entrée;la fidélité
l'écart maximal entre la valeur de sortie mesurée et la valeur idéale attendue d'un capteur;la précision
un capteur de lumière;une L.D.R.
un capteur de température;une C.T.N.
un capteur mesurant la vitesse du vent;un anémomètre
un capteur de force;une jauge de contrainte
un capteur mesurant l'accélération ;un accéléromètre
un capteur mesurant l'inclinaison ;un inclinomètre
un interrupteur qui se ferme à proximité d'un aimant;un I.L.S.
un capteur de proximité détectant la présence d'un objet métallique;un détecteur inductif

La suite est similaire au cas précédent sauf pour la mise en forme des questions où cette fois on remplacera

(.*);(.*)
(.*);(.*)
(.*);(.*)
(.*);(.*)

Par

quest("Quelle proposition désigne $1 ?//a");
rep("(o) $2");
rep("( ) $4");
rep("( ) $6");
rep("( ) $8");

Et on obtient 1980 nouvelles questions (1980 questions où on demande d'associer une définition à un mot parmi 4) :

quest("Quelle proposition désigne le coefficient directeur de la fonction de transfert d'un capteur ?//a");
rep("(o) la sensibilité");
rep("( ) la linéarité");
rep("( ) la fidélité");
rep("( ) la précision");

quest("Quelle proposition désigne le coefficient directeur de la fonction de transfert d'un capteur ?//a");
rep("(o) la sensibilité");
rep("( ) la linéarité");
rep("( ) la fidélité");
rep("( ) une L.D.R.");

quest("Quelle proposition désigne le coefficient directeur de la fonction de transfert d'un capteur ?//a");
rep("(o) la sensibilité");
rep("( ) la linéarité");
rep("( ) la fidélité");
rep("( ) une C.T.N.");

quest("Quelle proposition désigne le coefficient directeur de la fonction de transfert d'un capteur ?//a");
rep("(o) la sensibilité");
rep("( ) la linéarité");
rep("( ) la fidélité");
rep("( ) un anémomètre");

En résumé Prolog nous génère un motif de remplacement complexe permettant de réorganiser le fichier de départ en paragraphes de 4 lignes chacun. Ensuite, avec une seconde requête toujours à base d'expression régulière, on met en forme le fichier pour obtenir la syntaxe des questions du QCM.

Le fichier question;réponse a besoin de 2 transformations pour être converti en fichier de QCM. Ces 2 transformations sont enregistrables dans des fichiers requête de Dreamweaver (fichier texte au format XML). Les futurs générateurs de questions se résumeront donc à 2 fichiers requête, dont le premier est généré en Prolog (motif de remplacement complexe permettant de réorganiser les lignes du fichier de départ).

Chaque fichier requête contient une expression régulière de recherche et un motif de remplacement.

En collectionnant les fichiers requête telles des macros, on peut alors multiplier le nombre et diversifier le type de générateurs de questions.

 

Création d'un fichier requête Dreamweaver :

Dreamweaver permet d'enregistrer les requêtes rechercher/remplacer dans des fichiers texte. Par exemple, la requête consistant à rechercher l'expression régulière (.*);(.*) et à la remplacer par le motif de remplacement $2;$1 afin d'inverser l'ordre des champs sur chaque ligne d'un fichier donnera le fichier requête suivant :

<?xml version="1.0"?>
  <dwquery>
   <queryparams matchcase="false" ignorewhitespace="false" useregexp="true"wholeword="false"/>
    <find>
     <qtext qname="(.*);(.*)" qraw="true"></qtext>
    </find>
   <replace action="replaceText" param1="$2;$1" param2=""/>
</dwquery>

On constate que l'expression régulière de recherche est enregistrée dans la chaîne qname, et que le motif de remplacement est enregisté dans la chaîne param1.

Autre exemple : si on remplace l'expression régulière

(.*);(.*)
(.*);(.*)
(.*);(.*)
(.*);(.*)

Par le motif

quest("Quelle proposition désigne $1 ?//a");
rep("(o) $2");
rep("( ) $4");
rep("( ) $6");
rep("( ) $8");

Et qu'on enregistre la requête avec Dreamweaver on obtient le fichier suivant :

<?xml version="1.0"?>
<dwquery>
<queryparams matchcase="false" ignorewhitespace="false" useregexp="true"wholeword="false"/>
<find>
<qtext qname="(.*);(.*)
(.*);(.*)
(.*);(.*)
(.*);(.*)" qraw="true"></qtext>
</find>
<replace action="replaceText" param1="quest(&quot;Quelle proposition désigne $1 ?//a&quot;);
rep(&quot;(o) $2&quot;);
rep(&quot;( ) $4&quot;);
rep(&quot;( ) $6&quot;);
rep(&quot;( ) $8&quot;); " param2=""/>
</dwquery>

L'idée est alors la suivante : pour les requêtes complexes, comme par exemple la génération de 1980 questions à partir d'un fichier question;réponse de 12 lignes, on peut très bien générer le fichier requête plutôt que le motif de remplacement. De plus, les motifs de remplacement complexes sont limités en taille si on les met dans la boîte de dialogue Rechercher/Remplacer de Dreamweaver par un simple copier/coller.

Pour cela on part des 1980 paragraphes de 4 lignes donnés par Prolog, et on ajoute

<?xml version="1.0"?>
<dwquery>
<queryparams matchcase="false" ignorewhitespace="false" useregexp="true"wholeword="false"/>
<find>
<qtext qname="(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)
(.*)" qraw="true"></qtext>
</find>
<replace action="replaceText" param1="

au début et

" param2=""/>
</dwquery>

à la fin.

On obtient ainsi un fichier requête convertissant un fichier question;réponse de 12 lignes en un fichier contenant les 1980 paragraphes de 4 lignes chacun. Ensuite, le fichier requête donné plus haut permet de ne garder que la question, la bonne réponse, ou les mauvaises réponses sur chacune des lignes.

Voici les 4 fichiers requête Dreamweaver permettant de convertir rapidement un fichier question;réponse de 12 lignes en 3960 questions :

requete1.dwr : convertit le fichier à 12 lignes en fichier de 1980 paragraphes de 4 lignes chacun

requete2.dwr : convertit le fichier de 1980 paragraphes en 1980 questions commençant par "Qu'appelle-t-on"

requete3.dwr : convertit le fichier de 1980 paragraphes en 1980 questions commençant par "Quelle proposition désigne"

requete4.dwr : inverse l'ordre des champs sur chaque ligne pour convertir le fichier question;réponse en fichier réponse;question

C'est seulement le motif de remplacement complexe du fichier requete1.dwr qui a été généré en Prolog, et qui justifie ici l'utilisation de Prolog. Les autres motifs de remplacement ainsi que les expressions régulières des requêtes peuvent tous être écrits manuellement ou être rapidement obtenus par simple copier/coller d'éléments de base (exemple : 12 fois (.*)).

Et si on part d'un fichier à 6 lignes seulement (et non 12) afin d'obtenir 60 questions (et non 1980), seule la première requête doit être ré-écrite. Voici le fichier requête convertissant un fichier question;réponse de 6 lignes en un fichier possédant 60 questions. Les requêtes 2, 3 et 4 restant les mêmes que précédemment :

requete5.dwr : convertit un fichier à 6 lignes en fichier de 60 paragraphes de 4 lignes chacun

 

Conclusion et extensions possibles

En résumé, les nouvelles possibilités de ces générateurs de questions de seconde génération sont avant tout dûes à la puissance des expressions régulières de Dreamweaver avec enregistrement des requêtes dans des fichiers. Dans le cas de requêtes complexes (expression régulière de recherche ou motif de remplacement) il est parfois plus ingénieux de générer automatiquement le code de la requête plutôt que de l'écrire à la main. Pour générer un code répétitif ou linéaire, un langage impératif tel que Python ou JavaScript est en général suffisant. Pour générer un code proposant toutes les combinaisons possibles d'un problème (comme les listes complexes répondant à des critères précis), un langage logique comme Prolog est plus adapté, comme démontré dans cet article pour obtenir par exemple toutes les listes à 4 éléments à partir de la liste à 6 éléments [$1,$2,$3,$4,$5,$6].

Rappelons aussi que dans certains cas c'est le fichier question;reponse qui peut être généré par un langage de programmation impératif, comme par exemple pour la conversion hexadécimal/décimal où on génère en Python un fichier dont les lignes sont au format n_en_hexa;n_en_décimal. Notons que dans ce dernier cas la requête de transformation passant du fichier question;réponse au fichier de QCM est très simple.

Il reste à inventer le cas où le fichier question;réponse ainsi que les requêtes de transformation sont tous les deux générés automatiquement par un langage de programmation impératif, fonctionnel, relationnel, applicatif ou logique.

Sachant que Dreamweaver peut appliquer une requête de transformation sur l'ensemble des fichiers ouverts ou sur l'ensemble des fichiers texte d'une arborescence, ces générateurs de seconde génération se résumant chacun à 2 fichiers requête peuvent travailler sur plusieurs fichiers simultanément afin de générer en quelques secondes plusieurs milliers de questions.

Mais les générateurs de fichiers ne sont pas réservés aux QCM. En effet, en étendant la technique à différents besoins il est parfaitement possible de générer tout type de fichier texte ayant une structure précise, de complexité quelconque et répondant à certains critères. Les exemples de fichiers textes à créer sont multiples : questionnaires pour QCM (comme ci-dessus), fichier login/mot de passe pour configurer un système Linux, fichier de configuration (Samba, LDAP, DNS, ou autre), fichier HTML, macro ou fichier batch complexe, fichier source dans un langage précis (Python, Latex, lisp, etc.), structure de donné complexe en texte (fichier XML, expression régulière, image au format texte XBM et XPM, fichier CSS, fichier de base de registres, table de hachage, sérialisation d'objets complexes, etc.).

Quelque soit le fichier texte à créer, appelons-le FIC. Pour obtenir le fichier FIC il y a 3 solutions :

Le premier cas est biensûr réservé aux fichiers très simples et peu répétitifs. Le second cas représente un générateur de première génération (génération directe du fichier). Le troisième cas décrit un générateur de seconde génération (génération indirecte du fichier par requêtes de transformation).

Dans le second cas le générateur pourra être programmé avec un langage impératif simple comme par exemple JavaScript, Perl, Ruby ou Python.

Dans le troisième cas, le fichier de départ comme les requêtes de transformation pourront être soit écrits à la main, soit générés automatiquement avec un générateur à programmer. Dans ce cas le générateur peut être programmé en langage impératif (Perl, JavaScript, Python, Ruby, langage C, Pascal, PHP, Java, Matlab, etc.), en langage fonctionnel (Lisp, Scheme, Caml, etc.), en langage logique (Prolog, SQL, etc.), ou en utilisant un utilitaire spécialisé dans la transformation de flux textuel réalisant directement la transformation de FIC1 en FIC (sed, awk, etc.).

Il va de soit que dans le troisième cas le fichier FIC1 doit être beaucoup plus simple que le fichier FIC, les différentes requêtes de transformation à créer ajoutant justement au fichier FIC1 la complexité lui permettant de muter progressivement vers le fichier FIC (exemple décrit plus haut : FIC1 possède seulement 12 lignes au format question;réponse et FIC est un fichier de QCM possédant 1980 questions : les requêtes de transformation ont multiplié chacune des 12 questions en utilisant toutes les combinaisons possibles pour les 3 fausses réponses prises parmi les 11).

Précisons aussi que la génération ou la transformation de fichiers texte peut être obtenu dans différents environnements logiciels. Sous Windows, avec Python, Ruby et Prolog en ligne de commande pour la génération de fichiers simples ou de listes, le logiciel Dreamweaver constitue un environnement idéal pour appliquer une requête de transformation, grâce à son support d'expressions régulières, à sa possibilité d'enregistrer et de réutiliser les requêtes, et grâce au traitement par lot permettant d'appliquer une même requête sur plusieurs fichiers.

L'autre environnement très complet pour effectuer de telle manipulation de fichier est tout simplement le shell Linux. Grâce à toute la puissance du shell (redirection d'entrée/sortie, boucle FOR, scripts shell, etc.), aux différents compilateurs et interpréteurs disponibles en ligne de commande (Ruby, Python, Perl, Prolog, Lisp, GCC, PHP, Tcl, Javac, etc.) et aux multiples utilitaires de traitement des fichiers textes avec ou sans support d'expressions régulières (sed, awk, grep, egrep, seq, tr, head, tail, cut, cat, wc, sort, uniq, paste, split, pr, od, tee, nl, etc.) toutes les générations et les transformations de fichiers textes sont largement possibles dans un shell Linux.

Grâce aux redirections de l'entrée et de la sortie standard dans un shell (Linux ou Windows), un générateur de seconde génération peut se résumer à un programme (en Ruby, Perl ou Python) et une seule ligne de commande suffit pour transformer le fichier FIC en fichier FIC1. Si par exemple le générateur est un programme en Ruby appelé generateur.rb et lisant les lignes du fichier FIC sur son entrée standard par gets et envoyant les lignes résultantes de la transformation vers sa sortie standard simplement par puts alors la ligne de commande suivante transformera le fichier de départ FIC en fichier final FIC1 :

ruby generateur.rb < FIC > FIC1

Le principe reste le même quel que soit l'interpréteur (Ruby, Perl, Python ou même awk) et quel que soit le shell (Linux ou Windows). L'avantage est que le programme generateur.rb s'occupe exclusivement du traitement du fichier texte FIC afin qu'il mute vers FIC1, et l'ouverture, la lecture, l'écriture et la fermeture des fichiers FIC et FIC1 est confié au shell qui réalise très bien et très simplement ces tâches grâce aux redirections. La mutation de FIC vers FIC1 peut passer par plusieurs étapes successives, mais toutes les requêtes de transformation sont enregistrées cette fois dans un seul fichier generateur.rb (et non dans plusieurs fichiers d'expressions régulières de Dreamweaver comme vu ci-dessus). Enfin, si le générateur a besoin d'une liste particulière générée par Prolog, il suffirait alors de la convertir en structure de donnée ou en sérialisation d'objet afin que le programme generateur.rb puis l'interpréter et l'utiliser. Prolog servirait alors simplement à générer une partie du code du générateur generateur.rb.

Ni le shell Linux, ni le logiciel Dreamweaver, ni les langages de programmation cités sur cette page n'ont été conçus à l'origine pour générer automatiquement des fichiers textes complexes ou des requêtes de transformation (sauf sed et awk qui ont été justement conçu à l'origine dans ce but). Cependant en les utilisant chacun à l'instant où on en a besoin, les possibilités offertes par un tel chaînage sont alors tout simplement infinies, et l'efficacité dépasse de loin celle d'un unique logiciel aussi abouti soit-il (comme sed ou awk par exemple).

Pour terminer on peut remarquer que les possibilités de génération automatique de fichiers texte ne manquent pas, et qu'elles dépassent largement les besoins que nous pouvons avoir : tout problème dont la solution réside dans la possession d'un fichier texte particulier peut désormais être résolu.

 

www.gecif.net

Avril 2014