WikiPrépaLivrets

Mines Informatique 2 MPI 2023Sujet et rapport du jury

Pas encore noté
  • Complexité de Kolmogoroff et calculabilité
  • Codage de Huffman
  • Algorithmes dichotomiques
  • Langages réguliers et algébriques, lemme de l'étoile
  • Automates (Glushkov)
  • Parcours d'arbres et termes de De Bruijn
  • Programmation fonctionnelle en OCaml

Téléchargements

  • Corrigé : pas encore disponible

Présentation du sujet

Difficile
Complexité de Kolmogoroff : algorithmique, langages formels et programmation OCaml
Afficher ou masquer la section

Le sujet est un problème unique en quatre sections indépendantes portant sur la complexité de Kolmogoroff de la représentation décimale de 10^(10^10). La première section introduit la définition de cette complexité et la notion de décompresseur. La deuxième s'appuie sur le codage de Huffman pour compresser une expression OCaml. La troisième analyse des fonctions censées générer des chaînes de caractères uniques. La quatrième introduit un langage proche du lambda-calcul, avec grammaire, parseur vers les termes de De Bruijn et interpréteur.

  1. 1Section 1 : définition de la complexité de KolmogoroffDéfinition de la complexité de Kolmogoroff dans le cadre d'une machine à mémoire infinie et de programmes OCaml, calculabilité et notion de décompresseur.
  2. 2Section 2 : codage de HuffmanCompression d'une expression OCaml obfusquée par codage de Huffman et majoration de la complexité de Kolmogoroff associée.
  3. 3Section 3 : génération de chaînes de caractères uniquesAnalyse de trois fonctions pour identifier celle qui ne répond pas aux exigences et celle dont la syntaxe est la moins risquée.
  4. 4Section 4 : un nouveau langage type lambda-calculÉtude de la grammaire du langage, implémentation d'un parseur vers les termes de De Bruijn puis d'un interpréteur servant de décompresseur.

Difficile. Le rapport indique explicitement qu'au regard de la difficulté du sujet, les correcteurs sont satisfaits du niveau d'un grand nombre de candidats, et plusieurs questions ont des taux de réussite faibles.

L'épreuve en chiffres

Moyenne 11,23 / 20 · écart-type 4,26 · 663 présents · où vous situez-vous ?
Afficher ou masquer la section
Moyenne
11,23/ 20
Écart-type
4,26
Présents
663
Coefficient
4
Durée
4 h
moyenne 11,2305101520
Deux tiers des copies environ (moyenne ± écart-type)

Votre note sur 20 à ce sujet, en conditions de concours.

Source : document officiel du concours, épreuve du 5 mai 2023. Notes publiées par le concours (après harmonisation le cas échéant). Courbe : estimation par une loi normale.

Ce qu'a observé le jury

6 erreurs relevées
Manque de rigueur dans la rédaction des preuves · Lemme de l'étoile mal énoncé · Notation de Landau mal utilisée
Afficher ou masquer la section

Malgré des notions ambitieuses, les correcteurs témoignent d'une bonne maîtrise et mise en application des notions au programme par la plupart des candidats, avec des intuitions fines même sur les questions difficiles. Ils relèvent toutefois un manque général de rigueur dans la rédaction des preuves et des erreurs récurrentes de programmation OCaml. Tous les candidats ont traité au moins une quinzaine de questions, 25 en moyenne sur les 38 du sujet.

Les erreurs les plus sanctionnées

  1. 1
    Manque de rigueur dans la rédaction des preuves

    Beaucoup de candidats se contentent d'un descriptif approximatif sans vérifier l'ensemble des hypothèses, ni pour les récurrences ni pour les inductions structurelles.

  2. 2
    Lemme de l'étoile mal énoncéQ19

    L'intuition derrière le lemme de l'étoile semble comprise, mais trop peu de candidats en écrivent l'énoncé formel et l'utilisent correctement.

    « le lemme de l’étoile a été énoncé correctement par trop peu de candidat »
  3. 3
    Notation de Landau mal utilisée

    Plusieurs candidats écrivent qu'un entier fixé est égal à un O(C) plutôt que d'écrire l'inégalité correspondante, confondant domination asymptotique et égalité.

  4. 4
    Automate de Glushkov mal identifiéQ14

    Bien que traitée par 92 % des candidats, 60 % d'entre eux ont proposé un automate trop éloigné de celui attendu, probablement à cause d'une lecture trop rapide de la question.

    « 60% d’entre eux ont proposé un automate trop éloigné de l’automate de Glushkov »
  5. 5
    Structure de données efficace peu identifiéeQ20

    Pour une opération de recherche efficace, seuls 40 % des candidats ayant traité la question ont parlé d'arbre binaire de recherche et 14 % d'arbre équilibré, ce qui a surpris les correcteurs.

    « Les correcteurs sont surpris de ne pas avoir des pourcentages plus élevés de bonne réponse. »
  6. 6
    Erreurs récurrentes en OCaml

    Confusion entre le modulo OCaml et celui d'autres langages, tentative de modifier des chaînes de caractères immuables, ou confusion entre reconnaissance de motif et test de valeur.

