Programmer en Ruby

Cet article n'est pas un cours complet sur le langage de programmation Ruby. Il donne simplement la syntaxe Ruby permettant d'utiliser les différentes possibilités du langage afin de créer rapidement un petit script fonctionnel en Ruby. Il est vivement conseillé de connaître un autre langage de programmation (C, Pascal, Java, Python, Perl, ou autre) et de savoir programmer afin d'interpréter facilement les informations donnés ci-dessous.

Sommaire de cette page

Installation de Ruby

Le langage Ruby

Les objets

Les nombres

Chaînes de caractères et expressions régulières

Les boucles

Entrée et sortie standard

Tableaux et tables de hachage

Procédures et fonctions

Les modules

Problèmes de caractères accentués, table ASCII, page de code, et unicode

Accès aux fichiers et aux répertoires

Accès aux variables d'environnement

Création de scripts CGI en Ruby sur le serveur de Gecif.net

Création d'un générateur de questions de seconde génération pour QCM

Création d'un générateur de questions du type "Cochez les 2 seuls nombres égaux"

Création d'un générateur de questions du type "Cochez tous les nombres impairs"

Création d'un générateur de questions du type "Complétez l'addition"

Quelques générateurs directs de questions pour QCM

 

Retour en haut de la page

 

Installation de Ruby

L'interpréteur intéractif de Ruby s'appelle irb et l'interpréteur s'appelle ruby.

Pour les installer sous Windows téléchargez et installez Ruby Installer :

Télécharger rubyinstaller-1.9.3-p545.exe

Pour installer Ruby sous Ubuntu on pourra taper la commande suivante en tant que root :

apt-get install ruby (ou utiliser l'interface graphique Synaptic d'apt-get).

 

Retour en haut de la page

Le langage Ruby

Ruby allie la puissance de Python pour le calcul scientifique (nombres complexes en natif, gestion des grands nombres entiers de taille arbitraire, changement de base, écriture sous forme de rationnel, etc.) et l'élégance de Perl pour l'analyse de fichiers texte (support d'expressions régulières étendues, nombreuses fonctions de traitement des chaîne de caractères, lecture directe de l'entrée standard pour réaliser rapidement un filtre, etc.) tout en concervant les avantages de ces deux langages (syntaxe simple et intuitive, typage dynamique, structures de données complexes, tableau associatif, table de hachage, etc.).

En résumé Ruby = Perl + Python, ou si vous préférez : tout ce qui peut être fait en Perl peut être fait en Ruby, tout ce qui peut être fait en Python peut être fait en Ruby, mais Perl et Python restent complémentaires sur certains points.

 

Retour en haut de la page

Les objets

En Ruby tout est objet. Pour connaître l'ensemble des méthodes d'un objet on utilise la méthode methods :

Exemple : 5.methods affiche l'ensemble des méthodes que possède l'entier 5 (ou plus précisément l'objet 5).

Pour connaître la classe on utilise la méthode class : 5.class
Et pour connaître la classe parent d'une classe on utilise la méthode superclass ce qui permet de découvrir l'arborescence des objets de Ruby.

Exemple : Fixnum.class ou 5.class.superclass est équivalent et donnera Integer

Si on tape methods directement dans l'interpréteur irb, on obtient un ensemble de méthodes liés au programme principal (main).

Parmi elles il y a help qui permet d'obtenir de l'aide sur chaque méthode, équivalent à la commande ri lancé dans un shell (mais utilisable seulement si ri, l'aide en ligne de Ruby, est installé). RI = Ruby Index

 

Retour en haut de la page

Les nombres

Ruby gère :
- les nombre entiers
- les nombres à virgule flotante
- les entiers de taille arbitraire
- les nombres complexes

Chaque nombre appartient à une classe et possède des méthodes précises permettant d'effectuer :
- des calculs (ex : modulo)
- des tests (ex : real?)
- des conversions (ex : to_s)
- des itérations (ex : times)

Les méthodes permettant d'effectuer des calculs sont appelées des opérateurs (+ - * div / abs round, etc.)
Les méthodes effectuant un test et répondant par true ou false ont un nom finissant par un point d'interrogation.
Les méthodes permettant de répéter plusieurs fois un bloc sont appelées des itérateurs (times, upto, downto, etc.)

Exemple : 5.to_s convertit l'entier 5 en chaîne de caractères "5"
5.i multiplie l'entier 5 par le complexe i
5.times itère 5 fois le bloc suivant délimité ente do et end. Exemple : 5.times do puts "Bonjour" end
Pour récupérer la valeur du compteur dans la boucle on l'encadre de barres verticales :
5.times do |n| puts n end

Pour créer un nombre complexe on utilise la fonction Complex (avec un C majuscule) : z=Complex(2,3)
On peut aussi utiliser un calcul avec la méthode i liée aux nombres : z=2+3.i

Pour convertir une chaîne en nombre on utilise les méthode to_i (entier), to_f (réel) ou to_c (complexe) liée à la chaîne :
"75".to_i
"3.14".to_f
"4+3i".to_c
(sans opérateur entre le 3 et le i : ni . ni *)

La méthode to_s converti un nombre en chaîne, et la méthode to_r convertit un nombre en fraction (rationnel).

Par défaut Ruby affiche tous les nombre dans la base décimale.
Pour saisir un nombre en hexadécimal on le préfixe par 0x : n=0xA2
Pour saisir un nombre en binaire on le préfixe par 0b : n=0b1011
Pour saisir un nombre en octal on le préfixe par 0o : n=0o23

Pour convertir un nombre hexadécimal en décimal il suffit de l'afficher ou de le convertir en chaîne puisque la base par défaut est le décimal :
puts 0xAB
0xAB.to_s

Pour préciser qu'une chaîne représente un nombre hexadécimal on indique la base de départ à la méthode to_i :
"AB".to_i(16)

On peut aussi utiliser la méthode hex liée aux chaînes de caractères :
"AB".hex

Pour convertir un nombre décimal en hexadécimal on indique la base de sortie à la méthode to_s :
171.to_s(16)

Enfin pour passer d'une base à une autre sans passer par le décimal il y a 2 solutions. Exemple avec le nombre décimal 65 :

Soit on préfixe la base de départ par 0x et on précise la base finale dans to_s : 0x41.to_s(2) => "1000001"

Soit on écrit le nombre de départ dans une chaîne en précisant sa base dans to_i (et on précise toujours la base finale dans to_s) : "41".to_i(16).to_s(2) => "1000001"

La fonction rand renvoie un nombre réel aléatoire entre 0 (compris) et 1 (non compris).
Exemple : (rand*10+1).round renvoie un entier entre 1 (compris) et 10 (compris également)

Le module prime permet un test de primalité :

require 'prime'
71.prime?
234.prime_division

L'utilisation du module prime par la ligne "require 'prime'" a ajouté 2 nouvelles méthodes pour les nombres entiers : prime? qui teste si un nombre est premier et prime_division qui renvoie la décomposition en facteurs premiers sous forme d'un tableau de tableau.

Le module bigdecimal ajoute une classe BigDecimal permettant la gestion des grands nombres :

require 'bigdecimal'

BigDecimal("6E54") => #<BigDecimal:1016580,'0.6E55',9(18)>

BigDecimal("6E54").to_s => "0.6E55"

BigDecimal("6E54").to_s("F") => "6000000000000000000000000000000000000000000000000000000.0"

BigDecimal("6E54").class => BigDecimal

BigDecimal("6E54").class.superclass => Numeric

BigDecimal("6E54").class.superclass.superclass => Object

 

Retour en haut de la page

Chaînes de caractères et expressions régulières

Ruby dispose d'un grand nombre de méthodes liées aux chaînes de caractères et permettant une analyse fine d'information textuelle.

Pour connaître toutes les méthodes applicable à une chaîne de caractère on tape "bonjour".methods
On y retrouve des fonctions classiques (length, insert, size, chr, ord, etc.) et des méthodes relatives aux expressions régulières (match, =~, !~, etc.)

Pour connaître le code ASCII d'un caractère on utilise la méthode ord appliquée à une chaîne de 1 caractère :
"A".ord revoie 65

Pour obtenir un caractère de code ASCII donnée on utilise la méthode chr appliquée à un entier :
65.chr renvoie "A"

Exemples :
(rand*25+65).round.chr génère une lettre majuscule aléatoire (entre "A" et "Z").
s="" ; 20.times do s+=(rand*25+65).round.chr end génère une chaîne aléatoire de 20 lettres majuscules

Une chaîne de caractères peut être vue comme un tableau de caractères ce qui permet très facilement de ne garder que certains caractères dont le rang est passé en indice à la chaîne. Exemples :

"Bonjour et bienvenue sur cette page parlant de Ruby"[0] => "B"

"Bonjour et bienvenue sur cette page parlant de Ruby"[19] => "e"

"Bonjour et bienvenue sur cette page parlant de Ruby"[0..19] => "Bonjour et bienvenue"

"Bonjour et bienvenue sur cette page parlant de Ruby".size => 51

"Bonjour et bienvenue sur cette page parlant de Ruby"[47..51] => "Ruby"

La méthode split permet de découper une chaîne de caractères selon un séparateur précis et renvoie un tableau. Exemple :

s="Paul;DUPOND;75000;Paris"

t=s.split(";")

Le tableau t obtenu a pour contenu :

["Paul", "DUPOND", "75000", "Paris"]

La méthode join des tableaux réalise l'opération inverse. Exemples :

t.join => "PaulDUPOND75000Paris"

t.join(";") => "Paul;DUPOND;75000;Paris"

Sans paramètre la méthode split utilise l'espace comme séparateur. Exemple :

"Ceci est une phrase".split => ["Ceci", "est", "une", "phrase"]

Pour vérifier si une chaîne de caractères correspond à une expression régulière :

"bonjour" =~ /^b.*$/

Pour vérifier si une chaîne ne correspond pas à une expression régulière :
"bonjour" !~ /^b.*$/

En Ruby seules les valeurs "nil" et "false" sont considérées fausses. Tout le reste est vrai (true, 1, 0, 0.0, etc.)
Pour tester ce qui est vrai ou faux il suffit de taper !1, !0, !nil, !false, !true, etc. où ! est l'opérateur de complément booléen.

Avec les méthodes de traitement avancé des chaîne de caractères il est possible de découper, convertir, mettre en majuscule, remplacer ou supprimer certains caractères, etc.

Par exemple la méthode sub remplace la première occurrence d'une chaîne :

"Ceci est une chaîne de caractères".sub("c","#") => "Ce#i est une chaîne de caractères"

Et la méthode gsub remplace toutes les occurrences trouvées :

"Ceci est une chaîne de caractères".gsub("c","#") => "Ce#i est une #haîne de #ara#tères"

La méthode split découpe une phrase en mot et renvoie un tableau :

"Ceci est une chaîne de caractères".split => ["Ceci", "est", "une", "chaîne", "de", "caractères"]

 

Retour en haut de la page

Les boucles

Boucle FOR :

for n in 1..9 do puts n end

Boucle WHILE :

n=0 ; while n<9 do n+=1 ; puts n end

Les itérateurs sont des méthodes liées aux nombres entiers et permettant d'itérer un bloc :

Itère 5 fois, n va de 0 à 4 :
5.times do |n| puts n end

Ordre croissant, n va de 5 à 10 :
5.upto(10) do |n| puts n end

Ordre décroissant, n va de 34 à 18 :
34.downto(18) do |n| puts n end

Affiche l'alphabet en minuscule :
97.upto(97+25) do |n| puts n.chr end

 

Retour en haut de la page

Entrée et sortie standard

La fonction gets récupère une ligne (chaîne finissant par \n) sur l'entrée standard.
La fonction puts envoie une ligne (chaîne finissant par \n) sur la sortie standard.

Exemple de filtre affichant seulement les chaînes commençant par AB :

#!/usr/bin/ruby
while (s=gets)
if s =~ /^AB.*$/
puts s
end
end

Et voici le même filtre mais tapé sur une seule ligne de commande dans un shell Linux avec l'option -e de Ruby :

printf "toto\nABCD\noui" | ruby -e "while (s=gets);if s =~ /^AB.*$/;puts s;end;end"

Printf envoie 3 lignes sur l'entrée standard de ruby, qui n'affiche que la ligne ABCD

Dans le programme entre guillemets transmis à Ruby grâce au paramètre -e, tous les retours à la ligne on été remplacés par un point-virgule.

 

Créer des images en Ruby

Pour créer facilement des images en Ruby il faut installer et utiliser le module Rmagick.

 

Retour en haut de la page

Tableaux et tables de hachage

Création d'un tableau simple :

t=[1,2,"oui",3.14]

Les éléments ne sont pas forcément tous de même type.

t[0] est le premier élément, t[1] le deuxième, etc.
t[-1] est le dernier élément, t[-2] l'avant-dernier, etc.

Ajout d'un élément : t << "bonjour"

=> [1, 2, "oui", 3.14, "bonjour"]

Suppression d'un élément au tableau :

La méthode delete_at supprime un élément dont l'indice est passé en paramètre :

t.delete_at(3) => [1, 2, "oui", "bonjour"]

La méthode delete supprime directement l'élément passé en paramètre :

t.delete("oui") => [1, 2, "bonjour"]

La méthode each est un énumérateur :

t.each do |x| puts x end

1
2
oui
3.14
bonjour

Pour sérialiser un tableau, c'est-à-dire le convertir en chaîne de caractère afin de le sauvegarder :

s=t.to_s => "[1, 2, \"oui\", 3.14, \"bonjour\"]"

Pour désérialiser on utilise la fonction eval de Ruby :

g=eval(s) => [1, 2, "oui", 3.14, "bonjour"]

Grâce à eval il est possible de construire "à la main" des objets complexes dans une chaîne de caractère, qu'il suffit ensuite d'évaluer pour que la chaîne deviennent un objet réel.

Toutes les fonctions utiles ou avancées permettant la manipulation du tableau sont disponibles dans ses méthodes (ajout et suppression d'élément, énumération de tous les éléments, trie, concaténation, sérialisation, etc.)

Un tableau associatif (appelé aussi table de hachage (comme en Perl) ou dictionnaire (comme en Python)) :

h={"nom"=>"Dupond","prénom"=>"Pierre","age"=>23}

Accès à un élément précis : h["age"]

Pour ajouter un nouvel élément à un tableau associatif on donne simplement une valeur à une clé qui n'existe pas : difficile de faire plus simple !

h["taille"]=1.80

puts h
{"nom"=>"Dupond", "prénom"=>"Pierre", "age"=>23, "taille"=>1.8}

Énumération d'un tableau associatif : l'énumérateur each "alimente" 2 variables : la clé et la valeur de l'élément

h.each do |cle,valeur| puts "#{cle}=#{valeur}" end

h.keys est un tableau contenant seulement les clés : ["nom", "pr\x82nom", "age", "taille"]
h.values est un tableau contenant seulement les valeurs : ["Dupond", "Pierre", 23, 1.8]

Énumération des clés :

h.keys.each do |cle| puts cle end

Énumération des valeurs :

h.values.each do |valeur| puts valeur end

Pour sérialiser un tableau associatif on utilise toujours la méthode to_s, et pour désérialiser on utilise encore eval.

Génération automatique d'un tableau associatif contenant en clé les 26 lettres minuscules et en valeur leur code ASCII :

h=Hash.new ; 97.upto(122) do |n| h[n.chr]=n end

puts h
{"a"=>97, "b"=>98, "c"=>99, "d"=>100, "e"=>101, "f"=>102, "g"=>103, "h"=>104, "i "=>105, "j"=>106, "k"=>107, "l"=>108, "m"=>109, "n"=>110, "o"=>111, "p"=>112, "q "=>113, "r"=>114, "s"=>115, "t"=>116, "u"=>117, "v"=>118, "w"=>119, "x"=>120, "y "=>121, "z"=>122}

Hash.new crée un nouveau tableau associatif vide.

Création d'une chaîne de caractères s représentant la sérialisation de ce même tableau associatif :

s='{"a"=97' ; 98.upto(122) do |n| s+=', "'+n.chr+'"=>'+n.to_s end ; s+='}'

=> "{\"a\"=97, \"b\"=>98, \"c\"=>99, \"d\"=>100, \"e\"=>101, \"f\"=>102, \"g\"=> 103, \"h\"=>104, \"i\"=>105, \"j\"=>106, \"k\"=>107, \"l\"=>108, \"m\"=>109, \"n \"=>110, \"o\"=>111, \"p\"=>112, \"q\"=>113, \"r\"=>114, \"s\"=>115, \"t\"=>116, \"u\"=>117, \"v\"=>118, \"w\"=>119, \"x\"=>120, \"y\"=>121, \"z\"=>122}"

Grâce à eval on peut reconstruire le tableau associatif à partir de la chaîne s :

h=eval(s)

Création d'un tableau multi-dimensonnel :

t1 est un tableau à 1 dimension, t2 est un tableau à 2 dimensions et t3 est un tableau à 3 dimensions. Les éléments de t3 sont ici des nombres entiers.

On crée les 3 tableaux vides :

t1=[] => []

t2=[] => []

t3=[] => []

On rempli t1 :

t1=[1,2,3] => [1, 2, 3]

Puis on l'ajoute à t2 :

t2<<t1 => [[1, 2, 3]]

Et on recommence :

t1=[4,5,6] => [4, 5, 6]

t2<<t1 => [[1, 2, 3], [4, 5, 6]]

On ajoute t2 à t3 :

t3<<t2 => [[[1, 2, 3], [4, 5, 6]]]

On vide t2 :

t2=[] => []

Puis on recommence :

t1=[7,8,9] => [7, 8, 9]

t2<<t1 => [[7, 8, 9]]

t1=[10,11,12] => [10, 11, 12]

t2<<t1 => [[7, 8, 9], [10, 11, 12]]

t1=[13,14,15] => [13, 14, 15]

t2<<t1 => [[7, 8, 9], [10, 11, 12], [13, 14, 15]]

t3<<t2 => [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12], [13, 14, 15]]]

t3 est maintenant un tableau à 3 dimensions :

t3 => [[[1, 2, 3], [4, 5, 6]], [[7, 8, 9], [10, 11, 12], [13, 14, 15]]]

t3.size => 2

t3[0] => [[1, 2, 3], [4, 5, 6]]

t3[0].size => 2

t3[1] => [[7, 8, 9], [10, 11, 12], [13, 14, 15]]

t3[0].size => 3

t3[1][0] => [7, 8, 9]

t3[1][0][2] => 9

Remarques :

Dans un tableau multi-dimensonnel les sous-tableaux n'ont pas obligatoirement tous la même taille (par exemple ci-dessus le sous-tableau t3[0] possède 2 éléments alors que le sous-tableau t3[1] possède 3 éléments).

t=Array.new crée un tableau vide et est équivalent à t=[]

t=Array.new => []

En passant 1 paramètre à la méthode new on indique la dimension du tableau qui sera rempli par des valeurs nil :

t=Array.new(5) => [nil, nil, nil, nil, nil]

Avec 2 paramètres on indique la dimension et la valeur de remplissage du tableau :

t=Array.new(5,3) => [3, 3, 3, 3, 3]

La ligne suivante par exemple crée un tableau à 3 dimensions, avec 3 éléments dans chaque dimension et avec toutes les valeurs initialisées à 0 :

t=Array.new(3,Array.new(3,Array.new(3,0))) => [[[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0, 0], [0, 0, 0], [0, 0, 0]], [[0, 0 , 0], [0, 0, 0], [0, 0, 0]]]

 

Application : on désire générer des mots de passe syllabiques, c'est-à-dire avec une alternance de consonnes et de voyelles facilitant leur mémorisation comme par exemple ticaro ou zemipo.

On construit un tableau contenant les consonnes et un autre contenant les voyelles :

consonne=["b", "c", "d", "f", "g", "j", "k", "l", "m", "n", "p", "r", "s", "t", "v", "z"]

voyelle=["a","e","i","o","u"]

voyelle.size donne la taille du tableau voyelle : 5

Les voyelles vont de voyelle[0] à voyelle[4]

(rand*(voyelle.size-1)).round renvoie un entier aléatoire entre 0 et 4

voyelle[(rand*(voyelle.size-1)).round] donne une voyelle aléatoire

Et consonne[(rand*(consonne.size-1)).round] donne une consonne tirée aléatoirement dans le tableau consonne.

consonne[(rand*(consonne.size-1)).round]+voyelle[(rand*(voyelle.size-1)).round] donne une syllabe

mot="" initialise le mot de passe avec une chaîne vide.

mot<<consonne[(rand*(consonne.size-1)).round]+voyelle[(rand*(voyelle.size-1)).round] ajoute une syllabe au mot de passe.

3.times do mot<<consonne[(rand*(consonne.size-1)).round]+voyelle[(rand*(voyelle.size-1)).round] end ajoute 3 syllabes au mot de passe.

Enfin, la ligne suivante génère 8 mots de passe de 3 syllabes chacun :

8.times do mot="" ; 3.times do mot<<consonne[(rand*(consonne.size-1)).round]+voyelle[(rand*(voyelle.size-1)).round] end ; puts mot end

Et voici le résultat :

gefeli
vijebo
ronigu
vosofi
mepumo
safado
gezate
sadeka

Et la ligne de commande suivante permet de générer des mots de passe syllabiques directement dans un shell (Linux ou Windows) grâce à l'option -e de Ruby. A remarquer que toutes les doubles quotes ont été remplacées par des simples quotes afin que le programme soit renfermé dans une chaînes de caractères délimitée par des doubles quotes.

ruby -e "consonne=['b', 'c', 'd', 'f', 'g', 'j', 'k', 'l', 'm', 'n', 'p', 'r', 's', 't', 'v', 'z'] ; voyelle=['a','e','i','o','u'] ; 8.times do mot='' ; 3.times do mot<<consonne[(rand*(consonne.size-1)).round]+voyelle[(rand*(voyelle.size-1)).round] end ; puts mot end"

 

Comment "mélanger" un tableau, c'est-à-dire ordonner ses éléments aléatoirement ?

On part d'un tableau ordonné à 4 éléments :

t=["a","b","c","d"]

La méthode sort permet de réordonner le tableau, aléatoirement par une des deux solutions suivantes :

Solution 1 :

t.sort!{rand}

on obtient par exemple :

=> ["d", "c", "a", "b"]

Solution 2 :

t.sort!{|x,y|rand<=>rand}

on obtient par exemple :

=> ["c", "d", "b", "a"]

 

Retour en haut de la page

Procédures et fonctions

Une procédure se définit entre def et end :

def toto ; puts "bonjour" end

Appel de la procédure : toto

Passage de paramètre à une fonction :

def f(a,b) ; puts "a=#{a} et b=#{b}" end

Renvoie d'une valeur par une fonction :

def f(a,b) ; f=a+b end

Encore une fois de plus tout ceci est extrêmement intuitif : difficile de faire plus simple !

La classe et les méthodes d'une fonction sont celles de la valeur renvoyée (NilClass dans le cas d'une procédure).

