TP 2— SUDOKU

publicité
TP 2— S UDOKU
http://www.liafa.jussieu.fr/∼nath/tp2/tp2.pdf
Les questions sont les bienvenues et peuvent être envoyées à [email protected]
L’objectif de ce TP est de résoudre un sudoku de manière automatique, et disons-le tout de suite, peu subtile,
par la méthode du backtracking (d’aucuns diraient ”retour sur trace“). Pour cela, une fois n’est pas coutume,
nous utiliserons un peu de programmation impérative, par opposition à récursive.
La première section est une mise au point sur un écueil courant : les fonctions à plusieurs paramètres et le
placement des parenthèses. La deuxième section est une mise au point sur la programmation impérative.
1 T RAITEMENT DES FONCTIONS À PLUSIEURS PARAMÈTRES
Une fonction est une valeur comme les autres. Elle peut
# let deux_fois f = (fun x -> f x + f x);;
par exemple être passée en argument d’une autre fonction,
# deux_fois (fun n -> n + 1) 3 ;;
ou renvoyée comme valeur de retour. Ici, deux_fois prend
- : int = 8
une fonction f en paramètre et renvoie une autre fonction
(fun x -> ..).
Si l’on fait un peu attention, on peut aussi voir deux_fois comme une fonction qui prend deux arguments,
f et x, et renvoie f (x) + f (x). On peut d’ailleurs écrire (et c’est strictement équivalent) :
let deux_fois f x = f x + f x.
Cette méthode permet d’exprimer des fonctions à plusieurs arguments. On peut la généraliser à un nombre
quelconque (mais fixé à l’avance) d’arguments. Elle s’appelle la curryfication, et correspond à l’isomorphisme,
en mathématiques, entre (A × B) → C et A → (B → C).
Remarque : Avec les fonctions à plusieurs arguments, on voit apparaître des types de la forme
int -> int -> int. Dans ces cas là il faut lire (la flèche est associative à droite) int -> (int -> int).
Notations On peut écrire let f x = a à la place de let f = fun x -> a. Selon le même principe, on peut
simplifier l’écriture de fonctions à plusieurs paramètres :
• on peut compresser une suite de fun : le code fun x -> fun y -> z s’écrit aussi fun x y -> z.
• on peut faire rentrer plusieurs fun dans un let : let f = fun x y -> z s’écrit aussi let f x y = z.
Il faut bien comprendre que ces notations représentent exactement le même code.
1.1 A PPLICATION
PAR TIELLE
Il n’y a pas à proprement parler de “fonction à deux argu# let somme x y = x + y;;
ments” en Caml. Il y a par contre des fonctions qui prennent
# let somme’ x = fun y -> x + y;;
un argument, puis renvoient une fonction qui prend un deux# let somme_3 = somme’ 3;;
ième argument et renvoie un résultat. En particulier, fournir
# somme_3 2;;
un seul argument à une fonction Caml est toujours défini,
- int : 5
vous n’aurez jamais d’erreur du style “Vous n’avez pas fourni
assez d’arguments”.
Par exemple, dans le code ci-contre, les fonctions somme et somme’ sont exactement identiques, mais la
deuxième permet mieux de comprendre ce que signifie somme 3 : c’est la fonction qui, quand on lui donne un
paramètre, lui ajoute 3. On parle dans ce cadre d’application partielle : on a donné seulement une partie des
arguments à la fonction.
1.2 P LACEMENT
DES PARENTHÈSES
La source d’erreur est le fait que le placement des parenthèses en Caml est différent de la notation mathématique habituelle.
Les parenthèses utilisées autour des expressions n’ont pas de signification. Elles servent uniquement à
éliminer les ambiguités, comme en mathématiques (différence entre 1 + 2 ∗ 3 et (1 + 2) ∗ 3).
1
Pour appeler une fonction f avec un argument x, on écrit simplement f x. Vous pouvez aussi écrire (f x),
(f)x, f(x) ou (f)((x)) si cela vous fait plaisir, mais contrairement à la notation mathématique, les parenthèses autour du x ne sont pas nécessaires, et je vous déconseille de les mettre.
Le problème de la notation mathématique est son extension aux appels de fonction à plusieurs arguments,
comme par exemple g x y : on donne à la fonction g l’argument x, puis y. Si l’on veut être précis, on dit
que l’application de x à g renvoie une fonction (g est de la forme fun a -> fun b -> c), et que c’est à cette
fonction qu’on applique y. On pourrait donc aussi écrire let h = g x in h y ou (g x) y.
C’est ici que la notation mathématique à laquelle vous êtes habitués risque de vous faire faire des erreurs.
g x y est équivalent à (g x) y, mais pas à g(x y) ! Cette deuxième écriture signifie qu’on applique y à x, et
qu’on donne le résultat à g. Par contre, si vous voulez écrire f (g(x)), c’est bien f (g x) et pas f g x !
⊲ Question 1. Que déduire du code ci-contre? ⊳
let f x y = () ;;
let a = ref 0 in f (a := 1) (a := 2) ; !a ;;
2 R APPELS DE PROGRAMMATION IMPÉRATIVE
2.1 C HAMPS
MODIFIABLES
En caml, les variables ne sont pas modifiables : une fois qu’une variable a été déclarée par un let, sa valeur
ne change pas (jusqu’à la déclaration suivante).
Si ’a est un type caml, ’a ref désigne le type des références contenant une valeur de type ’a. On peut voir
une référence x comme un tiroir : on peut l’ouvrir pour regarder à l’intérieur (avec !x, on voit un objet de type
’a), ou bien changer son contenu pour y mettre la valeur v de type ’a, (avec x := v).
Pour créer une nouvelle référence, on utilise la fonction
ref en lui donnant une valeur de départ (la valeur que contiendra le tiroir avant que son contenu ne soit modifié). Attention à la subtilité : quand on utilise l’opérateur :=, on
change le contenu de la référence, pas la référence elle-même
(qui désigne toujours le même tiroir) !
let test =
let a, b = ref 1, ref 2 in
let c, d = a, ref a in
a := !a + !b; d := c; !(!d) + !a;;
⊲ Question 2. Quelle est la valeur de la variable test ? ⊳
Les cases des tableaux et des chaînes de caractères sont aussi des champs modifiables : tab.(i) et str.[i]
permettent d’obtenir la valeur en i-ème position (en partant de 0) du tableau tab et de la chaîne str. Pour les
modifier on n’utilise pas “:=” mais “<-” : par exemple tab.(i) <- v.
2.2 E XCEPTIONS
Les exceptions sont une manière d’interrompre une partie d’un programme en cas d’erreur. Par exemple,
si vous avez une formule mathématique à calculer, et qu’en plein calcul vous vous apercevez que vous devez
diviser 0, vous allez vous arrêter et vous plaindre que la formule n’est pas bien définie. caml sait faire pareil.
Les expressions sont des objets de type exn qui ressem#exception Erreur of string;;
blent beaucoup aux types sommes définis dans le précélet boum () = raise (Erreur "boum");;
dent TP : ce sont des constructeurs, qui peuvent comporter
des arguments, et sont déclarés par le mot-clé exception.
try (boum (); "message") with
Quand on a trouvé une erreur, on peut lancer une ex| Exit -> "sortie"
| Erreur message -> "erreur : " ˆ message
pression avec la fonction raise : elle prend une expression
| _ -> "exception inconnue"
en paramètre, et interromp le calcul (en particulier, tout ce
qui devait se passer ensuite dans le programme n’est pas
exécuté).
Cela permet de faire des erreurs qui stoppent complètement le calcul. Parfois, on voudrait plutôt détecter
l’erreur et utiliser une solution adaptée pour continuer le programme (par exemple si l’erreur est “plus de
papier dans l’imprimante”, il suffit de demander à l’utilisateur de rajouter du papier avant de continuer, au
lieu d’annuler complètement l’impression en cours). On peut rattrapper une exception avec la construction
try <expr> with <filtrage>. Cela se présente un peu comme un match ... with, mais le comportement
est différent :
2
• si l’évaluation de <expr> ne provoque aucune exception, on renvoie sa valeur
• sinon, on effectue le filtrage sur la valeur de l’exception envoyée
⊲ Question 3.
Écrire une fonction existe avec une boucle for, tel que existe elt tableau teste
l’existence de elt dans le tableau tableau en parcourant le moins possible d’éléments du tableau. ⊳
⊲ Question 4. Quel est le type de raise ? Pourquoi ? ⊳
2.3 D ANGERS
On veut créer un tableau à deux dimensions, sans utiliser
la fonction déjà toute faite Array.make_matrix.
⊲ Question 5. Quel est le problème avec mat ?
Coder (correctement) nouvelle_matrice avec une boucle. ⊳
let nouvelle_matrice n p x =
Array.make n (Array.make p x);;
let mat = nouvelle_matrice 3 3 0;;
mat.(0).(1) <- 2; mat;;
3 S UDOKU
Les sudokus classiques sont des grilles 9 × 9 où chaque case est soit blanche, soit contient un chiffre parmi
{1, . . . , 9}. Par convention, les cases blanches contiennent des 0. L’objectif est de remplir les cases blanches, de
manière à ce que sur chaque ligne, sur chaque colonne, et sur chacun des 9 carrés 3 × 3, chaque chiffre apparaît
une fois et une seule. Ce sont des sudokus d’ordre 3.
Un sudoku d’ordre n est une grille n2 × n2 où chaque case est soit blanche, soit contient un nombre parmi
{1, . . . , n2 }. L’objectif est de remplir les cases blanches, de manière à ce que sur chaque ligne, sur chaque
colonne, et sur chacun des n2 carrés n × n, chaque chiffre apparaît une fois et une seule.
Pour résoudre un sudoku, nous allons utiliser
la méthode du backtracking. L’entrée est un entier n et une grille n2 × n2 . Cette grille ne sera
pas modifiée. On procède récursivement, en
maintenant une liste des choix effectués, sous
la forme de triplet (i, j, k) : ligne i, colonne j,
on place l’entier k. L’algorithme procède ainsi
: si les conditions du sudoku ne sont pas violées, alors faire un nouveau choix, sinon revenir
en arrière, c’est-à-dire modifier le dernier choix
qui peut l’être et supprimer les choix faits après
celui-ci. S’il n’est pas possible de revenir en arrière, c’est qu’il n’y a pas de solution.
⊲ Question 6.
libre. ⊳
let resoudre_sudoku n grille =
let nc = n * n in
let rec boucle liste_choix =
try
if correct n nc grille liste_choix
then boucle (choix_suivant nc grille liste_choix)
else boucle (changer_choix nc grille liste_choix)
with
| Fini -> retourner_solution nc grille liste_choix
| Echec -> failwith "pas de solution"
in boucle [] ;;
Coder la fonction choix_suivant. Elle déclenche l’exception Fini s’il n’y a plus de case
⊲ Question 7. Coder la fonction changer_choix. Elle déclenche l’exception Echec s’il n’est pas possible de
revenir en arrière. ⊳
⊲ Question 8. Coder la fonction correct. Remarquer qu’il est inutile de tester toute la grille, seulement
une ligne, une colonne et un carré. Coder la fonction retourner_solution. Tester votre code, par exemple sur
une grille vide. ⊳
⊲ Question 9.
trouvée). ⊳
Modifier à peine le code pour rendre toutes les solutions (au lieu de rendre la première
⊲ Question 10. Fixons une grille d’ordre n possédant k cases blanches. Une configuration est une liste de
triplets, remplissant certaines des cases blanches de la grille. Elle est valide si elle ne viole pas les conditions du
3
sudoku une fois insérée dans la grille. L’ensemble des configurations valides peut être représenté de manière
à interpréter l’algorithme implémenté : plaçons tout en haut la configuration vide (liste vide). Étant donné une
configuration valide c, on lui associe des fils, qui sont les configurations valides qui étendent la configuration c
d’exactement un triplet. Ceci définit (il faudrait être plus précis) l’arbre des configurations. Que représentent
les feuilles ? Quelle est la taille de cet arbre, combien y a t-il de feuilles (en fonction de n et k) ? Dernière
question : l’algorithme implémenté parcourt les configurations. Décrire le parcours de l’arbre qu’il effectue.
Votre modification du code (question précédente) visite t-il toutes les configurations ? ⊳
⊲ Question 11. La fonction resoudre_sudoku est-elle récursive terminale? ⊳
⊲ Question 12. De nombreuses optimisations sont possibles, dont certaines s’appuie sur une étude fine de
l’arbre des configurations. Proposer et implémenter quelques optimisations. ⊳
4 Q UESTION DIFFICILE
Cette dernière partie est une petite incursion vers ce que cache la programmation fonctionnelle (à savoir le
lambda-calcul). L’entier de Church noté n̂ est la fonction qui à f associe f n (l’itérée n fois de f ).
On peut le définir en caml par :
Ainsi church n retourne n̂.
let rec church n f x =
if n = 0 then x
else church (n-1) f (f x);;
⊲ Question 13. Quel est le type de n̂? ⊳
⊲ Question 14. Coder 0̂, 1̂ et 2̂. ⊳
⊲ Question 15. Coder la fonction eval qui à n̂ associe n. ⊳
ˆ 1. ⊳
⊲ Question 16. Coder la fonction succ qui à n̂ associe n +
⊲ Question 17. Coder les fonctions add, mult et exp qui calculent la somme, le produit et l’exponentiation
de deux entiers de Church. ⊳
4
Téléchargement