{ "cells": [ { "cell_type": "markdown", "metadata": { "toc": "true" }, "source": [ "# Table of Contents\n", "
" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Texte d'oral de modélisation - Agrégation Option Informatique\n", "## Préparation à l'agrégation - ENS de Rennes, 2016-17\n", "- *Date* : 3 avril 2017\n", "- *Auteur* : [Lilian Besson](https://GitHub.com/Naereen/notebooks/)\n", "- *Texte*: Annale 2006, \"Sudoku\"" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## À propos de ce document\n", "- Ceci est une *proposition* de correction, partielle et probablement non-optimale, pour la partie implémentation d'un [texte d'annale de l'agrégation de mathématiques, option informatique](http://Agreg.org/Textes/).\n", "- Ce document est un [notebook Jupyter](https://www.Jupyter.org/), et [est open-source sous Licence MIT sur GitHub](https://github.com/Naereen/notebooks/tree/master/agreg/), comme les autres solutions de textes de modélisation que [j](https://GitHub.com/Naereen)'ai écrite cette année.\n", "- L'implémentation sera faite en OCaml, version 4+ :" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "scrolled": true }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "The OCaml toplevel, version 4.02.3\n" ] }, { "data": { "text/html": [ " " ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "Sys.command \"ocaml -version\";;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----\n", "## Question de programmation\n", "La question de programmation pour ce texte était donnée au tout début, en page 2 :\n", "\n", "> « Écrire une fonction prenant pour paramètres un entier, $p \\geq 1$, et un tableau carré de côté $p$ (donc de taille $p^2$) d'entiers, $T$, et renvoyant un booléen disant si ce tableau est un carré latin, c'est-à-dire contenant dans chaque ligne et chaque colonne une et une seule fois chaque entier de $1$ à $p$.\n", "\n", "Mathématiquement, si $N_p := \\{1,\\dots,p\\}$, cela donne un prédicat $\\mathrm{estCarreLatin}_p(T)$ sur un tableau $T$ :\n", "$$\n", "\\mathrm{estCarreLatin}_p(T) \\Longleftrightarrow\n", "\\forall i \\in N_p, \\left\\{ T_{i,j} : j \\in N_p \\right\\} = N_p\n", "\\;\\text{and}\\;\n", "\\forall j \\in N_p, \\left\\{ T_{i,j} : i \\in N_p \\right\\} = N_p\n", "$$\n", "\n", "> « En prenant $p = n^2$ on obtient une partie des contraintes d'admissibilité d'une grille complète de Su Doku, mais il reste encore à vérifier la contrainte sur les petits carrés. »\n", "\n", "Pour l'annecdote historique, cette idée de carré latin date vraiment de l'époque romaine antique. On a trouvé à Pompeï des carrés latins de taille $4$ ou $5$ !" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "----\n", "## Réponse à l'exercice requis\n", "C'est assez rapide :\n", "\n", "1. On écrit une fonction qui permet d'extraire une ligne ou une colonne d'un tableau $T$,\n", "2. On écrit ensuite une fonction qui permet de vérifier si un tableau de $p$ entiers contient exactement $N_p = \\{1, \\dots, p\\}$,\n", "3. Enfin, on vérifie toutes les contraintes." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "> *Remarque:* On suppose que tous les tableaux considérés sont :\n", "> - **non vides**\n", "> - et **carrés**\n", "> On ne vérifie pas ces deux points." ] }, { "cell_type": "code", "execution_count": 2, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" }, { "data": { "text/html": [ " " ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let ligne p t i = Array.init p (fun j -> t.(i).(j)) ;;\n", "(* t.(i) marche aussi bien ! *)\n", "\n", "let colonne p t j = Array.init p (fun i -> t.(i).(j)) ;;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On a besoin de savoir si un tableau de booléens sont tous vrais ou pas.\n", "On peut utiliser la fonction déjà existante, `Array.for_all`, ou bien un `Array.fold_left`, ou une implémentation manuelle." ] }, { "cell_type": "code", "execution_count": 3, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let tousVrai tab =\n", " let res = ref true in\n", " for i = 0 to (Array.length tab) - 1 do\n", " res := !res && tab.(i)\n", " done;\n", " !res\n", ";;" ] }, { "cell_type": "code", "execution_count": 4, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 4, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let tousVrai = Array.fold_left (&&) true;;\n", "(* Array.for_all marche aussi bien ! *)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Ca permet de facilement vérifier si un tableau `tab` de taille $p$ est exactement $N_p = \\{1,\\dots,p\\}$, en temps linéaire (c'est optimal) en $p$.\n", "\n", "1. On ajoute un test que tous les entiers soient bien entre $1$ et $p$,\n", "2. puis on fait ce test en $\\mathcal{O}(\\# tab)$, en créant est un tableau `estLa` de taille $p$, remplis de `false`. En bouclant sur $t$, on remplit $\\texttt{tab}[i]$ à `true` dans `estLa` (en fait, `tab(i) - 1` car les indices sont entre $0$ et $p-1$). A la fin, si le tableau `estLa` est rempli de `true`, alors on a vu tous les entiers de $N_p$ une et une seule fois." ] }, { "cell_type": "code", "execution_count": 5, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 5, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let estNp p tab =\n", " if tousVrai (Array.map (fun x -> (1 <= x) && (x <= p)) tab) then begin \n", " let estLa = Array.make p false in\n", " for i = 0 to p - 1 do\n", " estLa.(tab.(i) - 1) <- true\n", " done;\n", " tousVrai estLa\n", " end\n", " else\n", " false\n", ";;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On va adopter une méthode naïve mais simple à écrire :\n", "\n", "- on construit deux tableaux de $p$ booléens,\n", "- on les remplit des contraintes pour les $p$ lignes et les $p$ colonnes,\n", "- et on les vérifie avec `tousVrai`." ] }, { "cell_type": "code", "execution_count": 32, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 32, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let contraintes_lignes p t =\n", " tousVrai (Array.init p (fun i ->\n", " estNp p (ligne p t i)\n", " ))\n", ";;" ] }, { "cell_type": "code", "execution_count": 31, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 31, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let contraintes_colonnes p t =\n", " tousVrai (Array.init p (fun j ->\n", " estNp p (colonne p t j)\n", " ))\n", ";;" ] }, { "cell_type": "code", "execution_count": 7, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 7, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let carre_latin p t =\n", " (contraintes_lignes p t) && (contraintes_colonnes p t)\n", ";;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Une autre approche\n", "Plutôt que d'écrire une fonction pour extraire une colonne, et deux fonction qui vérifies les contraintes sur les lignes et les colonnes, on remarque le fait suivant :\n", "\n", "> « Les colonnes de $t$ sont les lignes de $t^T$, la matrice transposée de $t$ ».\n", "\n", "Donc pas besoin de savoir extraire les colonnes, dès qu'on a écrit `contraintes_lignes`, on peut avoir les contraintes sur les colonnes facilement.\n", "\n", "Pour calculer la transposée, une approche simple utilise deux boucles `for` :" ] }, { "cell_type": "code", "execution_count": 8, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 8, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let transpose_for p tab =\n", " let tab2 = Array.make_matrix p p 0 in\n", " for i = 0 to p - 1 do\n", " for j = 0 to p - 1 do\n", " tab2.(i).(j) <- tab.(j).(i);\n", " done;\n", " done;\n", " tab2\n", ";;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "On peut rapidement vérifier sur un exemple,\n", "$$ \\begin{bmatrix} 1 & 2 \\\\ 3 & 4 \\end{bmatrix}^{T} = \\begin{bmatrix} 1 & 3 \\\\ 2 & 4 \\end{bmatrix}. $$" ] }, { "cell_type": "code", "execution_count": 9, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 9, "metadata": {}, "output_type": "execute_result" } ], "source": [ "transpose_for 2 [| [|1; 2|]; [|3; 4|] |];;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notez qu'on peut faire mieux, sans boucles `for`, avec deux `Array.init` imbriqués :" ] }, { "cell_type": "code", "execution_count": 10, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 10, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let transpose p tab =\n", " Array.init p (fun i -> (Array.init p (fun j -> tab.(j).(i))));;" ] }, { "cell_type": "code", "execution_count": 11, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 11, "metadata": {}, "output_type": "execute_result" } ], "source": [ "transpose 2 [| [|1; 2|]; [|3; 4|] |];;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Et donc :" ] }, { "cell_type": "code", "execution_count": 12, "metadata": {}, "outputs": [ { "data": { "text/html": [ " " ] }, "execution_count": 12, "metadata": {}, "output_type": "execute_result" } ], "source": [ "let carre_latin2 p t =\n", " (contraintes_lignes p t) && (contraintes_lignes p (transpose p t))\n", ";;" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Un mauvais exemple\n", "On va prendre le premier carré de taille $p = 3$ dans le problème de Su Doku donné en figure 1 de l'énoncé.\n", "\n", "