Ce qui a été bien réussi

  • Les programmes proposés par les candidats sont généralement de très bonne qualité, sur la forme et le fond.
  • La majorité des candidats ont évoqué les indentations et retours à la ligne pour la question sur la lisibilité du code.
  • Un candidat sur six ayant traité la question 16 a réussi à écrire la preuve du début à la fin avec rigueur.

Conseils du jury

  • Toute fonction récursive doit comporter une condition d'arrêt.
  • Préférer une solution claire et courte à une solution longue et complexe : les réponses alternatives longues sont rarement entièrement correctes.
  • Respecter l'esprit du langage imposé : privilégier une approche fonctionnelle en OCaml plutôt qu'un style impératif proche du C.
  • Faire attention à la complexité de sa fonction, par exemple en évitant de rappeler plusieurs fois une fonction sur les mêmes arguments.
  • Bien expliciter la propriété à démontrer avant une récurrence ou une induction, énoncer l'hypothèse d'induction, et conclure par une phrase minimale.

Synthèse rédigée par WikiPrépa à partir du rapport officiel du jury (à télécharger en PDF). Les citations sont extraites du rapport.

Ces sujets peuvent vous intéresser

Pas encore de corrigé pour ce sujet : voici des sujets proches corrigés.

Lecture du sujet en ligne

L'énoncé complet, avec les formules et les figures, sans ouvrir le PDF.
Afficher ou masquer la section

ÉCOLE DES PONTS PARISTECH, ISAE-SUPAERO, ENSTA PARIS, TÉLÉCOM PARIS, MINES PARIS, MINES SAINT-ÉTIENNE, MINES NANCY, IMT ATLANTIQUE, ENSAE PARIS, CHIMIE PARISTECH - PSL.

Concours Mines-Télécom

CONCOURS 2023

DEUXIÈME ÉPREUVE D'INFORMATIQUE

Durée de l'épreuve : 4 heures
L'usage de la calculatrice et de tout dispositif électronique est interdit.
Les candidats sont priés de mentionner de façon apparente sur la première page de la copie :
INFORMATIQUE II - MPI
L'énoncé de cette épreuve comporte 12 pages de texte.
Cette épreuve concerne uniquement les candidats de la filière MPI.
Si, au cours de l'épreuve, un candidat repère ce qui lui semble être une erreur d'énoncé, il le signale sur sa copie et poursuit sa composition en expliquant les raisons des initiatives qu'il est amené à prendre.
Les sujets sont la propriété du GIP CCMP. Ils sont publiés sous les termes de la licence Creative Commons Attribution - Pas d'Utilisation Commerciale - Pas de Modification 3.0 France. Tout autre usage est soumis à une autorisation préalable du Concours commun Mines Ponts.

Préliminaires

L'épreuve est composée d'un problème unique, comportant 38 questions. Le problème est divisé en quatre sections qui peuvent être traitées séparément, à condition de lire toutes les définitions de la section 1 . Dans la première section (page 1), nous introduisons la complexité de Kolmogoroff et étudions des propriétés de calculabilité. Dans la deuxième section (page 3), nous estimons la complexité de Kolmogoroff à l'aide du codage de Huffman. La troisième section (page 4) contient des prolégomènes pour la section suivante. Dans la quatrième section (page 5), nous nous appuyons sur un modèle de calcul épuré, introduit à travers plusieurs langages formels, afin toujours d'estimer la complexité de Kolmogoroff.
Dans tout l'énoncé, un même identificateur écrit dans deux polices de caractère différentes désigne la même entité, mais du point de vue mathématique pour la police en italique (par exemple n, D ou π ) et du point de vue informatique pour celle en romain avec espacement fixe (par exemple n, d ou pi).

Travail attendu