Par exemple f(2,3).to_s donnera la chaîne "5".

La méthode to_s ne permet tout de même pas de sérialiser des fonctions, mais seulement des données.

Mais la désérialisation de fonctions par eval fonctionne naturellement :

s="def g(a,b) ; g=a-b end"
eval(s)

 

Retour en haut de la page

Les modules

Pour utiliser un module il faut taper la commande require.
Exemple : require 'cgi'

Pour gérer, installer ou lister les modules il faut utiliser la commande gem (dans irb ou dans le shell Linux).

Gem::Specification.all() renvoie des informations concernant les modules installés sous la forme d'un tableau.

Pour afficher la liste des modules installés :

Gem::Specification.all().map{|g| [g.name, g.version.to_s] }

 

Retour en haut de la page

Problèmes de caractères accentués, table ASCII, page de code, et unicode

Lors de l'affichage de chaînes de caractères contenant des accents, Ruby peut parfois planter en renvoyant l'erreur invalid multibyte char (US-ASCII) et refuser d'afficher les caractères accentués. Pour résoudre ce problème et préciser à Ruby que le fichier source est codé en ISO-8859-1 il faut ajouter la ligne suivante en tête du fichier source :

# encoding: ISO-8859-1

Voici tout ce qu'il faut savoir sur l'encodage des caractères afin d'éviter désormais les problèmes liés aux caractères accentués :

La norme ISO 8859-1 est également appelée Latin-1 ou Europe occidentale, et code 191 caractères de l'alphabet latin. Dans Dreamweaver le codage ISO 8859-1 est appelé Europe occidentale (menu Edition + Préférences + Nouveau document + codage par défaut, ou menu Modifier + Propriétés de la page + Titre/codage).

Il n'existe en fait que 3 types de jeux de caractères, et tôt ou tard il faut faire un choix parmi ces 3 solutions :

En résumé, la table ASCII et l'unicode sont parfaitement clairs, bien définis, normalisés, non ambigus et compris par tout le monde. En revanche les pages de code, qui étaient adaptées dans les années 90 lorsque l'information restait locale sans se déplacer, est à l'origine des problèmes de caractères accentués en informatiques, car chacun utilise sa propre page de code : chaque pays, chaque système, chaque logiciel. L'arrivée de nouveaux symboles comme le symbole monétaire de l'Euro a accentué le problème et l'incompatibilité entre les fichiers texte puisque chacun a rajouté dans la précipitation le symbole Euro dans sa page de code, mais sans se concerter : le symbole Euro a donc un code différent selon la page de code utilisée. Les pages de code ne sont donc plus adaptées à l'usage que l'on fait de l'information aujourd'hui, avec un partage mondial grâce au réseau de communication et d'échange d'informations Internet.

Et Ruby dans tout ça me diriez-vous ?

