Je vais expliquer ici pas à pas le code pour résoudre notre problème, et vous allez vous apercevoir que tout le boulot fait à la partie précédente va porter ses fruits

. Pour ceux qui veulent le tester d'abord, le code entier est à la fin de cette partie.
On commence par quelques includes et déclarations de variables de classes :
Code : C++ | #include <gecode/driver.hh>
#include <gecode/int.hh>
#include <gecode/minimodel.hh>
using namespace Gecode;
class CarreMagique : public Script {
private:
const int n;
IntVarArray carreMag;
|
On peut remarquer la syntaxe de Gecode pour un tableau de variables entières, IntVarArray.
Puis viens ensuite le principal morceau, le constructeur dont voici le début :
Code : C++ | public:
CarreMagique(const SizeOptions& opt)
: n(opt.size()), carreMag(*this,n*n,1,n*n)
|
La seule chose intéressante ici est
carreMag(*this,n*n,1,n*n)
, qui dit, dans l'ordre, que carreMag est de taille n*n, chacune de ses variables ayant un domaine de 1 à n*n. Comme nous l'avions modélisé dans la précédente partie !
Ensuite on déclare la variable s et on utilise une fourberie de Gecode pour accéder au tableau comme on le ferais pour une matrice.
Code : C++ | // Somme de chaque ligne, colonne et grande diagonale
IntVar s(*this, 1, n*n*n);
// Pour acceder au tableau comme à une matrice
Matrix<IntVarArray> m(carreMag, n, n);
|
Et voici maintenant les contraintes :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 | // Les lignes et colonnes doit avoir comme somme s
for (int i = 0; i < n; i++) {
linear(*this, m.row(i), IRT_EQ, s);
linear(*this, m.col(i), IRT_EQ, s);
}
// Les deux grandes diagonales doivent avoir comme somme s
{
IntVarArgs d1(n);
IntVarArgs d2(n);
for (int i = 0; i < n; i++) {
d1[i] = m(i,i);
d2[i] = m(n-i-1,i);
}
linear(*this, d1, IRT_EQ, s);
linear(*this, d2, IRT_EQ, s);
}
// Toutes les cases doivent avoir une valeur différente
distinct(*this, carreMag);
|
On découvre ici la façon de déclarer des contraintes.
linear(*this, m.row(i), IRT_EQ, s);
indique que la somme des éléments de la ligne ("row" en anglais) i est égale à s. De même pour les colonnes et les diagonales. Enfin la dernière contrainte
distinct(*this, carreMag);
indique que toutes les variables du tableau carreMag doivent prendre des valeurs disctinctes les unes des autres. Plutot simple non

?
La ligne de code qui suit est importante :
Code : C++ | // On cherche les solutions sur le carré
branch(*this, carreMag, INT_VAR_SIZE_MIN, INT_VAL_SPLIT_MIN);
|
Elle indique sur quelles variables le programme va chercher des solutions. Dans notre cas on veut résoudre le carré magique et c'est donc sur le tableau que l'on va effectuer les "branchements". Les deux autres arguments indiquent la méthode de branchement, ne vous y attardez pas.
Suivent des méthodes requises par Gecode, plus la fonction d'affichage du carré magique :
Code : C++ 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20 | CarreMagique(bool share, CarreMagique& s) : Script(share,s), n(s.n) {
carreMag.update(*this, share, s.carreMag);
}
virtual Space* copy(bool share) {
return new CarreMagique(share,*this);
}
virtual void print(std::ostream& os) const {
// Pour acceder au tableau comme à une matrice
Matrix<IntVarArray> m(carreMag, n, n);
for (int i = 0; i < n; i++) {
os << "\t";
for (int j = 0; j < n; j++) {
os.width(2);
os << m(i,j) << " ";
}
os << std::endl;
}
}
|
Sachez simplement que votre programme a besoin de ces fonctions de mise à jour et de copie.
Finalement nous pouvons écrire le "main" :
Code : C++ | int main(int argc, char* argv[]) {
SizeOptions opt("CarreMagique");
opt.size(4);
Script::run<CarreMagique,DFS,SizeOptions>(opt);
return 0;
}
|
Rien de fou ici,
opt.size(4);
est la taille du carré magique à résoudre. Notre programme n'étant pas franchement optimisé, ne dépassez pas 6 sinon vous allez attendre longtemps avant la réponse ! Quant à la ligne
Script::run<CarreMagique,DFS,SizeOptions>(opt);
, c'est ici que tout commence ! Vous appelez ici le solveur, soit le programme qui essaye de satisfaire les contraintes et s'occupe de tout, en fait

.
Pour récapituler, voici le code en entier :
Secret (cliquez pour afficher)Code : C++ - carre-magique.cpp 1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74 | #include <gecode/driver.hh>
#include <gecode/int.hh>
#include <gecode/minimodel.hh>
using namespace Gecode;
class CarreMagique : public Script {
private:
const int n;
IntVarArray carreMag;
public:
CarreMagique(const SizeOptions& opt)
: n(opt.size()), carreMag(*this,n*n,1,n*n) {
// Somme de chaque ligne, colonne et grande diagonale
IntVar s(*this, 1, n*n*n);
// Pour acceder au tableau comme à une matrice
Matrix<IntVarArray> m(carreMag, n, n);
// Les lignes et colonnes doit avoir comme somme s
for (int i = 0; i < n; i++) {
linear(*this, m.row(i), IRT_EQ, s);
linear(*this, m.col(i), IRT_EQ, s);
}
// Les deux grandes diagonales doivent avoir comme somme s
{
IntVarArgs d1(n);
IntVarArgs d2(n);
for (int i = 0; i < n; i++) {
d1[i] = m(i,i);
d2[i] = m(n-i-1,i);
}
linear(*this, d1, IRT_EQ, s);
linear(*this, d2, IRT_EQ, s);
}
// Toutes les cases doivent avoir une valeur différente
distinct(*this, carreMag);
// On cherche les solutions sur le carré
branch(*this, carreMag, INT_VAR_SIZE_MIN, INT_VAL_SPLIT_MIN);
}
CarreMagique(bool share, CarreMagique& s) : Script(share,s), n(s.n) {
carreMag.update(*this, share, s.carreMag);
}
virtual Space* copy(bool share) {
return new CarreMagique(share,*this);
}
virtual void print(std::ostream& os) const {
// Pour acceder au tableau comme à une matrice
Matrix<IntVarArray> m(carreMag, n, n);
for (int i = 0; i < n; i++) {
os << "\t";
for (int j = 0; j < n; j++) {
os.width(2);
os << m(i,j) << " ";
}
os << std::endl;
}
}
};
int main(int argc, char* argv[]) {
SizeOptions opt("CarreMagique");
opt.size(4);
Script::run<CarreMagique,DFS,SizeOptions>(opt);
return 0;
}
|
Je vous invite à compiler tout ça et à l'exécuter ! Lorsque votre programme trouve une solution il l'affiche avec quelques statistiques en bonus. En voici un exemple pour un carré magique de taille 4 :
Code : Autre - sortie du programme1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| CarreMagique
1 12 13 8
2 14 7 11
15 3 10 6
16 5 4 9
Initial
propagators: 11
branchings: 1
Summary
runtime: 0.009 (9.105000 ms)
solutions: 1
propagations: 22929
nodes: 1688
failures: 838
peak depth: 24
peak memory: 35 KB |
Le "runtime" est le temps que votre programme à mis pour trouver cete solution. Quand aux autres chiffres, plus ils sont grands, plus le problème spécifié dans votre programme est complexe à résoudre (oui, ici je vous cache des choses).
Pour les aventuriers
Ce code peut être amélioré de différentes façons. Par exemple, en faisant un peu de maths, on peut connaître la valeur de la somme s uniquement à partir de n, la taille du problème (voir
wikipedia). Vous pouvez donc modifier le code donné pour changer le domaine de s, puis tester le changement en vous aidant des statistiques données par Gecode.
Et si vous vous embêtez encore, essayer de coder un solveur de sudokus. C'est un des problèmes les plus simples qu'on peut résoudre en programmation par contraintes, et vous savez maintenant tout ce qu'il faut pour le programmer !