Pour répondre à une question, il est permis de réutiliser le résultat d'une question antérieure, même sans avoir réussi à établir ce résultat.
Il faudra coder des fonctions à l'aide du langage de programmation OCaml exclusivement, en reprenant l'en-tête de fonctions fourni par le sujet, sans s'obliger à recopier la déclaration des types. Il est permis d'utiliser la totalité du langage OCaml mais il est recommandé de s'en tenir aux fonctions les plus courantes afin de rester compréhensible. Des rappels ponctuels de documentation du langage OCaml peuvent être proposés à titre d'aide. Quand l'énoncé demande de coder une fonction, sauf demande explicite de l'énoncé, il n'est pas nécessaire de justifier que celle-ci est correcte ou de tester que des préconditions sont satisfaites.
Le barème tient compte de la clarté des programmes : nous recommandons de choisir des noms de variables intelligibles ou encore de structurer de longs codes par des blocs ou par des fonctions auxiliaires dont on décrit le rôle.

1 Complexité de Kolmogoroff

Nous notons Σ l'ensemble ordonné des 256 caractères ASCII étendus usuels et Σ^∗ l'ensemble des chaînes de caractères. Pour toute chaîne de caractères x ∈ Σ^∗, la longueur de x, notée |x|, est le nombre de caractères qui la composent. Par exemple, la longueur de la chaîne "abac" est 4 . Le nombre d'occurrences d'un symbole σ ∈ Σ dans une chaîne de caractères x ∈ Σ^∗ est noté |x|_σ. Par exemple, |abac|_a = 2.
Dans l'ensemble du sujet, nous nous appuyons sur une machine universelle, c'est-à-dire une fonction OCaml eval, de type string -> string, telle que :
  • si la chaîne x, de type string, est le code source d'une expression OCaml y de type string et si l'exécution du code x se termine sans erreur, alors eval x se termine et a pour valeur de retour la valeur de y;
  • sinon, eval x ne se termine pas.
Les exécutions de eval ont lieu sur une machine idéale dont la mémoire est infinie et qui est capable de gérer des types natifs de taille quelconque.
Définition : Pour toute chaîne de caractères y ∈ Σ^∗, nous disons que la chaîne de caractères x ∈ Σ^∗ est une description de la chaîne y si le calcul eval x se termine et renvoie la chaîne y. Nous appelons complexité de Kolmogoroff de y et notons K(y) la longueur de la plus courte chaîne de caractères x ∈ Σ^∗ qui décrit y.
L'objet de ce sujet est d'étudier des propriétés et diverses majorations de la complexité de Kolmogoroff. Dans toutes nos illustrations, nous nous concentrons sur la description de la chaîne de caractères
y_0 = ′1000000…′ ∈ Σ^∗
qui correspond à l'entier 10^((10^(10))) écrit en base 10 et que nous fixons une fois pour toutes.

1.1 Un premier exemple

◻1 - Proposer une première majoration de la complexité de Kolmogoroff K(y_0), qui s'appuie sur la chaîne de caractères x_0 = y_0 = ′1000000…′ comme description de y_0.
Indication OCaml : Il est rappelé que la fonction string_of_int convertit un entier en une chaîne de caractères.
◻2 - Soient n un entier naturel et n^′ la partie entière de n/2. Exprimer la quantité 10^n en fonction de 10^(n^′). Compléter le code OCaml suivant en utilisant une stratégie «diviser pour régner » :
let exp10 n = (* Calcul de 10^∧n a ecrire *)
in string_of_int (exp10 (exp10 10))
afin d'en faire une description de la chaîne de caractères y_0 = ′1000000…. . En déduire une nouvelle borne grossière (à 10^2 près) de la complexité de Kolmogoroff K(y_0), significativement meilleure que celle de la question 1.
◻3 - Décrire quelle ou quelles difficultés adviendraient si l'on exécutait le code de la question 2 sur une machine réelle.

1.2 Quelques propriétés

Indication OCaml : L'expression String.make (n : int) (sigma : char) : string désigne la chaîne de caractères répétant n fois le caractère σ ∈ Σ. Pour tout entier n compris entre 0 et 255 , Char.chr ( n : int) : char désigne le n^e caractère dans la numérotation ASCII.
◻4 - Présenter une bijection φ : ℕ → Σ^∗ entre l'ensemble des entiers naturels et l'ensemble de chaînes de caractères. En écrire le code sous la forme d'une fonction OCaml phi (n : int) : string.
Nous prétendons avoir écrit une fonction OCaml kolmogoroff (y : string) : int qui calcule la complexité de Kolmogoroff K(y).
5 - Écrire une fonction OCaml psi (m : int) : int dont la valeur de retour est l'entier
ψ(m) = min{n ∈ ℕ; K(φ(n)) ≥ m}
où φ : ℕ → Σ^∗ est la bijection définie à la question 4 et qui utilise la fonction kolmogoroff.
6 - Établir d'une part que, pour tout entier naturel m, on a
K(φ(ψ(m))) ≥ m
et d'autre part que l'on a
K(φ(ψ(m))) = O(logm).
Discuter l'existence de la fonction OCaml kolmogoroff.
Définition : Nous appelons décompresseur toute fonction OCaml d : string -> string. Pour toute chaîne de caractères y ∈ Σ^∗ et pour tout décompresseur D (noté informatiquement d), nous disons que la chaîne de caractères z est une description de la chaîne y par rapport à D si le calcul eval (d z) se termine sans erreur et a pour valeur de retour y. Nous appelons complexité de Kolmogoroff par rapport à D de la chaîne y, et notons K_D(y), la longueur de la plus courte chaîne de caractères z ∈ Σ^∗ qui décrit y par rapport à D.
7 - Dire comment se nomme en informatique un programme qui transforme un code source dans un certain langage de programmation en un code équivalent dans un second langage.
8 - Montrer que pour tout décompresseur D, il existe une constante entière c_D telle que, pour toute chaîne de caractères y, nous avons :
K(y) ≤ K_D(y) + c_D.