Le problème est que par défaut Ruby considère que le fichier source est codé exclusivement en US-ASCII, c'est-à-dire la table ASCII de base ne codant que 128 caractères sans caractères accentués (comme tous les fichers source en programmation, à partir du moment où on ne manipule pas de chaînes de caractères accentuées). Tout caractère ayant un code supérieur à 127 ne peut pas être interprété par Ruby si on ne lui précise pas quel jeu de caractères étendus il doit utiliser. Certes ceci ne fait pas très moderne pour le langage de programmation le plus récent, mais en fait ça permet à chacun de choisir sa table de caractères étendue selon la région du monde où Ruby est utilisé. Il ne faut pas oublier qu'à l'origine Ruby vient du Japon et qu'il est destiné à être utilisé sur toute la planète : il est donc normal, après réflexion, qu'il n'ait aucune préférence particulière pour les caractères étendus (pourquoi donnerait-il plus la priorité à l'alphabet Latin 1 plutôt qu'à l'alphabet Arabe, Chinois, Japonais, ou aux caractères étendues Américains ?). A chacun de préciser son jeu de caractères étendus, c'est ça aussi la mondialisation, en choisisant soit une page de code locale, soit l'unicode mondial encodé en UTF-8.

La ligne # encoding: ISO-8859-1 en tête du fichier source permet de préciser à Ruby le codage du fichier source avec la page de code ISO-8859-1 ce qui permet par exemple d'interpréter sans problème la ligne de code puts "Les élèves ont été évalués" sans l'erreur invalid multibyte char (US-ASCII) et en affichant correctement les caractères accentués. Mais le problème des caractères accentués demeure pour les données externes (lecture de fichier texte ou données arrivant sur l'entrée standard). Comment préciser à Ruby le codage des données externes si elles contiennent des caractères étendus ? Pour cela il faut utiliser l'option -E de Ruby sur la ligne de commande. Dans le cas d'un filtre en Ruby par exemple, destiné à traiter un fichier entree.txt encodé en ISO-8859-1 pour générer un fichier sortie.txt, la ligne de commande suivante supprime toutes les erreurs dues aux caractères accentués (en plus de la ligne # encoding: ISO-8859-1 en tête du fichier source programme.rb) :

ruby -EISO-8859-1 programme.rb < entree.txt > sortie.txt

 

Mais ISO-8859-1 n'est pas le seul jeu de caractères utilisable avec Ruby. Voici quelques jeux de caractères utilisables avec Ruby :

1 - La table ASCII

US-ASCII : table ascii de base sur 7 bits, commune aux 6 tables étendues ci-dessous. Les caractères 0x00 à 0x1F sont des caractères de contrôle et non des caractères imprimables (retour à la ligne, tabulation, sonnerie, escape, etc.)

ASCII-8BIT ou BINARY : codage sur 1 octet sans interprétation des caractères n°128 à 255

 

2 - Les pages de code à 256 caractères

ISO-8859-1 (ISO Latin1) : codage des caractères latins accentués, sans le caractère Euro ni l'e dans l'o. Les caractères 0x7F à 0x9F sont des caractères de contrôle et non des caractères imprimables. Cette page HTML que vous êtes en train de lire est codé en ISO-8859-1 (ce qui est précisé par la balise <meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" /> dans l'en-tête du fichier HTML)

ISO-8859-15 (ISO Latin9) : codage des caractères latins accentués, avec les caractères Euro et e dans l'o. Les caractères 0x7F à 0x9F sont des caractères de contrôle et non des caractères imprimables. Les caractères imprimables 0xA0 à 0xBF de la table ISO-8859-1 ont été remplacés par des caractères imprimables plus utiles dans la table ISO-8859-15 (avec notamment l'euro et l'e dans l'o). Les caractères 0xC0 à 0xFF sont identiques à la table ISO-8859-1.

CP = "Code Page" en anglais, soit "Page de code" en Français.

CP1252 (WinLatin1) : codage des caractères latins accentués, avec les caractères Euro et e dans l'o (Windows-1252 est aussi appelé ANSI par Microsoft). C'est le codage des caractères utilisé par le bloc note de Windows (si on enregistre le fichier au format ANSI). Microsoft a intégré dans la table CP1252 des caractères imprimables utiles (dont l'euro et l'e dans l'o) entre 0x7F et 0x9F à la place des caractères de contrôle. Les caractères 0xA0 à 0xFF sont identiques à la table ISO-8859-1. CP1252 = ISO-8859-1 avec quelques caractères imprimables supplémentaires à la place des caractères de contrôle. Dans le logiciel Table de caractères de Windows le jeu de caractères CP1252 est appelé Windows : Occidental.

Différences entre les pages de code Latin 9 (ISO-8859-15) et CP1252 (ANSI) : quelques caractères spéciaux différent entre les tables ISO-8859-15 et CP1252, dont le caractère Euro par exemple qui n'a pas le même code dans les deux tables. Les caractères latins accentués classiques ont eux les mêmes codes dans ces deux pages de code.

CP850 (DOSLatin1) : table ascii étendue utilisée par le DOS avec tous les caractères accentués dont les majuscules accentuées, et avec quelques caractères semi-graphiques mais seulement simple trait ou seulement double trait sans traits mixtes (page de code 850 sous MS-DOS, appelé aussi DOS : Europe de l'Ouest dans le logiciel Table de caractères de Windows). C'est cette page de code 850 qui est utilisée par EDIT sous MS-DOS 7 par défaut ou dans une fenêtre ligne de commande sous Windows XP.

CP437 (IBM PC) : table ascii étendue américaines, avec moins de caractères accentués et plus de caractères semi-graphiques que dans la page de code 850 (simple trait, double trait, et trait mixte simple-double), et avec les lettres grecques. La page de code 437 est une page de code définie par IBM et qui était utilisée aux États-Unis avec le système DOS et d'autres systèmes de la même époque, et a été la première définie matériellement sur les cartes d’affichage des premiers PC fabriqués par IBM. C'est une extension sur 8 bits de l’ASCII. Elle est encore utilisée dans les fenêtres de type console ou invite de commandes sur les systèmes Microsoft Windows aux États-Unis, ainsi que des émulateurs DOS comme DosBox. Dans le logiciel Table de caractères de Windows cette page de code 437 est appelée DOS : États-Unis. C'est cette page de code 437 qui est utilisée sur les clés USB bootables sous MS-DOS 7.1 pour faire tourner le logiciel Gecif sur des vieux PC encore aujourd'hui en 2014 (configuré dans CONFIG.SYS).

Différences entre les pages de code 850 et 437 : la page de code 850 contient plus de caractères accentués et est adaptée pour écrire du texte dans les langues de l'Europe occidentale, en minuscules comme en majuscules. La page de code 437 contient plus de caractères semi-graphiques et permet de dessiner toutes sortes de boîtes de dialogue en mode texte. Les littéraires occidentaux préféreront la 850, les programmeurs et les graphistes préfèreront la 437. A noter que les caractères accentués basiques utilisés en français ont la même position dans les deux tables.

MacRoman (Apple Macintosh) : page de code utilisée par Apple sur les ordinateurs Macintosh qui diffère de l'ISO 8859-1 de par les 32 premiers et 127 derniers caractères, mais inclut tout de même la plupart des caractères présents dans l'ISO 8859-1. Le symbole de l’euro (€) a remplacé le symbole monétaire générique « ¤ » précédent.

 

3 - L'unicode

UTF-8 : l'UTF-8 est un des algorithmes permettant d'encoder (sur 1 à 4 octets) dans un fichier texte n'importe quel caractère du jeu de caractères unicode contenant plus de 100 000 symboles. La table unicode est identique à l'ISO-8859-1 pour les caractères 0xA0 à 0xFF sauf qu'il sont codées sur 2 octets. UTF-8 est désormais le codage par défaut utilisé par Ubuntu qui utilise le jeu de caractères unicode.

 

En Ruby les objets chaînes de caractères disposent de 3 méthodes relatives à leur encodage :

Exemple :

"bonjour".encoding => #<Encoding:CP850>

"bonjour".encoding.to_s => "CP850"

"bonjour".encode("CP1252").encoding.to_s => "Windows-1252"

"bonjour".force_encoding("UTF-8").encoding.to_s => "UTF-8"

0x41.chr => "A"

0x41.chr.encoding => #<Encoding:US-ASCII>

0xCD.chr => "\xCD"

0xCD.chr.encoding => #<Encoding:ASCII-8BIT>

Donne le code ASCII (ou point de code) du même caractère dans différentes tables :

"é".encode("CP850").ord => 130

"é".encode("CP1252").ord => 233

"é".encode("UTF-8").ord => 233

Affichage du caractère de code ASCII 0xE9 à partir de deux tables différentes :

puts "\xE9".force_encoding("CP1252") => é

puts "\xE9".force_encoding("CP850") => Ú

Changement de codage d'une chaîne : la valeur binaire des octets reste constante, seule l'interprétation des caractères varie :

s="élève".encode("CP850") => "\x82l\x8Ave"

puts s => élève

s.force_encoding("CP1252") => "\x82l\x8Ave"

puts s => 'lSve

Autre exemple dans le sens inverse :

s="élève".encode("CP1252") => "\xE9l\xE8ve"

puts s.encoding => Windows-1252

puts s => élève

s.force_encoding("CP850") => "\xE9l\xE8ve"

puts s.encoding => CP850

puts s => ÚlÞve

Transcodage d'une même chaîne dans différentes tables : la valeur binaire des octets est cette fois modifiée :

s="élève".encode("UTF-8") => "\u00E9l\u00E8ve"

s=s.encode("CP1252") => "\xE9l\xE8ve"

s=s.encode("CP850") => "\x82l\x8Ave"

En Ruby un objet chaîne de caractères est systématiquement associé à une table de codage :

 

 

 

Retour en haut de la page

Accès aux fichiers et aux répertoires

La classe File permet de gérer les fichiers, et la classe Dir permet de gérer les répertoires.

Liste des méthodes de la classe Dir : Dir.methods

On y trouve par exemple les méthodes pwd et foreach.

Affichage du répertoire courant : puts Dir.pwd

Listage des fichiers du répertoire courant :

Dir.foreach(".") do |fic| puts fic end

Ouverture d'un fichier texte en lecture :

fic=File.new('toto.txt')

Affichage de toutes les lignes du fichier :

while(fic.gets) ; puts $_ end

Remise à zéro du pointeur de fichier afin de repartir de la première ligne :

fic.pos=0

Fermeture du fichier :

fic.close

Comme pour l'accès à la console, on retrouve dans les méthodes de la classe File :

- gets pour lire une ligne (s=string)
- getc pour lire un caractère (c=char)
- puts pour écrire une ligne
- putc pour écrire un caractère dont le code ASCII est donné

Création et écriture dans un nouveau fichier :

f=File.new('test.txt','w')
f.puts "une ligne"
f.puts "une autre ligne"
f.close

Et encore une fois tout ceci est extrêmement intuitif, simple et naturel : difficile de faire plus simple, c'est ça Ruby !

 

Retour en haut de la page

Accès aux variables d'environnement

L'objet ENV de Ruby est un tableau associatif contenant toutes les variables d'environnement accessibles par le programme.

ENV.keys affiches toutes les clés (le nom des variables d'environnement).

ENV.values affiche toutes les valeurs des variables d'environnement.

Exemple : puts ENV["path"]

Le script CGI suivant affiche la totalité du contenu du tableau associatif ENV :

#!/usr/bin/ruby
# Script CGI réalisé le 9 août 2014
# www.gecif.net
puts "Content-type: text/html\n\n"
puts "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n<title>Ruby</title>\n</head>\n<body>\n<font face=\"Verdana, Arial, Helvetica, sans-serif\">"

ENV.keys.each { |cle| puts cle+" : "+ENV[cle]+"<br><br>" }

puts "</font>\n</body>\n</html>"

 

Retour en haut de la page

Création de scripts CGI en Ruby sur le serveur de Gecif.net

Pour qu'un script en Ruby soit correctement interprété comme un CGI par le serveur il faut :

1 - que les 2 premières lignes du fichier soient :

#!/usr/bin/ruby
puts "Content-type: text/html\n\n"

2 - que l'éditeur dans lequel est écrit le script (par exemple Dreamweaver) utilise un simple LF comme retour à la ligne (et non un CR LF)

3 - que l'extension du fichier soit .rb

4 - que le chmod paramétrant les droits d'accès au fichier soit 755

Les caractères envoyés par le script Ruby sur la sortie standard par puts, print ou printf sont alors redirigés vers la fenêtre du navigateur client qui les interprétera comme un document source HTML.

Rappel : puts envoie sur la sortie standard la chaîne passée en paramètre suivie systématiquement d'un retour à la ligne, alors que print et printf envoient la chaîne sans retour à la ligne à la fin.

Exemple : puts "Bonjour" est équivalent à print "Bonjour\n" et à printf "Bonjour\n"

Par contre puts "Bonjour" et puts "Bonjour\n" sont équivalents (un seul \n envoyé après Bonjour dans les 2 cas).

Enfin, print "Bonjour\n\n" est équivalent à puts "Bonjour\n\n" (2 retour à la ligne \n, soit une ligne vide après la ligne Bonjour).

Et si la chaîne est entre simples côtes elle ne sera pas interprétée. Par exemple print '\n' enverra sur la sortie standard 2 caractères : un anti-slash et un n, et non un retour à la ligne.

Pour afficher dans le navigateur des caractères en mode texte sans mise en forme comme sur une console, il faut les encadrer par une balise HTML <pre>. Ainsi un \n envoyé sur la sortie standard du script CGI réalisera un réel retour à la ligne côté client (inutile d'ajouter un puts "<br>"). La balise HTML <pre> est bien pratique pour afficher du texte sans mise en forme comme dans un shell.

Par exemple le script CGI suivant affiche 3 lignes distinctes sans envoyer de balise <br> sur sa sortie standard : la balise <pre> affiche dans la fenêtre du navigateur le texte tel qu'il est dans le code source du fichier HTML :

#!/usr/bin/ruby
puts "Content-type: text/html\n\n"
puts "<pre>"
puts "Ceci est une première ligne"
puts "Ceci est une deuxième ligne"
puts "Et ceci est la dernière ligne"
puts "</pre>"

Et le résultat observé dans la fenêtre du navigateur client est le suivant :

Ceci est une première ligne
Ceci est une deuxième ligne
Et ceci est la dernière ligne

Pour préparer un document HTML utilisant la police Verdana on pourra envoyer la chaîne suivante sur la sortie standard :

puts "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n<title>Ruby</title>\n</head>\n<body>\n<font face=\"Verdana, Arial, Helvetica, sans-serif\">"

Et pour finir le document HTML on terminera le programme en Ruby par :

puts "</font>\n</body>\n</html>"

Par exemple le script CGI suivant affiche dans la fenêtre du navigateur client la liste des fichiers présents dans le répertoire courant du serveur :

#!/usr/bin/ruby
# Script CGI réalisé le 8 août 2014
# www.gecif.net
puts "Content-type: text/html\n\n"
puts "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n<title>Ruby</title>\n</head>\n<body>\n<font face=\"Verdana, Arial, Helvetica, sans-serif\">"
Dir.foreach(".") do |fic| puts fic+"<br>" end
puts "</font>\n</body>\n</html>"

En remplaçant le répertoire courant "." par ".." ou "../.." ou "../../.." etc. il est possible de visiter tous les répertoire du serveur en remontant jusqu'à la racine. Et connaissant le nom des fichiers on peut alors les afficher. Le script suivant affiche par exemple le contenu du fichier texte passwd présent dans le répertoire etc du serveur :

#!/usr/bin/ruby
# Script CGI réalisé le 8 août 2014
# www.gecif.net
puts "Content-type: text/html\n\n"
puts "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n<title>Ruby</title>\n</head>\n<body>\n<font face=\"Verdana, Arial, Helvetica, sans-serif\">"
fic=File.new('../../../../etc/passwd')
while(fic.gets) ; puts $_+"<br>" end
puts "</font>\n</body>\n</html>"

 

Les méthodes POST et GET de l'interface CGI :

Rappel : CGI (Common Gateway Interface) est une interface standardisée de communication entre le serveur Web (Apache par exemple) et les applications CGI. Pour établir cette communication, CGI utilise (ou redirige, plus exactement) l’entrée standard (Std In) et la sortie standard (Std Out) de l’application :

La communication de certaines données dans le sens serveur—> application CGI s’effectue aussi par l’intermédiaire des variables d’environnement CGI.

Pour communiquer les données saisies dans un formulaire dans le navigateur d’un client, dans le sens client—> serveur—> application CGI, il existe 2 méthodes possibles :

Voici les différences principales entre la méthode POST et la méthode GET :

La méthode POST :

La méthode GET :

 

Conversion des paramètres reçus par la méthode GET en un tableau associatif :

Imaginons que notre script CGI s'appelle toto.rb et qu'il est appelé avec les paramètres GET suivants :

http://fractale.gecif.net/scripts/toto.rb?a=1&b=2&c=3

Dans le programme en Ruby la variable ENV[QUERY_STRING] est alors une chaîne de caractères contenant a=1&b=2&c=3

L'idée est de convertir cette chaîne en une autre représentant la sérialisation d'un tableau associatif.

Le tableau associatif désiré est {"a"=>"1", "b"=>"2", "c"=>"3"}

Sa sérialisation est "{\"a\"=>\"1\", \"b\"=>\"2\", \"c\"=>\"3\"}"

Pour convertir la chaîne "a=1&b=2&c=3" en chaîne "{\"a\"=>\"1\", \"b\"=>\"2\", \"c\"=>\"3\"}" il y a 3 étapes :

Démonstration des 3 étapes :

s=ENV[QUERY_STRING].to_s => "a=1&b=2&c=3"

s=s.gsub("=","\"=>\"") => "a\"=>\"1&b\"=>\"2&c\"=>\"3"

s=s.gsub("&","\",\"") => "a\"=>\"1\",\"b\"=>\"2\",\"c\"=>\"3"

s="{\""+s+"\"}" => "{\"a\"=>\"1\",\"b\"=>\"2\",\"c\"=>\"3\"}"

A ce stade s est une chaîne de caractères représentant la sérialisation de notre tableau associatif. Il ne reste plus qu'à l'évaluer pour obtenir un réel tableau associatif fonctionnel :

h=eval(s) => {"a"=>"1", "b"=>"2", "c"=>"3"}

h.class => Hash

h est maintenant un véritable tableau associatif contenant tous les paramètres reçus par notre script CGI avec la méthode GET.

Et voici maintenant la conversion de la chaîne ENV[QUERY_STRING] en tableau associatif h en une seule ligne de code :

h=eval("{\""+ENV["QUERY_STRING"].gsub("&","\",\"").gsub("=","\"=>\"")+"\"}")

En toute rigueur la conversion de la chaîne ENV["QUERY_STRING"] en tableau associatif ne peut s'effectuer que si la chaîne ENV["QUERY_STRING"] n'est pas vide.

Le script CGI suivant affiche la chaîne brut QUERY_STRING, puis la convertit en tableau associatif h et affiche clairement le contenu du tableau associatif mais seulement si la chaîne n'est pas vide :

#!/usr/bin/ruby
# Script CGI réalisé le 9 août 2014
# www.gecif.net
puts "Content-type: text/html\n\n"
puts "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\">\n<title>Ruby</title>\n</head>\n<body>\n<font face=\"Verdana, Arial, Helvetica, sans-serif\">"
puts "ENV[QUERY_STRING] : "+ENV["QUERY_STRING"]+"<br><br>"
if !ENV["QUERY_STRING"].empty? then
   h=eval("{\""+ENV["QUERY_STRING"].gsub("&","\",\"").gsub("=","\"=>\"")+"\"}")
   h.keys.each { |cle| puts cle+" = "+h[cle]+"<br><br>" }

else
   puts "Aucun paramètres GET reçus ...<br><br>"
end
puts "</font>\n</body>\n</html>"

En appelant le script avec comme paramètres GET http://fractale.gecif.net/scripts/toto.rb?a=1&b=2&c=3 le résultat affiché dans la fenêtre du client est :

ENV[QUERY_STRING] : a=1&b=2&c=3

a = 1

b = 2

c = 3

Mais la conversion de la chaîne QUERY_STRING en tableau associatif n'est possible que si elle est bien formée (alternance de symboles = et & séparés par des caractères alphanumériques et non spéciaux). Pour tester le format de la chaîne QUERY_STRING avant la conversion on pourra utiliser l'expression régulière suivante qui teste si le chaîne QUERY_STRING contient au moins un paramètre GET :

/^\w+=\w+(&\w+=\w+)*$/

Dans le script CGI ci-dessus on peut alors remplacer le test if !ENV["QUERY_STRING"].empty? then par if ENV["QUERY_STRING"] =~ /^\w+=\w+(&\w+=\w+)*$/ then afin que la conversion en tableau associatif ne soit effectuée que si la chaîne QUERY_STRING représente bien un ou plusieurs paramètres GET.

 

Envoie de caractères Unicode au client dans une page encodée en UTF-8 :

Le script CGI suivant génère une page HTML où les caractères sont codés en UTF-8, et affiche dans la fenêtre du navigateur les caractères unicode n° 0x2700 à n° 0x27BF (symboles graphiques) :

#!/usr/bin/ruby
# Script CGI réalisé le 23 août 2014
# www.gecif.net
puts "Content-type: text/html\n\n"
puts "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\">\n <title>Ruby</title>\n</head>\n<body>\n<font size=\"+6\">"
0x9C.upto(0x9E) do |i|
  0x80.upto(0xBF) do |j|
    puts 0xE2.chr+i.chr+j.chr
  end
end
puts "</font>\n</body>\n</html>"

Cliquez ici pour tester ce script CGI dans une nouvelle fenêtre avec votre navigateur.

Remarques :

Mais il n'est pas nécessaire d'encoder le fichier source en UTF-8 ou d'envoyer des séquences d'octets en UTF-8 pour afficher des caractères unicode sur une page web. En effet, les entités numériques HTML de la forme &#1234; permettent d'afficher très simplement le caractère unicode n°1234 (code exprimé en décimal). Et l'entité HTML &#x1234; permet d'afficher le caractère unicode n°1234 où le code est exprimé ici en hexadécimal. Le fichier source HTML peut quant à lui être codé en page de code ISO-8859-1 ou même en simple table US-ASCII.

Le seul inconvénient des entités HTML pour afficher des caractères unicode est qu'il faut alors 8 octets pour enregistrer un caractères : là où le codage UTF-8 envoie seulement 3 octets au client, les entités HTML enverront 8 octets, représentant 8 caractères US-ASCII décrivant le code hexadécimal du caractère. Mais l'avantage est que c'est très simple de générer ainsi une table de caractères prise dans le jeu unicode. Le script CGI suivant affiche par exemple les caractères unicode n° 0x2200 à n° 0x23FF (symboles mathématiques et techniques) en envoyant simplement au navigateur client les entités HTML numériques &#x2200; à &#x23FF; (séparés par un espace insécable &nbsp;) :

#!/usr/bin/ruby
# Script CGI ralise le 24 aout 2014
# www.gecif.net
puts "Content-type: text/html\n\n"
puts "<html>\n<head>\n<meta http-equiv=\"Content-Type\" content=\"text/html; charset=iso-8859-1\"> \n<title>Ruby</title>\n</head>\n<body>\n<font size=\"+6\">"
0x2200.upto(0x23FF) do |i|
  puts "&#x"+i.to_s(16)+";&nbsp;"
end
puts "</font>\n</body>\n</html>"

Cliquez ici pour tester ce script CGI dans une nouvelle fenêtre avec votre navigateur.

 

Retour en haut de la page

Création d'un générateur de questions de seconde génération pour QCM

On part d'un fichier texte de donnée contenant 10 lignes caractérisant les liaisons cinématiques (toutes sauf l'hélicoïdale dont les mouvements autorisés ne sont pas calculables automatiquement à partir des degrés de liberté) nommé donnees.txt :

encastrement;0;0
glissière;1;0
pivot;1;1
pivot glissant;2;1
appui plan;3;1
rotule;3;3
sphère / plan;5;3
cylindre / plan;4;2
sphère / cylindre;4;3
rotule à doigt;2;2

Chaque lignes contient 3 colonnes séparées par des point-virgules avec :

A partir de ce fichier de donnée matriciel on désire obtenir tout un lot de questions sur les liaisons cinématiques, avec un nombre variable de réponses proposées, dans un fichier nommé questions.txt.

La transformation du fichier donnees.txt vers le fichier questions.txt sera effectuée par un programme en Ruby nommé generateur.rb et contenant toutes les requêtes de transformation.

Le programme generateur.rb doit lire les données sur son entrée standard, et doit envoyer les questions sur sa sortie standard. L'ensemble des questions sera ainsi généré grâce à la ligne de commande suivante :

ruby generateur.rb < donnees.txt > questions.txt

Rappel : si le fichier de données contient des caractères accentués et qu'il est codé en ISO-8859-1 il faut le préciser à Ruby avec l'option -E sur la ligne de commande afin qu'il puisse interpréter les caractères étendues (dont le code est supérieur à 127) :

ruby -EISO-8859-1 generateur.rb < donnees.txt > questions.txt

L'exemple vu ici est adaptable à tous les générateurs partant d'un fichier de données matriciel (structuré en lignes et en colonnes avec plus de 2 colonnes), par opposition à un simple fichier question;réponse.

La première étape du programme generateur.rb est de charger les données reçus sur son entrée standard dans une structure de donnée. Le tableau à 2 dimension est ici la structure de donnée la plus simple pour sauvegarder l'ensemble des lignes et des champs du fichier texte donnees.txt à structure matricielle (plusieurs champs par ligne séparé par un point-virgule).

tab=[]
while (s=gets)
s=s.sub("\n","")
tab << s.split(";")
end

Ainsi sauvegardées, les données sont accessibles dans le tableau à 2 dimensions tab où le premier indice représente la ligne du fichier donnees.txt et le second indice désigne le champs sur cette ligne. Exemple : tab[2][0] est le 1er champs (indice 0) de la 3ème ligne (indice 2).

Définissons une fonction qui génère une question. Cette fonction question attend 3 paramètres : la questions q, l'unique bonne réponse r, et un tableau t contenant toutes les mauvaises réponses. Le nombre de mauvaises réponses est variable et dépend de la taille du tableau :

def question(q,r,t)
puts 'quest("'+q.to_s+'//a");'
puts 'rep("(o) '+r.to_s+'");'
0.upto(t.size-1) do |i| puts 'rep("( ) '+t[i].to_s+'");' end
end

Si par exemple on appelle la fonction question avec les paramètres suivants :

question("Combien de degrés de liberté possède la liaison pivot ?",1,[0,2,3,4,5,6])

On obtient alors sur la sortie standard la question mise en forme au format du QCM :

quest("Combien de degrés de liberté possède la liaison pivot ?//a");
rep("(o) 1");
rep("( ) 0");
rep("( ) 2");
rep("( ) 3");
rep("( ) 4");
rep("( ) 5");
rep("( ) 6");

Si on veut la réponse "Aucune de ces propositions" parmi les boutons radio il faudra alors l'inclure volontairement soit dans l'unique bonne réponse soit dans le tableau des réponses fausses.

Remarque : en cas de problèmes dus à l'affichage des caractères accentués, voir le paragraphe "Problèmes de caractères accentués, table ASCII, page de code, et unicode" qui y est consacré, avec toutes les solutions.

Si malgré tout ça les erreurs dues aux caractères accentués persistent, une des solutions possibles est d'utiliser le codage html pour les caractères spéciaux, étant donné qu'au final l'ensemble des questions du QCM sera affiché dans un navigateur. Le fichier de donnée de départ pourrait être le suivant :

encastrement@0@0
glissi&egrave;re@1@0
pivot@1@1
pivot glissant@2@1
appui plan@3@1
rotule@3@3
sph&egrave;re / plan@5@3
cylindre / plan@4@2
sph&egrave;re / cylindre@4@3
rotule &agrave; doigt@2@2

Attention : dans ce cas le séparateur de champs ne peut plus être le point-virgule car il est utilisé par le codage des caractères accentués. Il faut choisir un caractère séparateur qui ne soit pas utilisé dans les réponses ou pour la ponctuation normale. Il y a par exemple les caractères @ ou | ou _. Dans l'exemple ci-dessus c'est le caractère arobase qui sert de séparateur de champs.

Voyons maintenant comment générer automatiquement un lot de questions.

Créons un tableau contenant tous les degrés de liberté possibles :

t=["0","1","2","3","4","5","6"]
=> ["0", "1", "2", "3", "4", "5", "6"]

La méthode find_all permet d'obtenir un tableau composé seulement des éléments répondant à une condition précise :

t.find_all {|s| s!="2"}
=> ["0", "1", "3", "4", "5", "6"]

La ligne suivant pose la question pour la première liaison cinématique :

question("Combien de degrés de liberté possède la liaison #{tab[0][0]} ?",tab[0][1],t.find_all {|s| s!=tab[0][1]})

Et la réponse est :

quest("Combien de degrés de liberté possède la liaison encastrement ?//a");
rep("(o) 0");
rep("( ) 1");
rep("( ) 2");
rep("( ) 3");
rep("( ) 4");
rep("( ) 5");
rep("( ) 6");

La ligne suivante pose la même question pour chacune des 10 liaisons cinématiques (10 questions générées) :

0.upto(9) do |n| question("Combien de degrés de liberté possède la liaison "+tab[n][0]+" ?",tab[n][1],t_degre.find_all {|s| s!=tab[n][1]}) end

Et le bloc suivant demande chacune des 6 caractéristiques (en rouge ci-dessous) pour chacune des 10 liaisons cinématiques (60 questions générées) :

0.upto(9) do |n|
bonne_reponse=tab[n][1]
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de degrés de liberté possède la liaison "+tab[n][0]+" ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=(6-tab[n][1].to_i).to_s
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de degrés de liaison possède la liaison "+tab[n][0]+" ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=tab[n][2]
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de rotations la liaison "+tab[n][0]+" autorise-t-elle ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=(3-tab[n][2].to_i).to_s
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de rotations la liaison "+tab[n][0]+" interdit-elle ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=(tab[n][1].to_i-tab[n][2].to_i).to_s
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de translations la liaison "+tab[n][0]+" autorise-t-elle ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=(3-(tab[n][1].to_i-tab[n][2].to_i)).to_s
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de translations la liaison "+tab[n][0]+" interdit-elle ?",bonne_reponse,mauvaises_reponses)

end

On désire maintenant générer une série de questions du style "Quelles liaisons possèdent 2 degrés de liberté ?", mais en utilisant :

Créons une nouvelle fonction générant une questions avec des cases à cocher (et non des boutons radio). Cette fonction attend 3 paramètres : la question, un tableau contenant toutes les bonnes réponses et un tableau contenant toutes les mauvaises réponses :

def case_a_cocher(q,rep_oui,rep_non)
if rep_oui==[] then rep_oui=["Aucune de ces propositions"]
else rep_non << "Aucune de ces propositions"
end
puts 'quest("'+q.to_s+'//a");'
0.upto(rep_oui.size-1) do |i| puts 'rep("[x] '+rep_oui[i].to_s+'");' end
0.upto(rep_non.size-1) do |i| puts 'rep("[ ] '+rep_non[i].to_s+'");' end
end

Remarque : la case à cocher particulière "Aucune de ces propositions" ne sera à cocher que si le tableau des bonnes réponses est vide et elle est ajoutée automatiquement à la liste des réponses par la fonction case_a_cocher elle-même (inutile ici de passer "Aucune de ces propositions" en paramètre, contrairement aux boutons radio).

Par exemple le code suivant :

bonnes_reponses=[]
mauvaises_reponses=[]
0.upto(tab.size-1) do |i|
  if tab[i][1]=="2" then bonnes_reponses << tab[i][0]
  else mauvaises_reponses << tab[i][0]
  end
end

case_a_cocher("Quelles liaisons possèdent 2 degrés de liberté ?",bonnes_reponses,mauvaises_reponses)

Envoie sur la sortie standard la question suivante :

quest("Quelles liaisons possèdent 2 degrés de liberté ?//a");
rep("[x] pivot glissant");
rep("[x] rotule a doigt");
rep("[ ] encastrement");
rep("[ ] glissière");
rep("[ ] pivot");
rep("[ ] appui plan");
rep("[ ] rotule");
rep("[ ] sphère / plan");
rep("[ ] cylindre / plan");
rep("[ ] sphère / cylindre");

rep("[ ] Aucune de ces propositions");

Et le bloc suivant pose la même question avec un nombre de degrés de liberté allant de 0 à 6 (7 questions générées) :

0.upto(6) do |n|
  bonnes_reponses=[]
  mauvaises_reponses=[]
  0.upto(tab.size-1) do |i|
    if tab[i][1]==n.to_s then bonnes_reponses << tab[i][0]
    else mauvaises_reponses << tab[i][0]
    end
  end
case_a_cocher("Quelles liaisons possèdent "+n.to_s+" degrés de liberté ?",bonnes_reponses,mauvaises_reponses)
end

Mais pour l'instant on propose systématiquement les 10 liaisons en réponse, en plus de la réponse "Aucune de ces propositions". Il serait intéressant de varier le nombre de réponses, en proposant entre 4 et 8 liaisons (la case "Aucune de ces propositions" serait alors utilisée plus souvent). Rappel du nombre de combinaisons pour les 5 cas :

Pour obtenir toutes les combinaisons on peut demander à Prolog. Voici par exemple le résultat donné par Prolog en lui demandant toutes les listes à 8 éléments prises dans la liste à 10 éléments [0,1,2,3,4,5,6,7,8,9]. Le résultats obtenu est 45 listes :

[0,1,2,3,4,5,6,7]
[0,1,2,3,4,5,6,8]
[0,1,2,3,4,5,6,9]
[0,1,2,3,4,5,7,8]
[0,1,2,3,4,5,7,9]
[0,1,2,3,4,5,8,9]
[0,1,2,3,4,6,7,8]
[0,1,2,3,4,6,7,9]
[0,1,2,3,4,6,8,9]
[0,1,2,3,4,7,8,9]
[0,1,2,3,5,6,7,8]
[0,1,2,3,5,6,7,9]
[0,1,2,3,5,6,8,9]
[0,1,2,3,5,7,8,9]
[0,1,2,3,6,7,8,9]
[0,1,2,4,5,6,7,8]
[0,1,2,4,5,6,7,9]
[0,1,2,4,5,6,8,9]
[0,1,2,4,5,7,8,9]
[0,1,2,4,6,7,8,9]
[0,1,2,5,6,7,8,9]
[0,1,3,4,5,6,7,8]
[0,1,3,4,5,6,7,9]
[0,1,3,4,5,6,8,9]
[0,1,3,4,5,7,8,9]
[0,1,3,4,6,7,8,9]
[0,1,3,5,6,7,8,9]
[0,1,4,5,6,7,8,9]
[0,2,3,4,5,6,7,8]
[0,2,3,4,5,6,7,9]
[0,2,3,4,5,6,8,9]
[0,2,3,4,5,7,8,9]
[0,2,3,4,6,7,8,9]
[0,2,3,5,6,7,8,9]
[0,2,4,5,6,7,8,9]
[0,3,4,5,6,7,8,9]
[1,2,3,4,5,6,7,8]
[1,2,3,4,5,6,7,9]
[1,2,3,4,5,6,8,9]
[1,2,3,4,5,7,8,9]
[1,2,3,4,6,7,8,9]
[1,2,3,5,6,7,8,9]
[1,2,4,5,6,7,8,9]
[1,3,4,5,6,7,8,9]
[2,3,4,5,6,7,8,9]

Il faut maintenant utiliser cette liste afin d'obtenir 45 questions proposant à chaque fois seulement les 8 réponses indiqué dans chacune des liste. Pour cela on va varier l'intervalle de valeur de la variable i dans le programme précédent. Pour l'instant i va une seule fois de 0 à 9 : il en résulte une seule question proposant 10 réponses. Il faut faire en sorte que i parcourt 45 fois les ensembles de valeurs indiqué dans chacune des listes.

Commençons par convertir la liste donnée en Prolog en tableau liste déclaré directement dans le code source du générateur generateur.rb. Pour cela il suffit de remplacer les crochets fermant ] par ], (un tableau pouvant être déclaré sur plusieurs lignes) sauf pour la dernière ligne finissant par ]] :

