Subversion Repositories wimsdev

Rev

Details | Last modification | View Log | RSS feed

Rev Author Line No. Line
2143 bpr 1
\documentclass[a4paper,oneside,11pt]{article}
2
 
3
\usepackage[latin1]{inputenc}
4
\usepackage[T1]{fontenc}
5
\usepackage[francais]{babel}
6
\usepackage{geometry}
7
\usepackage{verbatim}
8
\usepackage{listings}
9
\usepackage{color}
10
\usepackage{url}
11
\usepackage{xspace}
12
\urlstyle{sf}
13
\definecolor{green}{rgb}{0,0.8,0}
14
\lstloadlanguages{C}
15
\lstset{language=Java,extendedchars=true,stringspaces=false,tabsize=3,commentstyle=\color{blue},keywordstyle=\color{red},stringstyle=\color{green}} % Nice colors
16
\lstset{labelstyle=\tiny,labelstep=1,labelsep=5pt} % line numbers
17
 
18
\newcommand{\code}[1]{\texttt{#1{}}\xspace}
19
\newcommand{\filename}[1]{\textbf{#1{}}\xspace}
20
 
21
\geometry{reset}
22
 
23
\setlength{\parskip}{0.5em}
24
 
25
\title{Le Compte Est Bon}
26
\author{Lucas \scshape{Nussbaum}}
27
\date{}
28
 
29
\begin{document}
30
\maketitle
31
\tableofcontents
32
\section{Introduction}
33
Ce problème est inspiré d'un jeu télévisé du même nom, faisant partie de l'émission \textsl{Des chiffres et des lettres}, diffusée sur \textsl{France 2}.
34
 
35
\subsection{Règles}
36
Six nombres (appelés \textit{nombres de base} par la suite) sont tirés au sort parmi les nombres de 1 à 10, 25, 50, 75 et 100. Un septième nombre, appelé \textit{but} par la suite, est tiré au sort entre 100 et 999. Le joueur doit combiner les 6 nombres de base à l'aide des opérations mathématiques addition, soustraction, multiplication et division entière pour obtenir le but ou, à défaut, un nombre s'en approchant le plus possible.
37
 
38
\subsection{Exemples}
39
Voici quelques exemples afin d'y voir plus clair :
40
\begin{itemize}
41
\item Nombres de base : 4, 3, 5, 100, 1 et 6. But : 886\\
42
Il est possible d'atteindre le but à l'aide des opérations suivantes :
43
 
44
100 - 1 = 99\\
45
6 + 3 = 9\\
46
99 * 9 = 891\\
47
891 - 5 = 886
48
 
49
\item Nombres de base : 8, 2, 5, 6, 1, 3. But : 927\\
50
A l'aide des algorithmes exposés plus loin, nous pouvons déterminer qu'il n'est pas possible d'atteindre le but. Toutefois, il est possible d'atteindre 930, et c'est le nombre le plus proche du but qu'il soit possible d'atteindre. C'est donc la meilleure solution.
51
 
52
6 * 5 = 30\\
53
30 + 1 = 31\\
54
8 + 2 = 10\\
55
10 * 3 = 30\\
56
31 * 30 = 930
57
\end{itemize}
58
 
59
\subsection{Intérêt algorithmique}
60
Ce problème est très intéressant d'un point de vue algorithmique. Contrairement à ce qui est annoncé sur un site web, la seule solution possible n'est pas une recherche aléatoire. Plusieurs solutions permettent d'obtenir de manière performante des résultats, et sont suffisamment différentes pour qu'il soit intéressant de les comparer. Je vais étudier 2 solutions permettant d'obtenir de bons résultats dans la suite de ce texte.
61
 
62
\section{Solution récursive}
63
Cette solution est la plus facile à comprendre.
64
 
65
\subsection{Principe}
66
 
67
\begin{itemize}
68
\item On part d'un tableau de 6 éléments contenant les 6 nombres de base.
69
\item On combine deux de ces éléments afin d'en obtenir un nouveau.
70
\item On obtient donc un tableau de 5 éléments, le nouvel élément remplaçant les 2 éléments nécessaires pour le former.
71
\item On continue à créer de nouveaux éléments ainsi pour chaque tableau créé, pour chaque couple d'éléments possible, et pour chaque opérateur.
72
\item On s'arrête lorsque le nouvel élément généré est égal au but.
73
\item On garde en mémoire une trace de l'élément le plus proche du but, afin de pouvoir le retrouver si on n'arrive pas à atteindre le but.
74
\end{itemize}
75
 
76
\subsection{Exemple}
77
On part de la situation suivante : Nombres de base : 4, 3, 5, 100, 1 et 6. But : 886.
78
 
79
On a donc un tableau de 6 éléments contenant 4;3;5;100;1;6. Le premier couple qu'on peut créer à partir de deux éléments de ce tableau est le couple (4,3). On le combine à l'aide de l'addition, et on obtient 7.
80
 
81
On obtient donc le tableau de 5 éléments 7;5;100;1;6. On remplace le couple (7,5) par 12, obtenu avec l'addition.
82
 
83
On continue l'exploration descendante puis, si cette exploration ne permet pas d'atteindre le but, on essaie les autres opérateurs, et les autres couples.
84
 
85
\subsection{Avantages et inconvénients}
86
\subsubsection{Avantages}
87
Le principal avantage de cet algorithme est bien sûr sa facilité de compréhension.
88
 
89
De plus, il est rapide, et facile à implémenter.
90
 
91
\subsubsection{Inconvénients}
92
Le principal inconvénient de cet algorithme est qu'il ne permet pas de conserver en mémoire l'ensemble des solutions trouvées, ou des résultats intermédiaires. Cela peut-être gênant lors de l'analyse des résultats, par exemple pour rechercher un résultat particulier.
93
 
94
D'autre part, la solution trouvée par l'algorithme n'est pas forcément la plus simple (celle obtenue en effectuant le moins d'opérations, ou en utilisant le moins de nombres de base). L'algorithme dynamique décrit plus loin permet d'éviter cet inconvénient.
95
 
96
\subsection{Implémentation}
97
Une implémentation en C est disponible dans le fichier \filename{lceb\_rec.c}.
98
\subsubsection{Utilisation}
99
Après l'avoir compilé en tapant \code{make}, lancer le programme en tapant \code{./lceb\_rec \textit{but suivi des 6 nombres de base}}. Par exemple :
100
\begin{verbatim}
101
$ ./lceb_rec 886 4 3 5 100 1 6
102
Processing 4 3 5 100 1 6 . Goal : 886
103
FOUND
104
100 - 1 = 99
105
6 + 3 = 9
106
99 * 9 = 891
107
891 - 5 = 886
108
\end{verbatim}
109
\code{FOUND} signifie que le but a été atteint. Dans le cas contraire, \code{NOTFOUND} est suivi du résultat atteint, ainsi que de la distance par rapport au but :
110
\begin{verbatim}
111
$ ./lceb_rec 749 4 1 2 3 8 6
112
Processing 4 1 2 3 8 6 . Goal : 749
113
NOTFOUND 750 1
114
8 * 6 = 48
115
48 + 2 = 50
116
4 + 1 = 5
117
5 * 3 = 15
118
50 * 15 = 750
119
\end{verbatim}
120
 
121
\subsubsection{Détails de l'implémentation}
122
Les valeurs contenues dans les tableaux sont stockées sous la forme d'une structure :
123
\begin{verbatim}
124
struct result {
125
   int val; /* value */
126
   operation op; /* operator */
127
   struct result * l; /* left operand */
128
   struct result * r; /* right operand */
129
};
130
\end{verbatim}
131
En plus de contenir la valeur, elle contient les 2 résultats nécessaires pour l'obtenir, ainsi que l'opérateur utilisé. Si par exemple on stocke 4, comme la somme de 2 autres nombres 1 et 3, \code{val} vaudra 4, \code{op} vaudra ADD, et \code{l} et \code{r} pointeront respectivement sur les structures contenant 1 et 3.
132
 
133
La fonction \code{dispres} permet de remonter l'arbre des opérations, et ainsi, d'écrire les opérations nécessaires pour arriver à un résultat sous une forme lisible, comme dans les exemples ci-dessus.
134
 
135
La fonction \code{add} retourne, pour 2 nombres et un opérateur donnés, la structure du résultat, si l'opération est valide.
136
 
137
La fonction \code{resultest} permet de vérifier si un résultat intermédiaire est égal au but, et arrête l'exploration si c'est le cas.
138
 
139
La fonction \code{compute} est la fonction appelée récursivement pour traiter les tableaux de nombres.
140
 
141
Il faut noter que la mémoire n'est jamais libérée. Tous les résultats intermédiaires sont conservés, car il serait trop compliqué de déterminer lesquels sont nécessaires à l'affichage ultérieur éventuel d'une série d'opérations par dispres.
142
 
143
\section{Solution dynamique}
144
 
145
Cette solution est bien plus satisfaisante d'un point de vue logique.
146
 
147
\subsection{Principe}
148
Comme dans tout algorithme dynamique, la recherche de la solution passe par la recherche de sous-solutions.
149
 
150
Le point de départ est les 6 nombres de base. On va rechercher tous les résultats intermédiaires composés par 2 de ces 6 nombres de base, en combinant tous les couples possibles à l'aide de tous les opérateurs disponibles. Ensuite, les résultats intermédiaires composés de 3 nombres de base sont recherchés, puis par 4, 5, et 6 nombres de base.
151
 
152
Dès qu'un résultat intermédiaire est égal au but, on peut s'arrêter : on a trouvé une solution, et c'est une des plus propres, puisqu'elle utilise un minimum de nombres de base.
153
 
154
Si, à la fin de la recherche de tous les résultats intermédiaires, on n'a pas trouvé de résultat égal au but, il suffit de les parcourir à nouveau en recherchant le résultat qui s'en approche le plus.
155
 
156
\subsection{Avantages et inconvénients}
157
 
158
\subsubsection{Avantages}
159
 
160
Cet algorithme peut être facilement adapté à la recherche de solutions spécifiques. Par exemple, il est facile de rechercher la solution utilisant le plus de nombres de base, ou passant par le résultat intermédiaire le plus grand. La solution récursive exposée précédemment ne permet pas de faire facilement de telles recherches.
161
 
162
De plus, cet algorithme est rapide. Lors de la recherche de la première solution, il se montre un peu plus rapide que la solution récursive.
163
 
164
\subsubsection{Inconvénients}
165
 
166
Par contre, cet algorithme a le désavantage de nécessiter le stockage de tous les résultats intermédiaires. C'est aussi le cas dans la solution récursive avec l'implémentation choisie, mais d'autres implémentations de la solution récursive peuvent éviter ce problème. L'utilisation de la mémoire par l'implémentation de l'algorithme qui est présentée plus loin peut être très importante.
167
 
168
\subsection{Implémentation}
169
 
170
Une implémentation en C est disponible dans le fichier \filename{lceb\_dyn.c}.
171
 
172
\subsubsection{Détails de l'implémentation}
173
De nombreux points de l'implémentation sont communs avec celle de la solution récursive.
174
 
175
Deux champs sont ajoutés à la structure \code{result} :
176
\begin{itemize}
177
\item \code{used} décrit quels sont les nombres de base utilisés par un résultat intermédiaire donné.
178
\item \code{next} est un pointeur vers une structure result, permettant de stocker ces structures dans une liste chaînée.
179
\end{itemize}
180
 
181
Le tableau \code{results}, déclaré dans la fonction \code{main}, contient tous les résultats intermédiaires. \code{results[0]} contient les nombres de base, \code{results[1]} contient les résultats composés par 2 nombres de base, \code{results[2]} contient les résultats composés par 3 nombres de base, etc ...
182
 
183
Dans l'implémentation actuelle, l'exploration est arrêtée dans \code{resultest} dès que le but est atteint. Ensuite, si le but n'est pas atteint, tous les résultats sont repassés en revue à la fin de la fonction \code{main} pour déterminer celui s'approchant le plus du but.
184
 
185
\subsection{Optimisations éventuelles}
186
\subsubsection{Mémorisation du résultat le plus proche en cours de calcul}
187
Il est possible de mémoriser le résultat le plus proche du but en calculant la proximité du résultat courant dans \code{resultest}, et en positionnant \code{best} et \code{min}. Mais ce surplus de calcul ralentit l'algorithme. En effet, le but est atteint dans 98\% des cas, et il est donc préférable de rechercher le résultat le plus proche séparément.
188
 
189
\subsubsection{Utilisation d'un tableau de fonctions pour les opérateurs}
190
Une étude avec GNU prof tend à montrer que le \code{switch} dans la fonction \code{add} est un point faible de l'algorithme. Pour le contourner, un tableau de fonctions est ajouté. L'implémentation avec tableau de fonction est contenue dans \filename{lceb\_dynopt.c}.
191
 
192
\section{Comparaison des performances des différentes implémentations}
193
 
194
Ces valeurs ont été obtenues en compilant avec GNU cc v. 2.95.3, avec l'option -O6, et en utilisant une liste de 1000 valeurs générées aléatoirement avec le programme random.c.
195
Les durées ont été mesurées avec l'utilitaire UNIX \code{time} et le script \code{lcebusedata.sh} fourni.
196
 
197
\begin{tabular}{|l|c|c|c|}
198
\hline
199
Version & real & user & sys \\
200
\hline
201
Récursif & 1m13.324s & 1m5.239s & 0m20.903s \\
202
Dynamique & 0m47.694s & 0m40.080s & 0m20.804s \\
203
Dynamique optimisé & 0m47.665s & 0m40.230s & 0m20.693s \\
204
\hline
205
\end{tabular}
206
 
207
La très faible différence entre les versions dynamiques et dynamiques optimisé laissent penser que le compilateur effectue lui-même l'optimisation consistant à utiliser un tableau de fonctions.
208
 
209
\section{Rechercher autre chose}
210
\subsection{Plus grande somme des résultats intermédiaires}
211
Le programme \filename{lceb\_bigsum.c} montre avec quelle facilité il est possible de modifier la version dynamique pour rechercher autre chose. Ce programme recherche la solution dont la somme des résultats intermédiaires est la plus grande.
212
 
213
Exemple :
214
\begin{itemize}
215
\item Avec recherche de la plus grande somme :
216
\begin{verbatim}
217
$ ./lceb_bigsum 374 50 3 5 10 8 75
218
Processing 50 3 5 10 8 75 . Goal : 374
219
FOUND
220
75 * 50 = 3750
221
3750 + 3 = 3753
222
3753 - 5 = 3748
223
3748 - 8 = 3740
224
3740 / 10 = 374
225
\end{verbatim}
226
\item Version dynamique classique :
227
\begin{verbatim}
228
$ ./lceb_dyn 374 50 3 5 10 8 75
229
Processing 50 3 5 10 8 75 . Goal : 374
230
FOUND
231
75 * 5 = 375
232
10 - 3 = 7
233
375 + 7 = 382
234
382 - 8 = 374
235
\end{verbatim}
236
\end{itemize}
237
 
238
\section{Recherche de la solution dont l'arbre est le moins profond}
239
L'algorithme dynamique permet aussi, avec quelques modifications, de rechercher la solution dont l'arbre des solutions est le moins profond. Par exemple, si on cherche à faire 4 avec 1, 1, 1, 1, il sera plus élégant de faire (1 + 1) + (1 + 1), que ((1 + 1) + 1) + 1.
240
 
241
Le fichier \filename{lceb\_easiest.c} recherche la solution dont l'arbre est le moins profond.
242
 
243
Exemples :
244
\begin{itemize}
245
\item Version dynamique classique :
246
\begin{verbatim}
247
$ ./lceb_dyn 444 100 1 3 75 8 10
248
Processing 100 1 3 75 8 10 . Goal : 444
249
FOUND
250
75 - 1 = 74
251
10 + 8 = 18
252
74 * 18 = 1332
253
1332 / 3 = 444
254
\end{verbatim}
255
 
256
\item Version avec recherche de l'arbre le moins profond :
257
En affichant tous les résultats intermédiaires, on constate que la solution proposée par l'algorithme dynamique classique a une complexité de 4221, alors qu'une autre solution, utilisant le même nombre de nombres de base, a une complexité inférieure :
258
\begin{verbatim}
259
*** 2421 ***
260
75 - 1 = 74
261
10 + 8 = 18
262
18 / 3 = 6
263
74 * 6 = 444
264
\end{verbatim}
265
\end{itemize}
266
 
267
\section{Composition de l'archive}
268
Outre les exemples déjà présentés, l'archive contient aussi plusieurs scripts permettant de faciliter les tests des différents algorithmes.
269
\begin{itemize}
270
\item \filename{Makefile} : fichier permettant d'automatiser la compilation des programmes en tapant \code{make}.
271
\item \filename{getnotfound.sh} : ce fichier permet, à partir d'une liste de problèmes, d'afficher ceux pour lesquels une solution n'a pas pu être trouvée. Exemple : \code{./random 100 | ./getnotfound.sh lceb\_dyn}
272
\item \filename{lceb\_bigsum.c}, \filename{lceb\_dyn.c}, \filename{lceb\_dynopt.c}, \filename{lceb\_easiest.c}, \filename{lceb\_rec.c} sont les fichiers sources des algorithmes présentés précédemment. Ils sont sous licence GPL.
273
\item \filename{lceb.tex} est le fichier LaTeX utilisé pour générer ce que vous êtes en train de lire. Il est placé sous licence FDL.
274
\item \filename{lcebusedata.sh} permet d'utiliser une liste de problèmes avec un algorithme. Exemple :\\ \code{./lcebusedata.sh lceb\_dyn liste}.
275
\item \filename{random.c} permet de générer aléatoirement une liste de problèmes. Exemple : \code{./random 100} (pour générer 100 problèmes).
276
\end{itemize}
277
\section{Conclusion}
278
 
279
J'espère que la lecture de ces quelques idées sur ce problème très ludique vous aura intéréssée. Si vous pensez que des améliorations sont à apporter à ce document ou aux programmes d'exemple, vous pouvez bien sûr me contacter à l'adresse \url{lucas@lucas-nussbaum.net}.
280
 
281
\section{Licence}
282
Copyright (c) 2002  Lucas Nussbaum <lucas@lucas-nussbaum.net>
283
 
284
Permission is granted to copy, distribute and/or modify this document under the terms of the GNU Free Documentation License, Version 1.1 or any later version published by the Free Software Foundation.
285
 
286
A copy of the license is available on \url{http://www.gnu.org}.
287
\end{document}