2 Estimation de la complexité grâce au décompresseur de Huffman

Nous fixons dans cette section la chaîne de caractères x_0 ∈ Σ^∗ suivante
"let rec te = if e = 0 then 1 else let n = e − 1 in 10∗tn in let n = t10 in tn ".
La chaîne x_0 contient 71 caractères, l'espace étant un caractère et les guillemets ne faisant pas partie de la chaîne.
9 - Inférer le type OCaml de l'expression dénotée par la chaîne de caractères x_0.
◻10 - Déterminer la valeur de l'évaluation de x0 en tant que code source OCaml.
◻11 - Signaler une ou plusieurs caractéristiques du code source x_0 qui rend la lecture de ce code impénétrable par un humain.
Indication OCaml : Nous rappelons le détail de quelques fonctions du module OCaml Hashtbl permettant de manipuler des dictionnaires (ou tableaux associatifs) mutables.
  • Hashtbl.create ( n : int) : ('a, 'b) Hashtbl.t crée un dictionnaire vide de taille initiale n.
  • Hashtbl.add (d : ('a, 'b) Hashtbl.t) (k : 'a) (v : 'b) : unit ajoute une association entre la clé k et la valeur v au dictionnaire d.
  • Hashtbl.find_opt (d : ('a, 'b) Hashtbl.t) ( k : ^′ a ) : ' b option vaut Some v sile dictionnaire d possède une association entre la clé k et la valeur v et vaut None sinon.
    ◻12 - Écrire une fonction OCaml count (x : string) : (char, int) Hashtbl.t dont la valeur de retour est un dictionnaire qui associe chaque caractère σ ∈ Σ présent dans la chaîne x à son nombre d'occurrences |x|_σ.
Nous exécutons count x0 et obtenons le dictionnaire suivant :
'n' 't' 'i' 'l' 'l' 'c' 'f' 'h' 'r' 's' '-' '*' '0' '=' 'e' '
8 8 4 4 4 1 1 1 1 1 1 1 3 4 10 19
Nous appelons z_0 la chaîne de bits correspondant à la compression de la chaîne x_0 par l'algorithme de Huffman.
◻13 - Dessiner un arbre de Huffman associé à la chaîne de caractères x_0. Il est recommandé de placer les feuilles de gauche à droite comme dans le tableau ci-dessus.
Nous notons H le décompresseur qui utilise l'arbre de Huffman de la question 13 pour transformer une chaîne de bits z ∈ {0, 1}^∗ en un mot x ∈ Σ^∗ et renvoie la chaîne de caractères "string_of_int ( x )".
◻14 - Calculer la longueur |z_0| de la chaîne obtenue après compression de x_0. En déduire que la complexité de Kolmogoroff de y_0 = 10000… par rapport au décompresseur H vérifie
K_H(y_0) ≤ 239.

3 Interlude

Nous souhaitons écrire une fonction new_string : unit -> string ainsi spécifiée : à chaque appel, une chaîne de caractères inédite est produite. Nous offrons trois propositions de code.
let inc s = s := "a" ~ (!s); !s (* Commun aux 3 propositions *)
let new_string1 = (* Proposition 1 *)
    let seed1 = ref "#" in
    fun () -> inc seed1
let new_string2 () = (* Proposition 2 *)
    let seed2 = ref "#" in
    inc seed2
let seed3 = ref "#" (* Proposition 3 *)
let new_string3 () =
    inc seed3
15 - Analyser la portée de la variable seed1, respectivement seed2 et seed3, dans la fonction new_string1, respectivement new_string2 et new_string3.
16 - Déduire de la question 15 laquelle des trois fonctions new_string1, new_string2 et new_string3 ne respecte pas la spécification. Écrire un test qui permet de discriminer la fonction erronée.
17 - Déduire de la question 15 laquelle des deux fonctions correctes restantes est plus propice à des erreurs de manipulation. Expliquer.