liste=[
[0,1,2,3,4,5,6,7],
[0,1,2,3,4,5,6,8],
[0,1,2,3,4,5,6,9],
[0,1,2,3,4,5,7,8],
[0,1,2,3,4,5,7,9],
[0,1,2,3,4,5,8,9],
[0,1,2,3,4,6,7,8],
[0,1,2,3,4,6,7,9],
[0,1,2,3,4,6,8,9],
[0,1,2,3,4,7,8,9],
[0,1,2,3,5,6,7,8],
[0,1,2,3,5,6,7,9],
[0,1,2,3,5,6,8,9],
[0,1,2,3,5,7,8,9],
[0,1,2,3,6,7,8,9],
[0,1,2,4,5,6,7,8],
[0,1,2,4,5,6,7,9],
[0,1,2,4,5,6,8,9],
[0,1,2,4,5,7,8,9],
[0,1,2,4,6,7,8,9],
[0,1,2,5,6,7,8,9],
[0,1,3,4,5,6,7,8],
[0,1,3,4,5,6,7,9],
[0,1,3,4,5,6,8,9],
[0,1,3,4,5,7,8,9],
[0,1,3,4,6,7,8,9],
[0,1,3,5,6,7,8,9],
[0,1,4,5,6,7,8,9],
[0,2,3,4,5,6,7,8],
[0,2,3,4,5,6,7,9],
[0,2,3,4,5,6,8,9],
[0,2,3,4,5,7,8,9],
[0,2,3,4,6,7,8,9],
[0,2,3,5,6,7,8,9],
[0,2,4,5,6,7,8,9],
[0,3,4,5,6,7,8,9],
[1,2,3,4,5,6,7,8],
[1,2,3,4,5,6,7,9],
[1,2,3,4,5,6,8,9],
[1,2,3,4,5,7,8,9],
[1,2,3,4,6,7,8,9],
[1,2,3,5,6,7,8,9],
[1,2,4,5,6,7,8,9],
[1,3,4,5,6,7,8,9],
[2,3,4,5,6,7,8,9]]

Ensuite il faut, pour chaque ligne de cette liste, que i prenne les 8 valeurs indiquées. Pour cela on utilise l'énumérateur each des tableaux :

liste.each do |combi| donne comme valeur à combi chacun des 45 sous-tableaux du tableau liste

combi.each do |i| donne à i chacune des 8 valeurs du tableau combi

Le bloc suivant pose 7 questions pour chacune des 45 combinaisons données par la liste en Prolog (7x45=315 questions générées) :

0.upto(6) do |n|
liste.each do |combi|
  bonnes_reponses=[]
  mauvaises_reponses=[]
  combi.each do |i|
    if tab[i][1]==n.to_s then bonnes_reponses << tab[i][0]
    else mauvaises_reponses << tab[i][0]
    end
  end
  case_a_cocher("Quelles liaisons possèdent "+n.to_s+" degrés de liberté ?",bonnes_reponses,mauvaises_reponses)
end
end

Pour changer le nombre de réponses proposées dans les cases à cocher il suffit de modifier le tableau liste en le remplaçant par un autre tableau donné par Prolog. Mieux encore, sachant que les sous-tableaux d'un tableau de tableau en Ruby ne sont pas forcément tous de la même taille, on peut mettre toutes les listes générées par Prolog dans un seul tableau liste. Par exemple la liste suivante générera des questions avec 8, 4, 6 et 5 réponses :

liste=[
[0,1,2,3,4,5,6,7],
[0,1,2,3,4,5,6,8],
[0,1,2,3,4,5,6,9],
[0,1,2,3,4,5,7,8],
[0,1,2,3,4,5,7,9],
[0,1,2,3,4,5,8,9],
[0,1,2,3,4,6,7,8],
[0,1,2,3,4,6,7,9],
[0,1,2,3,4,6,8,9],
[0,1,2,3,4,7,8,9],
[3,7,8,9],
[4,5,6,7],
[4,5,6,8],
[4,5,6,9],
[4,5,7,8],
[4,5,7,9],
[4,5,8,9],
[4,6,7,8],
[4,6,7,9],
[4,6,8,9],
[4,7,8,9],
[5,6,7,8],
[5,6,7,9],
[5,6,8,9],
[5,7,8,9],
[6,7,8,9],
[2,3,4,5,7,9],
[2,3,4,5,8,9],
[2,3,4,6,7,8],
[2,3,4,6,7,9],
[2,3,4,6,8,9],
[2,3,4,7,8,9],
[2,3,5,6,7,8],
[2,3,5,6,7,9],
[2,3,5,6,8,9],
[2,3,5,7,8,9],
[2,3,6,7,8,9],
[3,4,5,8,9],
[3,4,6,7,8],
[3,4,6,7,9],
[3,4,6,8,9],
[3,4,7,8,9],
[3,5,6,7,8],
[3,5,6,7,9],
[3,5,6,8,9],
[3,5,7,8,9],
[3,6,7,8,9],
[4,5,6,7,8]]

Mais si on utilise systématiquement toutes les combinaisons possibles le nombre de questions générées devient vite très important (plus de 20 000 questions en utilisant tous les cas précédents). L'idée est alors de n'utiliser qu'une partie des listes des combinaisons générées en Prolog. Mais quelle partie utiliser ? C'est là que finalement Prolog n'est plus indispensable, et que Ruby peut créer lui-même ses propres listes en tirant au hasard les valeurs les composant. Cela permet une répartition aléatoire des réponses (comme précédemment), mais en maîtrisant le nombre final de questions.

(rand*9).round renvoie un entier entre 0 (compris) et 9 (compris)

=> 7

Le bloc suivant remplit le tableau tab avec 6 entiers de 0 à 9 tous différents mais rangés dans l'ordre croissant :

tab=[] ; while (tab.size!=6) do tab << (rand*9).round ; tab=tab.uniq.sort end

=> [2, 3, 4, 6, 7, 8]

Et le bloc suivant remplit le tableau liste avec 10 tableaux, tous différents, contenant chacun 6 entiers uniques. Ce bloc peut donc générer le tableau liste directement dans le programme en Ruby et remplacer la génération exhaustives de toutes les combinaisons par Prolog :

liste =[]
while (liste.size!=10) do
tab=[] ; while (tab.size!=6) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab
liste=liste.uniq end

=> [[1, 2, 3, 6, 8, 9], [0, 1, 2, 3, 6, 8], [0, 2, 3, 4, 7, 8], [2, 3, 4, 5, 7, 8], [0, 4, 6, 7, 8, 9], [0, 1, 2, 4, 5, 6], [1, 2, 3, 5, 8, 9], [2, 3, 4, 6, 7, 8], [2, 5, 6, 7, 8, 9], [0, 1, 2, 3, 4, 7]]

Et si on veut 45 questions à 8 réponses, 120 questions à 7 réponses, 140 questions à 6 réponses, 160 questions à 5 réponses et 180 questions à 4 réponses on préparera le tableau liste avec le code suivant :

liste =[]

while (liste.size!=45) do
tab=[] ; while (tab.size!=8) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

while (liste.size!=165) do
tab=[] ; while (tab.size!=7) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

while (liste.size!=305) do
tab=[] ; while (tab.size!=6) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

while (liste.size!=465) do
tab=[] ; while (tab.size!=5) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

while (liste.size!=645) do
tab=[] ; while (tab.size!=4) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

On obtient ainsi 645 questions, avec les réponses suffisamment mélangées, et avec un nombre de réponses variables de 4 à 8 (en plus de la réponse "Aucune de ces propositions" ajoutée systématiquement). Pour les questions à 8 et 7 réponses nous avons demandé à Ruby toutes les combinaisons possibles (respectivement 45 et 120). Il nous trouve alors les mêmes listes que Prolog. Pour les questions à 6, 5 et 4 réponses nous n'avons demandé qu'une partie de toutes les combinaisons : Ruby nous donne alors une sous-liste (tirée aléatoirement) de la liste exhaustive que Prolog aurait générée.

Passons maintenant à la génération automatique des question en utilisant le tableau liste pour la répartition des réponses proposées dans les cases à cocher.

Le bloc suivant demande le nombre de degrés de liaison en plus des degrés de liberté (630 questions générées pour une liste de 45 combinaisons) :

0.upto(6) do |n|
liste.each do |combi|
  bonnes_reponses=[]
  mauvaises_reponses=[]
  combi.each do |i|
    if tab[i][1]==n.to_s then bonnes_reponses << tab[i][0]
    else mauvaises_reponses << tab[i][0]
    end
  end
  case_a_cocher("Quelles liaisons possèdent "+n.to_s+" degrés de liberté ?",bonnes_reponses,mauvaises_reponses)
  bonnes_reponses=[]
  mauvaises_reponses=[]
  combi.each do |i|
    if tab[i][1]==(6-n).to_s then bonnes_reponses << tab[i][0]
    else mauvaises_reponses << tab[i][0]
    end
  end
  case_a_cocher("Quelles liaisons possèdent "+n.to_s+" degrés de liaison ?",bonnes_reponses,mauvaises_reponses)
end
end

Le bloc suivant pose 4 questions en demandant respectivement les rotations autorisées puis interdites, et les translations autorisées puis interdites :

1.upto(3) do |n|
liste.each do |combi|

bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][2]==n.to_s then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons autorisent "+n.to_s+" rotations ?",bonnes_reponses,mauvaises_reponses)

bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][2]==(3-n).to_s then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons interdisent "+n.to_s+" rotations ?",bonnes_reponses,mauvaises_reponses)

bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][1].to_i-tab[i][2].to_i==n then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons autorisent "+n.to_s+" translations ?",bonnes_reponses,mauvaises_reponses)

bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][1].to_i-tab[i][2].to_i==3-n then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons interdisent "+n.to_s+" translations ?",bonnes_reponses,mauvaises_reponses)

end
end

Enfin voici le code Ruby du générateur complet generateur.rb permettant de convertir le fichier de donnée donnee.txt en plusieurs centaines de questions dans le fichier questions.txt grâce à la ligne de commande ruby generateur.rb < donnees.txt > questions.txt. Le nombre exact de questions générées dépend du tableau liste qui peut être complété :

# encoding: ISO-8859-1
###############################################################
# Générateur de questions pour QCM en Ruby
# Réalisé par Jean-Christophe MICHEL le 20 août 2014
# www.gecif.net
###############################################################

###############################################################
# Préparation de la liste contenant toutes les combinaisons
# des réponses à proposer dans les cases à cocher
# (cette liste peut aussi être générée par Prolog)
###############################################################

liste =[]

while (liste.size!=45) do
tab=[] ; while (tab.size!=8) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

while (liste.size!=165) do
tab=[] ; while (tab.size!=7) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

while (liste.size!=305) do
tab=[] ; while (tab.size!=6) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

while (liste.size!=465) do
tab=[] ; while (tab.size!=5) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

while (liste.size!=645) do
tab=[] ; while (tab.size!=4) do tab << (rand*9).round ; tab=tab.uniq.sort end
liste << tab ; liste=liste.uniq end

###############################################################
# Fonction question
###############################################################

def question(q,r,t)
puts 'quest("'+q.to_s+'//a");'
puts 'rep("(o) '+r.to_s+'");'
0.upto(t.size-1) do |i| puts 'rep("( ) '+t[i].to_s+'");' end
end

###############################################################
# Fonction case_a_cocher
###############################################################

def case_a_cocher(q,rep_oui,rep_non)
if rep_oui==[] then rep_oui=["Aucune de ces propositions"]
else rep_non << "Aucune de ces propositions"
end
puts 'quest("'+q.to_s+'//a");'
0.upto(rep_oui.size-1) do |i| puts 'rep("[x] '+rep_oui[i].to_s+'");' end
0.upto(rep_non.size-1) do |i| puts 'rep("[ ] '+rep_non[i].to_s+'");' end
end

###############################################################
# Programme principal
###############################################################

# Charge la structure de donnée à partir de l'entrée standard :
tab=[]
while (s=gets)
s=s.sub("\n","")
tab << s.split(";")
end

# tableau permettant de formuler les mauvaises réponses
# en y supprimant l'unique bonne réponse :

t_degre=["0","1","2","3","4","5","6"]

# Génère les questions directes à boutons radio :

0.upto(9) do |n|
bonne_reponse=tab[n][1]
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de degrés de liberté possède la liaison "+tab[n][0]+" ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=(6-tab[n][1].to_i).to_s
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de degrés de liaison possède la liaison "+tab[n][0]+" ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=tab[n][2]
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de rotations la liaison "+tab[n][0]+" autorise-t-elle ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=(3-tab[n][2].to_i).to_s
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de rotations la liaison "+tab[n][0]+" interdit-elle ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=(tab[n][1].to_i-tab[n][2].to_i).to_s
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de translations la liaison "+tab[n][0]+" autorise-t-elle ?",bonne_reponse,mauvaises_reponses)

bonne_reponse=(3-(tab[n][1].to_i-tab[n][2].to_i)).to_s
mauvaises_reponses=t_degre.find_all {|s| s!=bonne_reponse}
question("Combien de translations la liaison "+tab[n][0]+" interdit-elle ?",bonne_reponse,mauvaises_reponses)

end

# Génère les questions à case à cocher en utilisant toutes les combinaisons présentes dans le tableau liste :

# Degrés de liberté et de liaison
0.upto(6) do |n|
liste.each do |combi|
bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][1]==n.to_s then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons possèdent "+n.to_s+" degrés de liberté ?",bonnes_reponses,mauvaises_reponses)
bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][1]==(6-n).to_s then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons possèdent "+n.to_s+" degrés de liaison ?",bonnes_reponses,mauvaises_reponses)
end
end

# Rotations et translations autorisés et interdites :
1.upto(3) do |n|
liste.each do |combi|

bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][2]==n.to_s then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons autorisent "+n.to_s+" rotations ?",bonnes_reponses,mauvaises_reponses)

bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][2]==(3-n).to_s then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons interdisent "+n.to_s+" rotations ?",bonnes_reponses,mauvaises_reponses)

bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][1].to_i-tab[i][2].to_i==n then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons autorisent "+n.to_s+" translations ?",bonnes_reponses,mauvaises_reponses)

bonnes_reponses=[]
mauvaises_reponses=[]
combi.each do |i|
if tab[i][1].to_i-tab[i][2].to_i==3-n then bonnes_reponses << tab[i][0]
else mauvaises_reponses << tab[i][0]
end
end
case_a_cocher("Quelles liaisons interdisent "+n.to_s+" translations ?",bonnes_reponses,mauvaises_reponses)

end
end

 

Retour en haut de la page

Création d'un générateur de questions du type "Cochez les 2 seuls nombres égaux"

On désire obtenir une série de questions proposant dans les réponses 6 nombres, dont 3 en décimal et 3 en hexadécimal, avec seulement une égalité entre un nombre en décimal et un nombre en hexadécimal.

Voici le programme source en Ruby qui génère les 6 nombres séparés par un point-virgule sur sa sortie standard :

#######################################################################################################
# Ce programme permet de générer des questions de type "Cochez les 2 seuls nombres qui sont égaux"
# Réalisé par Jean-Christophe MICHEL le 19 décembre 2014

# www.gecif.net
#######################################################################################################

100.times do
liste =[]
# Crée une liste à 5 nombres avec tous les nombres différents :
while (liste.size!=5) do
liste =[]
a1=(rand*255).round
b1=(rand*253).round
c1=(rand*253).round+2
b2=b1+2
c2=c1-2

liste << a1
liste << b1
liste << c1
liste << b2
liste << c2

liste=liste.uniq
end

# Envoie les 6 nombres séparés par un point-virgule sur la sortie standard en les écrivant dans une base particulière :

print a1,";",a1.to_s(16).upcase,";",b1,";",b2.to_s(16).upcase,";",c1,";",c2.to_s(16).upcase,"\n"