4 Estimation de la complexité grâce au décompresseur de De Bruijn

Nous nous avisons que la complexité de Kolmogoroff est influencée par la verbosité et l'expressivité du langage de programmation. Dans cette section, nous tentons de réduire l'encombrement ou la facilité d'écriture dus à la syntaxe du langage OCaml en introduisant un modèle de calcul nouveau et épuré.

4.1 Construction syntaxique d'un langage

Nous présentons systématiquement les grammaires sous la forme d'un quadruplet ( N, Γ, S, Π ) où N désigne l'alphabet des symboles non terminaux, Γ l'alphabet des symboles terminaux, S ∈ N le symbole initial et Π l'ensemble des règles de production, de la forme X → γ avec X ∈ N et γ ∈ (N ∪ Γ)^∗.
Une dérivation immédiate se note α ⇒ β, avec (α, β) ∈ ((N ∪ Γ)^∗)^2, et indique qu'il existe une règle de production X → γ de Π et des décompositions α = α_1 Xα_2 et β = α_1 γα_2, avec (α_1, α_2) ∈ ((N ∪ Γ)^∗)^2. Une dérivation se note ⇒ ^∗ et indique l'existence d'une suite finie, éventuellement vide, de dérivations immédiates. Enfin, le langage engendré par une grammaire G se note L(G) et désigne l'ensemble des mots de Γ^∗ qui dérivent du symbole initial S.
Selon les questions, nous représentons les mots de Γ par le type char list ou le type string.
Soit G_0 la grammaire ( {V}, {a, b, #}, V, Π_0 ) dont les règles de production sont
Π_0 : V → aV| bV|#
18 - Exhiber une expression régulière qui dénote le langage L(G_0).
19 - Dessiner l'automate de Glushkov associé à l'expression régulière de la question 18.
20 - Écrire une fonction OCaml parseV (w : char list) : string * char list dont la spécification suit :
Pré-condition : Il existe une décomposition du mot w en w = vs avec v ∈ L(G_0) et s ∈ Σ^∗. Valeur de retour : Couple ( v, s ) où le préfixe v est représenté par le type string et s est le suffixe restant.
Effet : Une exception SyntaxError est levée si la pré-condition n'est pas satisfaite.
Soit G_1 la grammaire ({T}, {(, _−,)}, T, Π_1) dont les règles de production sont
Π_1 : T → (TT)|_−.
21 - Montrer que le langage L(G_1) est sans préfixe, c'est-à-dire qu'il n'existe pas deux mots non vides w et w^′ dans L(G_1) tels que w soit un préfixe strict de w^′. On pourra, par exemple, raisonner par induction structurelle sur le nombre de parenthèses ouvrantes et fermantes qui se trouvent dans un préfixe d'un mot de L(G_1).
◻22 - Montrer que la grammaire G_1 n'est pas ambiguë, c'est-à-dire que, pour tout mot du langage L(G_1), il n'existe qu'un seul arbre d'analyse (parfois aussi appelé arbre de dérivation) associé.
Soit G_2 la grammaire ( {T}, {var, (),, − > }, T, Π_2 ) dont les règles de production sont
Π_2 : T → var|(TT)|var − > T
23 - Montrer que la grammaire G_2 n'est pas ambiguë.
Soit finalement G la grammaire ( {T, V}, {a, b, #, (),, − > }, T, Π ) dont les règles de production sont
Π : {T → V|(TT)|V − > T; V → 0aV| bV|#.
Nous admettons que la grammaire G n'est pas ambiguë. Nous appelons variable les mots de L(G_0). Informellement, la grammaire G engendre un langage L(G) qui permet de parler de variables, d'applications d'une expression à une autre et de fonctions. Il nous reste à en construire une sémantique, ce qui sera fait en section 4.3.
Afin de représenter l'arbre d'analyse d'un mot du langage L(G), nous introduisons le type ada par la déclaration suivante.
type ada = V of string | A of (ada * ada) | F of (string * ada)
Nous notons 𝒜 l'ensemble des arbres d'analyse.
Le constructeur OCaml V représente les dérivations immédiates de règle T → V et introduit les feuilles; le constructeur OCaml A représente les dérivations immédiates de règle T → (TT) et introduit des nœuds internes d'arité 2 ; le constructeur OCaml F représente les dérivations immédiates de règle V − > T et introduit des nœuds internes d'arité 1 . Les dérivations à partir du symbole non terminal V sont directement représentées par une valeur OCaml de type string.
Par exemple, le mot
((aaa#->aab#->aab#(aba#->aba#abb#))(baa#->bab#bba#)) }\mp@code{(}\mathcal{G}
admet pour arbre d'analyse :

24 - Écrire une fonction OCaml parseT (w : char list) : ada * char list dont la spécification suit :
Pré-condition : Il existe une décomposition du motw en w = ps avec p ∈ L(G) et s ∈ Σ^∗, dans laquelle le préfixe p est de longueur maximale.
Valeur de retour : Couple ( a, s ) où a est l'arbre d'analyse de p et s est le suffixe restant.
Effet : Une exception SyntaxError est levée si la pré-condition n'est pas satisfaite.
Indication OCaml : Nous supposons définie une fonction charlist_of_string : string -> char list qui transforme une chaîne de caractères en une liste de caractères.
◻25 - Écrire une fonction OCaml parse ( w : string) : ada dont la valeur de retour est l'unique arbre d'analyse de w quand w ∈ L(G) et qui lève une exception SyntaxError sinon.
Définition : Pour tout entier naturel n ∈ ℕ, nous définissons le mot
⌈n⌉ = b# − > a# − > (b#(b#…(b#(b#a#))…)) ∈ L(G)
où la variable b# apparaît n fois à droite des deux symboles ->.
◻26 - Indiquer s'il existe un automate fini capable de reconnaître le langage {⌈n⌉; n ∈ ℕ} et justifier la réponse.
Définition : Plus généralement, nous disons qu'un mot w de L(G) incarne un entier naturel n ∈ ℕ s'il existe deux variables distinctes v_1 et v_2 dans L(G_0) telles que w est de la forme
w = v_2 − > v_1 − > (v_2(v_2…(v_2(v_2 v_1))…)) ∈ L(G)
où la variable v_2 apparaît n fois à droite des deux symboles − >.
◻27 - Écrire une fonction OCaml int_of_ada (a : ada) : int dont la valeur de retour est l'entier naturel n incarné par a. Si un tel entier n'existe pas, une exception SyntaxError est levée.

4.2 Réécriture des variables et sérialisation

◻28 - Nommer une structure de donnée concrète efficace qui réalise le type de donnée abstrait «ensemble » lorsqu'il existe une relation d'ordre entre les objets à ranger.
Indication OCaml : Nous supposons définis un module OCaml StringSet permettant de construire des ensembles de chaînes de caractères persistants, de type StringSet.t, et des fonctions :
  • StringSet.mem : string -> StringSet.t -> bool qui teste l'appartenance d'un élément à un ensemble.
  • StringSet.remove : string -> StringSet.t -> StringSet.t qui retourne un ensemble privé d'un élément.
  • StringSet.singleton : string -> StringSet.t qui construit un singleton à partir d'un élément.
  • StringSet.union : StringSet.t -> StringSet.t -> StringSet.t qui construit l'union de deux ensembles.
Définition : L'ensemble des variables libres VL(a) ⊆ Σ^∗ d'un arbre d'analyse a ∈ 𝒜 est défini par induction structurelle avec les règles d'inférence suivantes :
  • si l'arbre d'analyse a est de la forme V v , où v ∈ L(G_0), alors VL(a) est le singleton {v};
  • si l'arbre d'analyse a est de la forme A (a1, a2), où a_1 et a_2 sont deux arbres d'analyse, alors VL(a) est la réunion VL(a_1) ∪ VL(a_2);
  • si l'arbre d'analyse a est de la forme F (v, a1), où v ∈ L(G_0) et a_1 est un arbre d'analyse, alors VL(a) est l'ensemble VL(a_1)∖{v}.
    ◻29 - Écrire une fonction OCaml free_vars (a : ada) : StringSet.t dont la valeur de retour est l'ensemble des variables libres de l'arbre d'analyse a.
Définition : Un arbre d'analyse est dit clos s'il ne contient pas de variables libres; de même, un mot de L(G) est dit clos si son arbre d'analyse est clos.
Dans un arbre d'analyse clos a ∈ 𝒜, pour toute chaîne de caractères v ∈ L(G_0), si la construction OCaml v v apparait comme feuille de l'arbre a, alors il existe un nœud interne de la forme F (v,_) parmi les ascendants de V v qui coïncide avec l'《 introduction » de la variable v.
Afin de ne plus s'encombrer avec des variables nommées par une chaîne de caractères, nous adoptons une nouvelle représentation des mots du langage L(G) sous forme d'un arbre, appelé terme de De Bruijn.
Définition : Un terme de De Bruijn s'obtient à partir d'un arbre d'analyse a ∈ 𝒜 en remplaçant toute feuille de l'arbre a, disons V v avec v ∈ L(G_0), par une feuille étiquetée par l'entier u = 1 + ℓ, où ℓ ∈ ℕ est le nombre de nœuds internes de la forme F (v',) avec v^′ ∈ L(G_0)∖{v}, qui existent entre ladite feuille V v et le plus proche nœud interne de la forme F (v,) parmi ses ascendants dans l'arbre d'analyse a.
Nous déclarons un nouveau type
type tdb = Vdb of int | Adb of ( tdb∗tdb ) | Fdb of tdb
Nous notons 𝒯 l'ensemble des termes de De Bruijn.
Voici un exemple de construction d'un terme de De Bruijn (à droite) à partir d'un arbre d'analyse (à gauche) du mot
(aa#->ab#->ba#->bb#->(ab#((bb#aa#)ab#))bb#->(bb#aa#->(bb#->bb#aa#))):
30 - Dessiner le terme de De Bruijn qui représente le mot ⌈n⌉.
31 - Écrire une fonction OCaml ada_of_tdb ( t : tdb) : ada dont la valeur de retour est un arbre d'analyse clos associé au terme de De Bruijn t.
Définition : Le codage binaire des termes de De Bruijn est l'application t ∈ 𝒯 ↦ tˆ ∈ {0, 1}^∗ définie par induction structurelle avec les règles d'inférence suivantes sur l'ensemble des termes de De Bruijn :
  • si le terme t ∈ 𝒯 est de la forme Vdb u, avec u ∈ ℕ^⋆, alors tˆ est la chaîne de caractères 11..10 (avec le symbole 1 répété u fois).
  • si le terme t ∈ 𝒯 est de la forme Adb(t1, t2), t_1 et t_2 étant deux termes de De Bruijn, alors t^ est la chaîne de caractères 01t_1^t_2^.
  • si le terme t ∈ 𝒯 est de la forme Fdb t1, où t_1 est un terme de De Bruijn, alors tˆ est la chaîne de caractères 00t_1 ˆ.
    ◻32 - Soient n un entier naturel et t le terme de De Bruijn associé au mot ⌈n⌉ et obtenu à la question 30. Calculer la longueur |t^| de la chaîne de caractères qui encode t.
    ◻33 - Vérifier que le codage binaire des termes de De Bruijn est une application injective.
    Nous utilisons le type string avec les caractères '0' et '1' afin de représenter des codages binaires de termes de De Bruijn.
34 - Écrire une fonction OCaml decode ( z : string ) : tdb dont la valeur de retour est l'unique terme de De Bruijn t tel que tˆ = z si un tel terme t existe et qui lève une exception SyntaxError sinon.
Il est possible de s'appuyer sur la fonction charlist_of_string précédemment présentée.

4.3 Interpréteur et décompresseur de De Bruijn

Dans cette sous-section, nous construisons un interpréteur, c'est-à-dire une procédure qui évalue les expressions appartenant au langage L(G) et en déduisons une nouvelle description du mot y_0 = ′1000000… " relative à un décompresseur approprié.
Naïvement, lorsque nous rencontrons un sous-mot de la forme w = (v − > w_b w_a) avec v ∈ L(G_0) et (w_a, w_b) ∈ (L(G))^2, nous aimerions que w soit équivalent à un mot tiré de w_b dont les occurrences de la variable v ont été remplacées par w_a. Autrement dit, lorsqu'un arbre d'analyse est de la forme A (F (v, b), a), nous voudrions construire un nouvel arbre à partir de b ∈ 𝒜 et dans lequel les apparitions de v ∈ L(G_0) sont devenues des sous-arbres a ∈ 𝒜.
Définition : Soient (a, b) ∈ (𝒜)^2 deux arbres d'analyse et v ∈ L(G_0) une variable. La substitution de la variable v par l'arbre a dans l'arbre b est l'application [v ← a] : 𝒜 → 𝒜 définie par induction structurelle avec les règles d'inférence suivantes :
  • Si l'arbre b ∈ 𝒜 est de la forme V v1, avec v_1 ∈ L(G_0), alors
[v ← a](b) = {a, si v = v_1; b, si v ≠ v_1
  • Si l'arbre b ∈ 𝒜 est de la forme A (b1, b2), où b_1 ∈ 𝒜 et b_2 ∈ 𝒜 sont deux arbres d'analyse, alors
[v ← a](b) = A (b1^′, b2^′)
où b_1^′ = [v ← a](b_1) et b_2^′ = [v ← a](b_2).
  • Si l'arbre b ∈ 𝒜 est de la forme F(v1, b1), avec v_1 ∈ L(G_0) et b_1 ∈ 𝒜, alors
[v ← a](b) = {b, si v = v_1; F(v1, b1^′), avec b_1^′ = [v ← a](b_1), si v ≠ v_1 et v_1 ∉ VL(a); F(v1^′, b^′)), avec b_1^′ = [v ← a]([v_1 ← v_1^′](b_1)), sinon
où v_1^′ ∈ L(G_0) est une variable inédite qui n'appartient pas à VL(a) ∪ VL(b) ∪ {v, v_1}.
Nous supposons déjà programmée une fonction new_string : unit -> string inspirée de la section 3 qui permet, si besoin, d'engendrer une variable de L(G_0) jamais encore utilisée.
35 - Écrire une fonction OCaml substitute (v : string) (by_a : ada) (in_b : ada) : ada qui substitue la variable v par l'arbre a dans l'arbre b.
Définition : La réduction en un pas est l'application ▹ : 𝒜 → 𝒜 définie par induction structuelle avec les règles d'inférence suivantes :
  • Si l'arbre a ∈ 𝒜 est de la forme V v , où v ∈ L(G_0), alors la valeur ▹(a) n'est pas définie.
  • Si l'arbre a ∈ 𝒜 est de la forme A (a1, a2), où a_1 ∈ 𝒜 et a_2 ∈ 𝒜 sont deux arbres d'analyse, alors
▹(a) = {A(a1, a2′), aveca_2^′ = ▹(a_2), si a_1 est de la forme V V; a^′, aveca^′ = [v ← a_2](a_(11)), si a_1 est de la forme F(v, a11); A(a1, a2), aveca_1^′ = ▹(a_1), sinon.
  • Si l'arbre a ∈ 𝒜 est de la forme F (v, a1), où v ∈ L(G_0) et a_1 ∈ 𝒜, alors
▹(a) = F(v, a1^′) où a_1^′ = ▹(a_1).
36 - Écrire une fonction OCaml reduce_one_step (a : ada) : ada qui implémente la réduction en un pas ▹. Lorsque reduce_one_step rencontre un cas non défini, une exception NoReduction est levée.
37 - Écrire une fonction OCaml interpret (a : ada) : ada qui applique répétitivement la réduction en un pas ▹ à l'arbre d'analyse a jusqu'à ce que la réduction ne soit plus définie. La valeur de retour est le dernier arbre d'analyse rencontré.
Nous admettons que, pour tout entier naturel non nul n, si π est le mot de L(G)
π = (a# − > ((a#a#)a#)⌈n⌉),
l'expression interpret (parse pi) produit une incarnation de l'entier n^((n^n)). Nous notons B le décompresseur défini par
let decompB (z : string) : string =
    "string_of_int }\mp@subsup{}{|}{}(int\_of_ada\sqcup(interpret\lrcorner(ada_of_tdb\lrcorner(decodeப" ^ z ^ "))))"
38 - Proposer une méthode pour obtenir une chaîne de caractères z_0, formée uniquement de 0 et de 1 , telle que decompB zo a pour valeur de retour la chaîne de caractères y_0 = "1000000 ..." et en calculer la longueur. En déduire que la complexité de Kolmogoroff de y_0 par rapport au décompresseur B vérifie
K_B(y_0) ≤ 70.

Fin de l'épreuve

Questions fréquentes

4 questions
Sur quels chapitres porte Informatique 2 Mines-Ponts MPI 2023 ?
Afficher ou masquer la section

Sur quels chapitres porte Informatique 2 Mines-Ponts MPI 2023 ?

Le sujet porte sur la complexité de Kolmogoroff : algorithmes dichotomiques, codage de Huffman, langages réguliers et algébriques, automates, parcours d'arbres et programmation en OCaml.

Le sujet Informatique 2 MPI 2023 des Mines sur la complexité de Kolmogoroff est-il difficile ?

Oui, le rapport indique explicitement que les correcteurs jugent le sujet difficile, tout en étant satisfaits du niveau d'un grand nombre de candidats qui ont su s'exprimer sur une bonne partie des 38 questions.

Quelles erreurs le jury a-t-il le plus relevées sur ce sujet ?

Un manque de rigueur dans la rédaction des preuves, un lemme de l'étoile mal énoncé, une notation de Landau mal utilisée et plusieurs erreurs récurrentes de programmation en OCaml.

Faut-il bien maîtriser OCaml pour ce sujet ?

Oui, le sujet comporte de nombreuses questions de programmation en OCaml, avec des attentes précises sur le style fonctionnel, la gestion des chaînes de caractères et le respect des spécifications des fonctions demandées.

Pas de description pour le moment