end

Exemple de fichier texte généré sur la sortie standard :

234;EA;22;18;195;C1
90;5A;95;61;170;A8
60;3C;234;EC;105;67
243;F3;222;E0;55;35
160;A0;7;9;207;CD
139;8B;55;39;204;CA
45;2D;247;F9;54;34
229;E5;183;B9;88;56

Voici la requête de transformation Dreamweaver qui convertit le fichier texte brut en fichier de QCM.

Expression régulière de recherche :

([0-9,A-F]{1,3});([0-9,A-F]{1,3});([0-9,A-F]{1,3});([0-9,A-F]{1,3});([0-9,A-F]{1,3});([0-9,A-F]{1,3})

Motif de remplacement :

quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] $1<sub>(10)</sub></br>");
rep("[x] $2<sub>(16)</sub></br>");
rep("[ ] $3<sub>(10)</sub></br>");
rep("[ ] $4<sub>(16)</sub></br>");
rep("[ ] $5<sub>(10)</sub></br>");
rep("[ ] $6<sub>(16)</sub></br>");

Et voici un exemple de questions obtenues par ce générateur :

quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] 186<sub>(10)</sub></br>");
rep("[x] BA<sub>(16)</sub></br>");
rep("[ ] 116<sub>(10)</sub></br>");
rep("[ ] 76<sub>(16)</sub></br>");
rep("[ ] 9<sub>(10)</sub></br>");
rep("[ ] 7<sub>(16)</sub></br>");
quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] 71<sub>(10)</sub></br>");
rep("[x] 47<sub>(16)</sub></br>");
rep("[ ] 250<sub>(10)</sub></br>");
rep("[ ] FC<sub>(16)</sub></br>");
rep("[ ] 118<sub>(10)</sub></br>");
rep("[ ] 74<sub>(16)</sub></br>");
quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] 177<sub>(10)</sub></br>");
rep("[x] B1<sub>(16)</sub></br>");
rep("[ ] 77<sub>(10)</sub></br>");
rep("[ ] 4F<sub>(16)</sub></br>");
rep("[ ] 41<sub>(10)</sub></br>");
rep("[ ] 27<sub>(16)</sub></br>");
quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] 240<sub>(10)</sub></br>");
rep("[x] F0<sub>(16)</sub></br>");
rep("[ ] 71<sub>(10)</sub></br>");
rep("[ ] 49<sub>(16)</sub></br>");
rep("[ ] 216<sub>(10)</sub></br>");
rep("[ ] D6<sub>(16)</sub></br>");

Voici une autre variante de ce générateur qui propose dans les réponses 8 nombres dont 4 en décimal et 4 en binaire naturel :

#######################################################################################################
# Ce programme permet de générer des questions de type "Cochez les 2 seuls nombres qui sont égaux"
# Réalisé par Jean-Christophe MICHEL le 19 décembre 2014

# www.gecif.net
#######################################################################################################

100.times do
liste =[]
# Crée une liste à 7 nombres avec tous les nombres différents :
while (liste.size!=7) do
liste =[]
a1=(rand*255).round
b1=(rand*247).round
c1=(rand*247).round+8
d1=(rand*247).round+8
i=(rand*8).round
b2=b1+i
i=(rand*8).round
c2=c1-2
i=(rand*8).round
d2=d1-2

liste << a1
liste << b1
liste << c1
liste << d1
liste << b2
liste << c2
liste << d2

liste=liste.uniq
end

# Envoie les 8 nombres séparés par un point-virgule sur la sortie standard en les écrivant dans une base particulière :

print a1,";",a1.to_s(2),";",b1,";",b2.to_s(2),";",c1,";",c2.to_s(2),";",d1,";",d2.to_s(2),"\n"

end

Exemple de fichier texte généré sur la sortie standard :

69;1000101;180;10111010;23;10101;78;1001100
178;10110010;113;1110111;145;10001111;131;10000001
83;1010011;103;1101110;211;11010001;254;11111100
211;11010011;73;1001110;152;10010110;129;1111111
139;10001011;119;1111010;179;10110001;89;1010111
23;10111;213;11010110;91;1011001;159;10011101
246;11110110;231;11101100;4;10;157;10011011

Voici la requête de transformation Dreamweaver qui convertit le fichier texte brut en fichier de QCM.

Expression régulière de recherche :

([0-9]*);([0-9]*);([0-9]*);([0-9]*);([0-9]*);([0-9]*);([0-9]*);([0-9]*)

Motif de remplacement :

quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] $1<sub>(10)</sub></br>");
rep("[x] $2<sub>(2)</sub></br>");
rep("[ ] $3<sub>(10)</sub></br>");
rep("[ ] $4<sub>(2)</sub></br>");
rep("[ ] $5<sub>(10)</sub></br>");
rep("[ ] $6<sub>(2)</sub></br>");
rep("[ ] $7<sub>(10)</sub></br>");
rep("[ ] $8<sub>(2)</sub></br>");

Et voici un exemple de questions obtenues par ce générateur :

quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] 69<sub>(10)</sub></br>");
rep("[x] 1000101<sub>(2)</sub></br>");
rep("[ ] 180<sub>(10)</sub></br>");
rep("[ ] 10111010<sub>(2)</sub></br>");
rep("[ ] 23<sub>(10)</sub></br>");
rep("[ ] 10101<sub>(2)</sub></br>");
rep("[ ] 78<sub>(10)</sub></br>");
rep("[ ] 1001100<sub>(2)</sub></br>");
quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] 178<sub>(10)</sub></br>");
rep("[x] 10110010<sub>(2)</sub></br>");
rep("[ ] 113<sub>(10)</sub></br>");
rep("[ ] 1110111<sub>(2)</sub></br>");
rep("[ ] 145<sub>(10)</sub></br>");
rep("[ ] 10001111<sub>(2)</sub></br>");
rep("[ ] 131<sub>(10)</sub></br>");
rep("[ ] 10000001<sub>(2)</sub></br>");
quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] 83<sub>(10)</sub></br>");
rep("[x] 1010011<sub>(2)</sub></br>");
rep("[ ] 103<sub>(10)</sub></br>");
rep("[ ] 1101110<sub>(2)</sub></br>");
rep("[ ] 211<sub>(10)</sub></br>");
rep("[ ] 11010001<sub>(2)</sub></br>");
rep("[ ] 254<sub>(10)</sub></br>");
rep("[ ] 11111100<sub>(2)</sub></br>");
quest("Cochez les deux seuls nombres qui sont égaux :||Parmi tous ces nombres seulement 2 sont égaux : cochez-les//a");
rep("[x] 211<sub>(10)</sub></br>");
rep("[x] 11010011<sub>(2)</sub></br>");
rep("[ ] 73<sub>(10)</sub></br>");
rep("[ ] 1001110<sub>(2)</sub></br>");
rep("[ ] 152<sub>(10)</sub></br>");
rep("[ ] 10010110<sub>(2)</sub></br>");
rep("[ ] 129<sub>(10)</sub></br>");
rep("[ ] 1111111<sub>(2)</sub></br>");

Remarque : dans certains cas le générateur pouvait envoyer sur sa sortie standard des nombres négatifs pour c2 ou d2. La ligne correspondante ne sera alors pas convertie par la requête de transformation. Pour détecter et supprimer ces lignes erronnées dans le fichier du QCM il suffit de recherher ;- . Si on demande 100 questions et que la requête de transformation n'en a trouvé que 99 cela signifie qu'il y a 1 ligne erronnée à supprimer. Pour éviter ces erreurs les lignes c1=(rand*255).round et d1=(rand*255).round ont été remplacées par c1=(rand*247).round+8 et d1=(rand*247).round+8.

 

Retour en haut de la page

Création d'un générateur de questions du type "Cochez tous les nombres impairs"

Le programme suivant génère des questions du type "Cochez tous nombres impairs" en proposant 8 nombres hexadécimaux dans les réponses :

#######################################################################################################
# Ce programme permet de générer des questions de type "Cochez tous les nombres impairs"
# Réalisé par Jean-Christophe MICHEL le 20 décembre 2014

# www.gecif.net
#######################################################################################################

100.times do
liste =[]
# Crée une liste à 8 nombres avec tous les nombres différents :
while (liste.size!=8) do
liste =[]
8.times do
liste << (rand*4095).round
end
liste=liste.uniq
end

# Envoie la question et les 8 réponses sur la sortie standard :
puts 'quest("Cochez tous les nombres impairs ://a");'
aucun=1
for n in 0..7 do
if liste[n].odd? then
print 'rep("[x] ',liste[n].to_s(16).upcase,'<sub>(16)</sub></br>");',"\n"
aucun=0
else
print 'rep("[ ] ',liste[n].to_s(16).upcase,'<sub>(16)</sub></br>");',"\n"
end
end

if aucun==1 then
print 'rep("[x] Aucun : tous ces nombres sont pairs</br>");',"\n"
else
print 'rep("[ ] Aucun : tous ces nombres sont pairs</br>");',"\n"
end

end

Exemples de questions obtenues par ce générateur :

quest("Cochez tous les nombres impairs ://a");
rep("[x] 4B7<sub>(16)</sub></br>");
rep("[ ] DF2<sub>(16)</sub></br>");
rep("[ ] 8BC<sub>(16)</sub></br>");
rep("[x] 60F<sub>(16)</sub></br>");
rep("[ ] ECC<sub>(16)</sub></br>");
rep("[x] A43<sub>(16)</sub></br>");
rep("[x] FA7<sub>(16)</sub></br>");
rep("[x] 8A3<sub>(16)</sub></br>");
rep("[ ] Aucun : tous ces nombres sont pairs</br>");
quest("Cochez tous les nombres impairs ://a");
rep("[x] EB5<sub>(16)</sub></br>");
rep("[x] 44B<sub>(16)</sub></br>");
rep("[x] FE9<sub>(16)</sub></br>");
rep("[x] D21<sub>(16)</sub></br>");
rep("[ ] 620<sub>(16)</sub></br>");
rep("[ ] CBE<sub>(16)</sub></br>");
rep("[x] DD1<sub>(16)</sub></br>");
rep("[x] 4AF<sub>(16)</sub></br>");
rep("[ ] Aucun : tous ces nombres sont pairs</br>");
quest("Cochez tous les nombres impairs ://a");
rep("[x] A85<sub>(16)</sub></br>");
rep("[x] 3BB<sub>(16)</sub></br>");
rep("[ ] 598<sub>(16)</sub></br>");
rep("[ ] F5A<sub>(16)</sub></br>");
rep("[x] 731<sub>(16)</sub></br>");
rep("[ ] 628<sub>(16)</sub></br>");
rep("[x] D9B<sub>(16)</sub></br>");
rep("[x] 4B7<sub>(16)</sub></br>");
rep("[ ] Aucun : tous ces nombres sont pairs</br>");

Et voici une autre variante qui génère des questions du type "Cochez tous nombres pairs" en proposant cette fois 4 nombres hexadécimaux et 4 nombres binaires dans les réponses :

#######################################################################################################
# Ce programme permet de générer des questions de type "Cochez tous les nombres pairs"
# Réalisé par Jean-Christophe MICHEL le 20 décembre 2014

# www.gecif.net
#######################################################################################################

100.times do
liste =[]
# Crée une liste à 8 nombres avec tous les nombres différents :
while (liste.size!=8) do
liste =[]
8.times do
liste << (rand*4095).round
end
liste=liste.uniq
end

# Envoie la question et les 8 réponses sur la sortie standard :
puts 'quest("Cochez tous les nombres pairs ://a");'
aucun=1
for n in 0..3 do
if liste[n].odd? then
print 'rep("[ ] ',liste[n].to_s(16).upcase,'<sub>(16)</sub></br>");',"\n"
else
print 'rep("[x] ',liste[n].to_s(16).upcase,'<sub>(16)</sub></br>");',"\n"
aucun=0
end
end
for n in 4..7 do
if liste[n].odd? then
print 'rep("[ ] ',liste[n].to_s(2).upcase,'<sub>(2)</sub></br>");',"\n"
else
print 'rep("[x] ',liste[n].to_s(2).upcase,'<sub>(2)</sub></br>");',"\n"
aucun=0
end
end

if aucun==1 then
print 'rep("[x] Aucun : tous ces nombres sont impairs</br>");',"\n"
else
print 'rep("[ ] Aucun : tous ces nombres sont impairs</br>");',"\n"
end

end

Exemples de questions obtenues par ce générateur :

quest("Cochez tous les nombres pairs ://a");
rep("[ ] 921<sub>(16)</sub></br>");
rep("[x] DBC<sub>(16)</sub></br>");
rep("[ ] E5B<sub>(16)</sub></br>");
rep("[ ] 1B1<sub>(16)</sub></br>");
rep("[ ] 100001101101<sub>(2)</sub></br>");
rep("[x] 1101001110<sub>(2)</sub></br>");
rep("[x] 111110010000<sub>(2)</sub></br>");
rep("[ ] 1101011<sub>(2)</sub></br>");
rep("[ ] Aucun : tous ces nombres sont impairs</br>");
quest("Cochez tous les nombres pairs ://a");
rep("[x] 122<sub>(16)</sub></br>");
rep("[ ] A93<sub>(16)</sub></br>");
rep("[x] C0<sub>(16)</sub></br>");
rep("[ ] A5F<sub>(16)</sub></br>");
rep("[x] 110011000010<sub>(2)</sub></br>");
rep("[ ] 11001010111<sub>(2)</sub></br>");
rep("[x] 11011100000<sub>(2)</sub></br>");
rep("[ ] 100100101101<sub>(2)</sub></br>");
rep("[ ] Aucun : tous ces nombres sont impairs</br>");
quest("Cochez tous les nombres pairs ://a");
rep("[x] BAC<sub>(16)</sub></br>");
rep("[ ] 597<sub>(16)</sub></br>");
rep("[x] CEA<sub>(16)</sub></br>");
rep("[ ] AF5<sub>(16)</sub></br>");
rep("[ ] 10000101001<sub>(2)</sub></br>");
rep("[ ] 110110111001<sub>(2)</sub></br>");
rep("[x] 10100100010<sub>(2)</sub></br>");
rep("[x] 11110110010<sub>(2)</sub></br>");
rep("[ ] Aucun : tous ces nombres sont impairs</br>");

 

Retour en haut de la page

Création d'un générateur de questions du type "Complétez l'addition binaire" avec mise en forme de l'addition

La série de générateur de questions présentés ici permettent d'obtenir des questions de type "Complétez l'addition binaire" ou "Complétez l'addition hexadécimale" en présentant l'adition verticalement à l'écran.

Dans tous les cas le processus de génération des questions se fera en deux temps :

Addition hexadécimale

Voici le programme en Ruby de génération de liste :

Ce programme génère une liste de 3 nombres hexadécimaux a b et c tels que c=a+b et vérifie que dans tous les cas les 3 nombres hexadécimaux sont tous sur 4 chiffres (pas de chiffres de poids fort nul, pas de propagation des retenues entraînant un 5ème chiffres dans le résultat).

######################################################################################################
# Crée une liste de 3 nombres hexadécimaux a;b;c tels que c=a+b et que a, b et c soient tous sur 4 chiffres
# Programme réalisé par Jean-Christophe MICHEL le 26 janvier 2015
# www.gecif.net
######################################################################################################
100.times do
a=0;
b=0;
c=0;
while (a.to_s(16).length!=4 || b.to_s(16).length!=4 || c.to_s(16).length!=4) do
a=(rand*65000).round
b=(rand*65000).round
c=a+b;
end

# Envoie les 3 nombres hexadécimaux séparés par un point-virgule sur la sortie standard :

print a.to_s(16).upcase,";",b.to_s(16).upcase,";",c.to_s(16).upcase,"\n"

end

Exemple de liste générée :

30C0;3D38;6DF8
6DD4;5046;BE1A
93EF;423E;D62D
B4B4;3794;EC48
4531;79B4;BEE5
41F6;6D37;AF2D
345A;19A4;4DFE
5D02;7C05;D907
3820;A46A;DC8A
84A8;5509;D9B1
5086;66DB;B761
3E9B;627A;A115
7CE0;6554;E234
4D8A;16E5;646F
96A7;2853;BEFA
A014;1ED6;BEEA
4C47;8000;CC47
4481;12A4;5725
920A;2602;B80C
79FF;7777;F176
5BE8;4EA7;AA8F
AD8A;2DA5;DB2F
842F;358D;B9BC
1201;C9E5;DBE6
48C6;A65F;EF25
1C62;B880;D4E2
742F;5C11;D040
5101;20CD;71CE
5D04;5ABA;B7BE

Expression régulière de recherche :

([0-9,A-F]{4});([0-9,A-F]{4});([0-9,A-F]{4})

Motif de remplacement avec mise en forme de l'addition :

quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;$1<sub>(16)</sub></br>&nbsp;+&nbsp;$2<sub>(16)</sub></br>__________");
rep("&nbsp;=&nbsp;[$3]<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");

Exemples de questions générées :

quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;318E<sub>(16)</sub></br>&nbsp;+&nbsp;51F8<sub>(16)</sub></br>__________");
rep("&nbsp;=&nbsp;[8386]<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;1417<sub>(16)</sub></br>&nbsp;+&nbsp;484D<sub>(16)</sub></br>__________");
rep("&nbsp;=&nbsp;[5C64]<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;2A66<sub>(16)</sub></br>&nbsp;+&nbsp;4005<sub>(16)</sub></br>__________");
rep("&nbsp;=&nbsp;[6A6B]<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;252B<sub>(16)</sub></br>&nbsp;+&nbsp;4F97<sub>(16)</sub></br>__________");
rep("&nbsp;=&nbsp;[74C2]<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");

Exemple de présentation dans le navigateur :

Autre motif de remplacement demandant de compléter le deuxième nombre dans l'addition et non le résultat :

quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;$1<sub>(16)</sub>");
rep("&nbsp;+&nbsp;[$2]<sub>(16)</sub></br>__________</br>&nbsp;=&nbsp;$3<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");

Exemples de questions générées :

quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;41AA<sub>(16)</sub>");
rep("&nbsp;+&nbsp;[35A0]<sub>(16)</sub></br>__________</br>&nbsp;=&nbsp;774A<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;1943<sub>(16)</sub>");
rep("&nbsp;+&nbsp;[BCB8]<sub>(16)</sub></br>__________</br>&nbsp;=&nbsp;D5FB<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;46CC<sub>(16)</sub>");
rep("&nbsp;+&nbsp;[1DB9]<sub>(16)</sub></br>__________</br>&nbsp;=&nbsp;6485<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;8F92<sub>(16)</sub>");
rep("&nbsp;+&nbsp;[49AB]<sub>(16)</sub></br>__________</br>&nbsp;=&nbsp;D93D<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");
quest("Complétez l'addition hexadécimale <font color=\"#FF0000\"><b>en entrant 4 chiffres</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;1765<sub>(16)</sub>");
rep("&nbsp;+&nbsp;[15E7]<sub>(16)</sub></br>__________</br>&nbsp;=&nbsp;2D4C<sub>(16)</sub></font></b>");
debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");

Exemple de présentation dans le navigateur :

Addition binaire de 2 nombres

Voici le programme en Ruby de génération de liste :

Ce programme génère une liste de 3 nombres binaires a b et c tels que c=a+b et vérifie que dans tous les cas les 3 nombres binaires auront le format suivant : a sur 6 bits, b et c sur 8 bits (pas de bits de poids fort nul, pas de propagation des retenues entraînant un 9ème bits dans le résultat).

######################################################################################################
# Crée une liste de 3 nombres binaires a;b;c tel que c=a+b et avec a sur 6 bits et b et c sur 8 bits
# Programme réalisé par Jean-Christophe MICHEL le 26 janvier 2015
# www.gecif.net
######################################################################################################
40.times do
a=0;
b=0;
c=0;
while (a.to_s(2).length!=6 || b.to_s(2).length!=8 || c.to_s(2).length!=8) do
a=(rand*255).round
b=(rand*255).round
c=a+b;
end

# Envoie les 3 nombres binaires séparés par un point-virgule sur la sortie standard :

print a.to_s(2),";",b.to_s(2),";",c.to_s(2),"\n"

end

Exemple de liste générée :

101101;11000110;11110011
100000;10000101;10100101
101010;10010011;10111101
110110;10010001;11000111
110010;10111100;11101110
100010;10000111;10101001
111110;10101010;11101000
110001;10101110;11011111
100010;10000100;10100110
101101;10001100;10111001
100101;10110011;11011000
111011;10100111;11100010
100010;11000000;11100010
111100;10101011;11100111
110010;10011000;11001010
110011;10111011;11101110
100010;10000101;10100111
110111;10010110;11001101

Expression régulière de recherche :

([0-1]{6});([0-1]{8});([0-1]{8})

Motif de remplacement avec mise en forme de l'addition :

quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$1<sub>(2)</sub></br>&nbsp;+&nbsp;$2<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[$3]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");

Exemples de questions générées :

quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;101000<sub>(2)</sub></br>&nbsp;+&nbsp;10010110<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[10111110]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;101111<sub>(2)</sub></br>&nbsp;+&nbsp;10001001<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[10111000]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;100010<sub>(2)</sub></br>&nbsp;+&nbsp;10010100<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[10110110]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;101000<sub>(2)</sub></br>&nbsp;+&nbsp;10111100<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[11100100]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;100000<sub>(2)</sub></br>&nbsp;+&nbsp;11000011<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[11100011]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");

Exemple de présentation dans le navigateur :

Autre motif de remplacement avec mise en forme de l'addition demandant de compléter le deuxième nombre dans l'addition et non le résultat :

quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$1<sub>(2)</sub>");
rep("&nbsp;+&nbsp;[$2]<sub>(2)</sub></br>______________</br>&nbsp;=&nbsp;$3<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");

Exemples de questions générées :

quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;100110<sub>(2)</sub>");
rep("&nbsp;+&nbsp;[10101100]<sub>(2)</sub></br>______________</br>&nbsp;=&nbsp;11010010<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;100000<sub>(2)</sub>");
rep("&nbsp;+&nbsp;[10000010]<sub>(2)</sub></br>______________</br>&nbsp;=&nbsp;10100010<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;101101<sub>(2)</sub>");
rep("&nbsp;+&nbsp;[10100000]<sub>(2)</sub></br>______________</br>&nbsp;=&nbsp;11001101<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;100001<sub>(2)</sub>");
rep("&nbsp;+&nbsp;[10000000]<sub>(2)</sub></br>______________</br>&nbsp;=&nbsp;10100001<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;110111<sub>(2)</sub>");
rep("&nbsp;+&nbsp;[10011101]<sub>(2)</sub></br>______________</br>&nbsp;=&nbsp;11010100<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");

Exemple de présentation dans le navigateur :

Addition binaire de 3 nombres

Voici le programme en Ruby de génération de liste :

Ce programme génère une liste de 4 nombres binaires a b c et d tels que d=a+b+c et vérifie que dans tous les cas les 4 nombres binaires auront le format suivant : a sur 5 bits, b sur 6 bits, c sur 7 bits, et d sur 8 bits (pas de bits de poids fort nul, pas de propagation des retenues entraînant un 9ème bits dans le résultat).

######################################################################################################
# Crée une liste de 3 nombres binaires a;b;c;d tel que d=a+b+c avec :
# a sur 5 bits b sur 6 bits c sur 7 bits et d sur 8 bits
# Programme réalisé par Jean-Christophe MICHEL le 26 janvier 2015
# www.gecif.net
######################################################################################################
40.times do
a=0;
b=0;
c=0;
d=0;
while (a.to_s(2).length!=5 || b.to_s(2).length!=6 || c.to_s(2).length!=7 || d.to_s(2).length!=8) do
a=(rand*255).round
b=(rand*255).round
c=(rand*255).round
d=a+b+c;
end

# Envoie les 3 nombres binaires séparés par un point-virgule sur la sortie standard :

print a.to_s(2),";",b.to_s(2),";",c.to_s(2),";",d.to_s(2),"\n"

end

Exemple de liste générée :

11110;100000;1111010;10111000
10101;100100;1110011;10101100
11110;111101;1001110;10101001
10111;111011;1001001;10011011
11111;110011;1101001;10111011
10011;101110;1100011;10100100
10100;101100;1011111;10011111
11011;100001;1010010;10001110
11111;100110;1010001;10010110
10010;110110;1110001;10111001
11111;110010;1101001;10111010
11011;100000;1110001;10101100
11001;110100;1000111;10010100
11001;101001;1010001;10010011
11000;110011;1101111;10111010
11001;111011;1001110;10100010
11001;111001;1100000;10110010
11100;101010;1110011;10111001
10111;111001;1111111;11001111

Expression régulière de recherche :

([0-1]{5});([0-1]{6});([0-1]{7});([0-1]{8})

Motif de remplacement avec mise en forme de l'addition :

quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;$1<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;&nbsp;$2<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;$3<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[$4]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");

Exemples de questions générées :

quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10111<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;&nbsp;101100<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;1111010<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[10111101]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10110<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;&nbsp;100110<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;1111011<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[10110111]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;11000<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;&nbsp;101100<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;1100101<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[10101001]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;10001<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;&nbsp;111000<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;1111000<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[11000001]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;11000<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;&nbsp;100100<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;1010000<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[10001100]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");
quest("Complétez l'addition binaire <font color=\"#FF0000\"><b>en entrant 8 bits</b></font> :</br></br><b><font face=\"Courier New, Courier, monospace\">&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;11001<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;&nbsp;101010<sub>(2)</sub></br>&nbsp;+&nbsp;&nbsp;1100000<sub>(2)</sub></br>______________");
rep("&nbsp;=&nbsp;[10100011]<sub>(2)</sub></font></b>");
debut("Pour entrer les chiffres saisissez-les au clavier ou utilisez les boutons ci-dessous :");
caracteres_speciaux("0","1");

Exemple de présentation dans le navigateur :

 

Retour en haut de la page

Quelques générateurs directs de questions pour QCM

Les programmes en Ruby suivants sont des générateurs directs de questions pour QCM. Ils génèrent directement une série de questions (sans passer par un fichier de données et sans utiliser une requête de transformation Dreamweaver à base d'expression régulière) et sont facilement adaptables à un grand nombre de besoins futurs.

Saisie d'un caractère dont le code ASCII est donné dans une des 3 bases (décimal, binaire ou hexadécimal) :

# encoding: ISO-8859-1
# Ce programme génère des questions demandant de saisir un caractère dont le code ASCII est donné dans une des 3 bases :
# Réalisé le 2 octobre 2015
for n in 33..125 do
puts 'quest("Saisissez au clavier le caractère qui a pour code ASCII #'+n.to_s+' :");'
puts 'rep("['+n.chr+']");'
puts 'sch("images/ascii.gif","583","213");'
puts 'quest("Saisissez au clavier le caractère qui a pour code ASCII $'+n.to_s(16).upcase+' :");'
puts 'rep("['+n.chr+']");'
puts 'sch("images/ascii.gif","583","213");'
puts 'quest("Saisissez au clavier le caractère qui a pour code ASCII %'+n.to_s(2)+' :");'
puts 'rep("['+n.chr+']");'
puts 'sch("images/ascii.gif","583","213");'
end

Saisie du code ASCII dans une des 3 bases (décimal, binaire ou hexadécimal) d'un caractère donné :

# encoding: ISO-8859-1
# Ce programme génère des questions demandant de saisir le code ASCII d'un caractère donné dans les 3 bases :
# Réalisé le 2 octobre 2015
for n in 33..125 do
puts 'quest("Entrez le code ASCII en décimal du caractère <b><font color=\"#FF0000\">&#'+n.to_s+';</font></b> :");'
puts 'rep("['+n.to_s+']");'
puts 'debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");'
puts 'caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");'
puts 'sch("images/ascii.gif","583","213");'
puts 'quest("Entrez le code ASCII en hexadécimal du caractère <b><font color=\"#FF0000\">&#'+n.to_s+';</font></b> :");'
puts 'rep("['+n.to_s(16).upcase+']");'
puts 'debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");'
puts 'caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");'
puts 'sch("images/ascii.gif","583","213");'
puts 'quest("Entrez le code ASCII en binaire du caractère <b><font color=\"#FF0000\">&#'+n.to_s+';</font></b> :");'
puts 'rep("['+n.to_s(2)+']");'
puts 'debut("Pour entrer des lettres saisissez-les en MAJUSCULES ou utilisez les boutons ci-dessous :");'
puts 'caracteres_speciaux("0","1","2","3","4","5","6","7","8","9","A","B","C","D","E","F");'
puts 'sch("images/ascii.gif","583","213");'
end

Sélection de la seule association caractère / code ASCII qui est correcte :

# encoding: ISO-8859-1
# Réalisé le 29 septembre 2015
100.times do
liste =[]
# Crée une liste à 7 nombres avec tous les nombres différents :
while (liste.size!=7) do
liste =[]
a1=(rand*90).round+35
b1=(rand*90).round+35
c1=(rand*90).round+35
b2=(rand*90).round+35
c2=(rand*90).round+35
b3=(rand*90).round+35
c3=(rand*90).round+35

liste << a1
liste << b1
liste << c1
liste << b2
liste << c2
liste << b3
liste << c3

liste=liste.uniq
end

puts 'quest("En cochant 2 cases sélectionnez la seule association <b>caractère / code ASCII</b> qui est correcte ://a");'
puts 'rep("[x] $'+a1.to_s(16).upcase+'");'
puts 'rep("[x] &#'+a1.to_s+';");'
puts 'rep("[ ] $'+b1.to_s(16).upcase+'");'
puts 'rep("[ ] &#'+c1.to_s+';");'
puts 'rep("[ ] #'+b2.to_s+'");'
puts 'rep("[ ] #'+c2.to_s+'");'
puts 'rep("[ ] &#'+b3.to_s+';");'
puts 'rep("[ ] &#'+c3.to_s+';");'
puts 'sch("images/ascii.gif","583","213");'

end

100.times do
liste =[]
# Crée une liste à 7 nombres avec tous les nombres différents :
while (liste.size!=7) do
liste =[]
a1=(rand*90).round+35
b1=(rand*90).round+35
c1=(rand*90).round+35
b2=(rand*90).round+35
c2=(rand*90).round+35
b3=(rand*90).round+35
c3=(rand*90).round+35

liste << a1
liste << b1
liste << c1
liste << b2
liste << c2
liste << b3
liste << c3

liste=liste.uniq
end

puts 'quest("En cochant 2 cases sélectionnez la seule association <b>caractère / code ASCII</b> qui est correcte ://a");'
puts 'rep("[x] #'+a1.to_s+'");'
puts 'rep("[x] &#'+a1.to_s+';");'
puts 'rep("[ ] $'+b1.to_s(16).upcase+'");'
puts 'rep("[ ] &#'+c1.to_s+';");'
puts 'rep("[ ] $'+b2.to_s(16).upcase+'");'
puts 'rep("[ ] #'+c2.to_s+'");'
puts 'rep("[ ] &#'+b3.to_s+';");'
puts 'rep("[ ] &#'+c3.to_s+';");'
puts 'sch("images/ascii.gif","583","213");'

end

100.times do
liste =[]
# Crée une liste à 7 nombres avec tous les nombres différents :
while (liste.size!=7) do
liste =[]
a1=(rand*90).round+35
b1=(rand*90).round+35
c1=(rand*90).round+35
b2=(rand*90).round+35
c2=(rand*90).round+35
b3=(rand*90).round+35
c3=(rand*90).round+35

liste << a1
liste << b1
liste << c1
liste << b2
liste << c2
liste << b3
liste << c3

liste=liste.uniq
end

puts 'quest("En cochant 2 cases sélectionnez la seule association <b>caractère / code ASCII</b> qui est correcte ://a");'
puts 'rep("[x] %'+a1.to_s(2)+'");'
puts 'rep("[x] &#'+a1.to_s+';");'
puts 'rep("[ ] %'+b1.to_s(2)+'");'
puts 'rep("[ ] &#'+c1.to_s+';");'
puts 'rep("[ ] %'+b2.to_s(2)+'");'
puts 'rep("[ ] %'+c2.to_s(2)+'");'
puts 'rep("[ ] &#'+b3.to_s+';");'
puts 'rep("[ ] &#'+c3.to_s+';");'
puts 'sch("images/ascii.gif","583","213");'

end

100.times do
liste =[]
# Crée une liste à 9 nombres avec tous les nombres différents :
while (liste.size!=9) do
liste =[]
a=(rand*90).round+35
b=(rand*90).round+35
c=(rand*90).round+35
d=(rand*90).round+35
e=(rand*90).round+35
f=(rand*90).round+35
g=(rand*90).round+35
h=(rand*90).round+35
i=(rand*90).round+35

liste << a
liste << b
liste << c
liste << d
liste << e
liste << f
liste << g
liste << h
liste << i

liste=liste.uniq
end

puts 'quest("Quel est le code ASCII du caractère <b><font color=\"#FF0000\">&#'+a.to_s+';</font></b> ?//a");'
puts 'rep("(o) #'+a.to_s+'");'
puts 'rep("( ) $'+b.to_s(16).upcase+'");'
puts 'rep("( ) %'+c.to_s(2)+'");'
puts 'rep("( ) #'+d.to_s+'");'
puts 'rep("( ) $'+e.to_s(16).upcase+'");'
puts 'rep("( ) %'+f.to_s(2)+'");'
puts 'rep("( ) #'+g.to_s+'");'
puts 'rep("( ) $'+h.to_s(16).upcase+'");'
puts 'rep("( ) %'+i.to_s(2)+'");'
puts 'sch("images/ascii.gif","583","213");'

end

100.times do
liste =[]
# Crée une liste à 9 nombres avec tous les nombres différents :
while (liste.size!=9) do
liste =[]
a=(rand*90).round+35
b=(rand*90).round+35
c=(rand*90).round+35
d=(rand*90).round+35
e=(rand*90).round+35
f=(rand*90).round+35
g=(rand*90).round+35
h=(rand*90).round+35
i=(rand*90).round+35

liste << a
liste << b
liste << c
liste << d
liste << e
liste << f
liste << g
liste << h
liste << i

liste=liste.uniq
end

puts 'quest("Quel est le code ASCII du caractère <b><font color=\"#FF0000\">&#'+b.to_s+';</font></b> ?//a");'
puts 'rep("( ) #'+a.to_s+'");'
puts 'rep("(o) $'+b.to_s(16).upcase+'");'
puts 'rep("( ) %'+c.to_s(2)+'");'
puts 'rep("( ) #'+d.to_s+'");'
puts 'rep("( ) $'+e.to_s(16).upcase+'");'
puts 'rep("( ) %'+f.to_s(2)+'");'
puts 'rep("( ) #'+g.to_s+'");'
puts 'rep("( ) $'+h.to_s(16).upcase+'");'
puts 'rep("( ) %'+i.to_s(2)+'");'
puts 'sch("images/ascii.gif","583","213");'

end

100.times do
liste =[]
# Crée une liste à 9 nombres avec tous les nombres différents :
while (liste.size!=9) do
liste =[]
a=(rand*90).round+35
b=(rand*90).round+35
c=(rand*90).round+35
d=(rand*90).round+35
e=(rand*90).round+35
f=(rand*90).round+35
g=(rand*90).round+35
h=(rand*90).round+35
i=(rand*90).round+35

liste << a
liste << b
liste << c
liste << d
liste << e
liste << f
liste << g
liste << h
liste << i

liste=liste.uniq
end

puts 'quest("Quel est le code ASCII du caractère <b><font color=\"#FF0000\">&#'+c.to_s+';</font></b> ?//a");'
puts 'rep("( ) #'+a.to_s+'");'
puts 'rep("( ) $'+b.to_s(16).upcase+'");'
puts 'rep("(o) %'+c.to_s(2)+'");'
puts 'rep("( ) #'+d.to_s+'");'
puts 'rep("( ) $'+e.to_s(16).upcase+'");'
puts 'rep("( ) %'+f.to_s(2)+'");'
puts 'rep("( ) #'+g.to_s+'");'
puts 'rep("( ) $'+h.to_s(16).upcase+'");'
puts 'rep("( ) %'+i.to_s(2)+'");'
puts 'sch("images/ascii.gif","583","213");'

end

Bit de parité dans une liaison série RS232 :

# encoding: ISO-8859-1
# Réalisé le 29 septembre 2015
for n in 35..125 do
if n.to_s(2).gsub("0","").size.odd? then
puts 'quest("Le code ASCII du caractère <b><font color=\"#FF0000\">&#'+n.to_s+';</font></b> est transmis par une liaison série RS232 avec un contrôle de parité paire. Quel doit être l\'état du bit de parité ?");'
puts 'rep("( ) 0");'
puts 'rep("(o) 1");'
puts 'rep("( ) Impossible à déterminer");'
puts 'sch("images/ascii.gif","583","213");'
puts 'quest("Le code ASCII du caractère <b><font color=\"#FF0000\">&#'+n.to_s+';</font></b> est transmis par une liaison série RS232 avec un contrôle de parité impaire. Quel doit être l\'état du bit de parité ?");'
puts 'rep("(o) 0");'
puts 'rep("( ) 1");'
puts 'rep("( ) Impossible à déterminer");'
puts 'sch("images/ascii.gif","583","213");'
puts 'quest("Le code ASCII du caractère <b><font color=\"#FF0000\">&#'+n.to_s+';</font></b> est transmis par une liaison série RS232 avec un contrôle de parité. Quel doit être l\'état du bit de parité ?");'
puts 'rep("( ) 0");'
puts 'rep("( ) 1");'
puts 'rep("(o) Impossible à déterminer");'
puts 'sch("images/ascii.gif","583","213");'
else
puts 'quest("Le code ASCII du caractère <b><font color=\"#FF0000\">&#'+n.to_s+';</font></b> est transmis par une liaison série RS232 avec un contrôle de parité paire. Quel doit être l\'état du bit de parité ?");'
puts 'rep("(o) 0");'
puts 'rep("( ) 1");'
puts 'rep("( ) Impossible à déterminer");'
puts 'sch("images/ascii.gif","583","213");'
puts 'quest("Le code ASCII du caractère <b><font color=\"#FF0000\">&#'+n.to_s+';</font></b> est transmis par une liaison série RS232 avec un contrôle de parité impaire. Quel doit être l\'état du bit de parité ?");'
puts 'rep("( ) 0");'
puts 'rep("(o) 1");'
puts 'rep("( ) Impossible à déterminer");'
puts 'sch("images/ascii.gif","583","213");'
end
end

Conversion de vitesses angulaires :

# encoding: ISO-8859-1

# Ce programme permet de générer des questions sur la conversion de vitesse angulaire (rd/s rd/mn tr/s et tr/mn)
# Réalisé par Jean-Christophe MICHEL le 16 octobre 2015

40.times do
liste =[]
# Crée une liste à 7 nombres avec tous les nombres différents :
while (liste.size!=7) do
liste =[]
a=(rand*100).round+2
b=(rand*100).round+2
c=(rand*100).round+10
d=(rand*100).round+2
e=(rand*100).round+2
f=(rand*100).round+2
g=(rand*100).round+20

liste << a
liste << b
liste << c
liste << d
liste << e
liste << f
liste << g

liste=liste.uniq
end

puts 'quest("Cochez les deux seules valeurs de vitesse angulaire qui sont équivalentes :||Parmi toutes ces vitesses de rotation seulement 2 sont égales : cochez-les//a");'
puts 'rep("[x] '+a.to_s+' rd/s");'
puts 'rep("[x] '+(a*30.0/3.1415926535).round(3).to_s+' tr/mn");'
puts 'rep("[ ] '+b.to_s+' rd/s");'
puts 'rep("[ ] '+(b*3.1415926535/30.0).round(3).to_s+' tr/mn");'
puts 'rep("[ ] '+c.to_s+' rd/mn");'
puts 'rep("[ ] '+(c*30.0/3.1415926535).round(3).to_s+' tr/s");'
puts 'rep("[ ] '+d.to_s+' tr/mn");'
puts 'rep("[ ] '+(d*30.0/3.1415926535).round(3).to_s+' rd/s");'

puts 'quest("Cochez les deux seules valeurs de vitesse angulaire qui sont équivalentes :||Parmi toutes ces vitesses de rotation seulement 2 sont égales : cochez-les//a");'
puts 'rep("[x] '+g.to_s+' tr/mn");'
puts 'rep("[x] '+(g*2*3.1415926535).round(3).to_s+' rd/mn");'
puts 'rep("[ ] '+f.to_s+' rd/s");'
puts 'rep("[ ] '+(f*3.1415926535/30.0).round(3).to_s+' tr/mn");'
puts 'rep("[ ] '+e.to_s+' rd/s");'
puts 'rep("[ ] '+(e*60.0/3.1415926535).round(3).to_s+' tr/mn");'
puts 'rep("[ ] '+d.to_s+' tr/mn");'
puts 'rep("[ ] '+(d/(2*3.1415926535)).round(3).to_s+' rd/mn");'

puts 'quest("Quelle est la seule équivalence de vitesse angulaire qui est correcte ?//a");'
puts 'rep("(o) '+e.to_s+' tr/mn = '+(e*2*3.1415926535).round(3).to_s+' rd/mn");'
puts 'rep("( ) '+f.to_s+' rd/mn = '+(f/(2*3.1415926535)).round(3).to_s+' tr/s");'
puts 'rep("( ) '+d.to_s+' rd/s = '+(d*2*3.1415926535).round(3).to_s+' tr/s");'
puts 'rep("( ) '+(g*60.0).round(3).to_s+' rd/s = '+g.to_s+' rd/mn");'
puts 'rep("( ) '+(c/40.0).round(3).to_s+' tr/s = '+c.to_s+' tr/mn");'
puts 'rep("( ) '+(b*(3.1415926535/30.0)).round(3).to_s+' tr/mn = '+b.to_s+' rd/s");'
puts 'quest("Quelle est la seule équivalence de vitesse angulaire qui est correcte ?//a");'
puts 'rep("( ) '+e.to_s+' tr/mn = '+(e*3*3.1415926535).round(3).to_s+' rd/mn");'
puts 'rep("(o) '+f.to_s+' rd/mn = '+(f/(60.0*2*3.1415926535)).round(3).to_s+' tr/s");'
puts 'rep("( ) '+d.to_s+' rd/s = '+(d*2*3.1415926535).round(3).to_s+' tr/s");'
puts 'rep("( ) '+(g*60.0).round(3).to_s+' rd/s = '+g.to_s+' rd/mn");'
puts 'rep("( ) '+(c/40.0).round(3).to_s+' tr/s = '+c.to_s+' tr/mn");'
puts 'rep("( ) '+(b*(3.1415926535/30.0)).round(3).to_s+' tr/mn = '+b.to_s+' rd/s");'
puts 'quest("Quelle est la seule équivalence de vitesse angulaire qui est correcte ?//a");'
puts 'rep("( ) '+e.to_s+' tr/mn = '+(e*3.1415926535).round(3).to_s+' rd/mn");'
puts 'rep("( ) '+f.to_s+' rd/mn = '+(f/(2*3.1415926535)).round(3).to_s+' tr/s");'
puts 'rep("(o) '+d.to_s+' rd/s = '+(d/(2*3.1415926535)).round(3).to_s+' tr/s");'
puts 'rep("( ) '+(g*60.0).round(3).to_s+' rd/s = '+g.to_s+' rd/mn");'
puts 'rep("( ) '+(c/40.0).round(3).to_s+' tr/s = '+c.to_s+' tr/mn");'
puts 'rep("( ) '+(b*(3.1415926535/30.0)).round(3).to_s+' tr/mn = '+b.to_s+' rd/s");'
puts 'quest("Quelle est la seule équivalence de vitesse angulaire qui est correcte ?//a");'
puts 'rep("( ) '+e.to_s+' tr/mn = '+(e/(2*3.1415926535)).round(3).to_s+' rd/mn");'
puts 'rep("( ) '+f.to_s+' rd/mn = '+(f/(2*3.1415926535)).round(3).to_s+' tr/s");'
puts 'rep("( ) '+d.to_s+' rd/s = '+(d*2*3.1415926535).round(3).to_s+' tr/s");'
puts 'rep("(o) '+(g/60.0).round(3).to_s+' rd/s = '+g.to_s+' rd/mn");'
puts 'rep("( ) '+(c/40.0).round(3).to_s+' tr/s = '+c.to_s+' tr/mn");'
puts 'rep("( ) '+(b*(3.1415926535/30.0)).round(3).to_s+' tr/mn = '+b.to_s+' rd/s");'
puts 'quest("Quelle est la seule équivalence de vitesse angulaire qui est correcte ?//a");'
puts 'rep("( ) '+e.to_s+' tr/mn = '+(e/3.1415926535).round(3).to_s+' rd/mn");'
puts 'rep("( ) '+f.to_s+' rd/mn = '+(f/(2*3.1415926535)).round(3).to_s+' tr/s");'
puts 'rep("( ) '+d.to_s+' rd/s = '+(d*2*3.1415926535).round(3).to_s+' tr/s");'
puts 'rep("( ) '+(g*60.0).round(3).to_s+' rd/s = '+g.to_s+' rd/mn");'
puts 'rep("(o) '+(c/60.0).round(3).to_s+' tr/s = '+c.to_s+' tr/mn");'
puts 'rep("( ) '+(b*(3.1415926535/30.0)).round(3).to_s+' tr/mn = '+b.to_s+' rd/s");'
puts 'quest("Quelle est la seule équivalence de vitesse angulaire qui est correcte ?//a");'
puts 'rep("( ) '+e.to_s+' tr/mn = '+(e*(3.1415926535/30.0)).round(3).to_s+' rd/mn");'
puts 'rep("( ) '+f.to_s+' rd/mn = '+(f/(2*3.1415926535)).round(3).to_s+' tr/s");'
puts 'rep("( ) '+d.to_s+' rd/s = '+(d*2*3.1415926535).round(3).to_s+' tr/s");'
puts 'rep("( ) '+(g*60.0).round(3).to_s+' rd/s = '+g.to_s+' rd/mn");'
puts 'rep("( ) '+(c/40.0).round(3).to_s+' tr/s = '+c.to_s+' tr/mn");'
puts 'rep("(o) '+(b*(30.0/3.1415926535)).round(3).to_s+' tr/mn = '+b.to_s+' rd/s");'

end

 

Conclusion

Cette page vous a donné tous les éléments de base pour commencer à programmer en Ruby, sous Linux comme sous Windows.