X ENS Informatique Commune MP PC PSI 2023Sujet, corrigé et rapport du jury
Gestion de version de grands textes
- Programmation Python : listes, boucles, dictionnaires
- Complexité des algorithmes
- Programmation dynamique
- Graphes : plus courts chemins, algorithme de Dijkstra
Téléchargements
Présentation du sujet
Gestion de versions de textes : différentiels, distance d'édition de Levenshtein et algorithme de DijkstraAfficher ou masquer la section
Présentation du sujet
Le sujet, à traiter en Python en deux heures, porte sur la gestion des versions successives d'un texte modifié par plusieurs auteurs. Il construit d'abord des différentiels entre textes de même longueur, puis calcule la distance d'édition par programmation dynamique pour des textes de longueurs différentes, et enfin reformule ce calcul comme une recherche de plus court chemin avec l'algorithme de Dijkstra.
- 1Partie I : différentiels par positions fixesÉgalité et distance entre textes de même longueur, usage d'un dictionnaire, calcul et application d'un différentiel, inversion et annulation de modifications.
- 2Partie II : différentiels sur des positions variablesPoids d'un différentiel, matrice de Levenshtein par programmation dynamique, reconstruction d'un différentiel, détection de conflits et fusion.
- 3Partie III : calcul de différentiels par plus courts cheminsGraphe des états d'édition, utilisation de l'algorithme de Dijkstra, complexité comparée à la programmation dynamique et heuristique.
Ce qu'a observé le jury
6 erreurs relevéesSyntaxe Python et consignes ignorées · Ordre des conditions dans une boucle while · Complexités absentes ou mal justifiéesAfficher ou masquer la section
Ce qu'a observé le jury
6 erreurs relevéesLe jury relève un manque certain de préparation chez une partie des candidats, visible dès la syntaxe Python de base. Les premières questions de la partie I sont bien réussies, mais la difficulté monte à partir de la question 9, où peu de candidats se représentent correctement les structures manipulées. La partie III n'a presque pas été traitée avec succès.
Les erreurs les plus sanctionnées
- 1Syntaxe Python et consignes ignoréesQ5
Beaucoup de candidats ne connaissent pas l'opérateur != et utilisent des fonctions non autorisées comme copy, ou modifient les entrées alors que l'énoncé l'interdit.
- 2Ordre des conditions dans une boucle whileQ4
Lors d'un parcours de tableau avec condition d'arrêt, il faut tester que l'indice reste dans le tableau avant d'accéder à la case.
« while i<len(T) and condition(T[i]): et non while condition(T[i]) and i<len(T): »
- 3Complexités absentes ou mal justifiéesQ1, Q7, Q8
Complexités oubliées, fonction jugée en O(1) sans tenir compte des sous-fonctions, O(n) annoncé sans variable n, ou composition f(g(x)) comptée comme un produit au lieu d'une somme.
- 4Copie et manipulation de listesQ5, Q7
L'affectation l' = l ne copie pas la liste, et pop renvoie déjà le dernier élément : le lire juste avant est inutile.
« Donc utiliser liste(-1) juste avant liste.pop n'a pas de sens. »
- 5Dimensions de la matrice de LevenshteinQ10, Q11
Peu de candidats voient que la matrice a len(liste1)+1 lignes et len(liste2)+1 colonnes, et la reconstruction doit partir de la dernière case.
- 6Fusion de différentiels trop naïveQ13
Les réponses se contentent souvent de concaténer les deux listes, alors que chaque différentiel décale les positions de l'autre.
Ce qui a été bien réussi
- Les questions 1, 2, 6 et 8 sont simples et traitées avec très peu d'erreurs.
- La question 7, qui réutilise les fonctions précédentes, est globalement bien réussie.
- L'utilisation de base des boucles est globalement maîtrisée.
Conseils du jury
- Lire et respecter les opérations autorisées et la consigne de ne pas modifier les entrées.
- Donner systématiquement la complexité demandée, en nommant les paramètres dont elle dépend.
- Utiliser un dictionnaire quand la complexité attendue l'exige.
- Traiter les cas limites, comme une différence sur les derniers caractères.
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
Lecture du sujet en ligne
L'énoncé complet, avec les formules et les figures, sans ouvrir le PDF.Afficher ou masquer la section
Lecture du sujet en ligne
CONCOURS D'ADMISSION 2023
16h30-18h30
FILIERES MP-PC-PSI
Epreuve
INFORMATIQUE B (XELSR)
L'utilisation des calculatrices n'est pas autorisée pour cette épreuve
Gestion de versions de grands textes
- len (1) renvoie la longueur de la liste 1 , c'est-à-dire le nombre d'éléments qu'elle contient. Complexité en
O(1) . - 11 == 12 teste l'égalité des listes 11 et 12 . Complexité en
O(n) avecn le minimum de len(l1) et len(l2). - 1 [i] désigne le i-ème élément de la liste 1 , où l'indice i est compris entre 0 et len (l)-1 Complexité en
O(1) . - 1 [i:j] construit la sous-liste [1 [i], ..., l[j-1]]. Complexité en
O(j − i) . L'usage des variantes1[i : ] à la place de1[i : len(1)] , et de1[ : j] à la place de1[0 : j] est aussi autorisé. - l. append(e) modifie la liste l en lui ajoutant l'élément e en dernière position. Complexité en
O(1)(∗) . - l.pop() renvoie le dernier élément de la liste l (supposée non vide) et supprime l'occurrence de cet élément en dernière position dans la liste. Complexité en
O(1)(∗) .
Si d est un dictionnaire Python :
- {key_1: v_1, . . . , key_n: v_n} crée un nouveau dictionnaire en associant chaque valeur v_i à une clé key_i. Complexité en
O(n)(∗) . - d[key] renvoie la valeur associée à la clé key dans d et lève une erreur si la clé key n'est pas présente. Complexité en
O(1)(∗) . - d [key]
= v modifie d pour associer la valeur v à la clé key, même si la clé key n'est pas présente dans d initialement. Complexité enO(1)(∗) . - key in d teste si la clé key est présente dans d. Complexité en
O(1)(∗) .
Partie I : Différentiels par positions fixes
Exemples
>>> textes_égaux(['v', 'i', 's', 'a'], ['v', 'a', 'i', 's'])
False
>>> textes_égaux(['v', 'i', 's', 'a'], ['v', 'i', 's', 'a'])
True
>>> distance(['v', 'i', 's', 'a'], ['v', 'a', 'i', 's'])
3
>>> distance(['a', 'v', 'i', 's'], ['v', 'i', 's', 'a'])
4
>>> aucun_caractère_commun(['a', 'v', 'i', 's'], ['v', 'i', 's', 'a'])
False
>>> aucun_caractère_commun(['a', 'v', 'i', 's'], ['u', 'r', 'n', 'e'])
True

textes associés aux clés 'avant' et 'après' représentent les textes (de même longueur) de la tranche avant et après modification. Dans la suite de cette partie, on s'appuiera sur les fonctions suivantes pour manipuler cette structure.
def tranche(arg_début, arg_avant, arg_après):
return {'début': arg_début, 'avant': arg_avant, 'après': arg_après}
def début(tr):
return tr['début']
def après(tr):
return tr['après']
def avant(tr):
return tr['avant']
def fin(tr):
return début(tr) + len(après(tr))
[
tranche(3, ['g', 'r', 'a', 'n', 'd'], ['p', 'e', 't', 'i', 't']),
tranche(11, ['â', 't', 'e', 'a', 'u'], ['i', 'e', 'n', ', 'a']),
tranche(17, ['f'], ['s']),
tranche(19, ['r', 't'], ['i', 'f'])
]
- début
(tr_1) < fin(tr_1) < … < début(tr_k) < fin(tr_k) - pour tout
j ∈ [1, k] , pour touti ∈ [0, len(avant(tr_j)) − 1] , avant(tr_j)[i] ≠ après(tr_j)[i]
- si
k > 0 , alors0 ≤ début(tr_1) etfin(tr_k) ≤ n - pour tout
j ∈ [1, k] , texte_1[ début(tr_j) : fin(tr_j)] = avant(tr_j) - pour tout
j ∈ [1, k] , texte_2[ début(tr_j) : fin(tr_j)] = après(tr_j) - pour tout
i ∈ [0, n − 1] , sii ∉ ⋃_(1 ≤ j ≤ k)[ début(tr_j), fin(tr_j) − 1] , alors texte_1[i] = texte_2[i]
- les tranches sont présentées par indices de début croissants, sans se chevaucher, ni se toucher ;
- chaque tranche tr couvre un intervalle de positions [début(tr), fin(tr) - 1] sur lequel texte
_1 et texte_2 diffèrent à chaque position, et dont les sous-textes sur ces intervalles correspondent à avant(tr) pour texte_1 et après(tr) pour texte_2 .
def versionne(texte):
return {'courant' : texte, 'historique' : [] }
def courant(texte_versionné):
return texte_versionné['courant']
def remplace_courant(texte_versionné, texte):
texte_versionné['courant'] = texte
def historique(texte_versionné):
return texte_versionné['historique']
>>> texte_versionné = versionne(['a', 'v', 'i', 's'])
>>> modifie(texte_versionné, ['v', 'i', 's', 'a'])
>>> modifie(texte_versionné, ['v', 'i', 't', 'a'])
>>> modifie(texte_versionné, ['l', 'i', 's', 'a'])
>>> assert courant(texte_versionné) == ['l', 'i', 's', 'a']
>>> assert historique(texte_versionné) == [
différentiel(['a', 'v', 'i', 's'], ['v', 'i', 's', 'a']),
différentiel(['v', 'i', 's', 'a'], ['v', 'i', 't', 'a']),
différentiel(['v', 'i', 't', 'a'], ['l', 'i', 's', 'a'])
]
>>> annule(texte_versionné)
['v', 'i', 't', 'a'])
>>> annule(texte_versionné)
['v', 'i', 's', 'a'])
>>> annule(texte_versionné)
['a', 'v', 'i', 's']
Partie II : Différentiels sur des positions variables

- la clé 'début_avant' représente la position
i d'un sous-texte avant qui a été supprimé de texte_1 ; - la clé 'avant' est associée au texte avant;
- la clé 'début_après' représente la position dans texte
_2 d'un sous-texte après, qui a été ajouté à la place du sous-texte avant en positioni dans texte_1 ; - la clé 'après' est associée au texte après.
def tranche(arg_début_avant, arg_avant, arg_début_après, arg_après):
return {'début_avant': arg_début_avant,
'avant': arg_avant,
'début_après': arg_début_après,
'après': arg_après}
def début_avant(tr):
return tr['début_avant']
def début_après(tr):
return tr['début_après']
def après(tr):
return tr['après']
def avant(tr):
return tr['avant']
def fin_avant(tr):
return début_avant(tr) + len(avant(tr))
def fin_après(tr):
return début_après(tr) + len(après(tr))
- début_avant
(tr_1) ≤ fin_avant(tr_1) < … < début_avant(tr_k) ≤ fin_avant(tr_k) - début_après
(tr_1) ≤ fin_ après(tr_1) < … < début_après(tr_k) ≤ fin_après(tr_k) - pour tout
j ∈ [1, k] , aucun_caractère_commun(avant(tr_j) , après(tr_j)) = True - pour tout
j ∈ [1, k], len(avant(tr_j)) > 0 ou len(après(tr_j)) > 0
- si
k > 0 , alors0 ≤ début_avant(tr_1) etfin_ avant(tr_k) ≤ len(texte_1) - si
k > 0 , alors0 ≤ début_après(tr_1) etfin_ après(tr_k) ≤ len(texte_2) - pour tout
j ∈ [1, k] , texte_1 [début_avant(tr_j) : fin_avant(tr_j)] = avant(tr_j) - pour tout
j ∈ [1, k] , texte_2 [début_après(tr_j) : fin_après(tr_j)] = après(tr_j) - pour tout
j ∈ [1, k − 1] , les sous-textes texte 1 [fin_avant( tr_j) : début_avant( tr_(j + 1))] et texte_2[ fin_après( tr_j) : début_après( tr_(j + 1))] sont égaux - si
k = 0 alors les textes texte_1 et texte_2 sont égaux - si
k > 0 alors texte1[0 : début_avant(tr_1)] = texte_2[0 : début_après(tr_1)] ettexte_1[finavanttr_k) : len(texte_1)] = texte_2[finaprès(tr_k) : len(texte_2)]
[
tranche( 2, [], 2, [' ', 'b', 'o', 'n']),
tranche( 5, ['â', 't'], 9, ['i']),
tranche( 8, [], 11, ['n', ']),
tranche( 9, ['u'], 14, []),
tranche(11, ['f'], 15, ['s']),
tranche(13, ['r', 't'], 17, ['i', 'f'])
]
>>> poids([tranche(0, ['b'], 0, ['t', 'r', 'o', 't', 't']),
tranche(2, ['c', 'y', 'c', 'l'], 6, ['n'])])
1 1
| F | U | A | B | C | C | Y | Z | ||
| 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | |
| 1 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | |
| 2 | 3 | 2 | 1 | 2 | 3 | 4 | 5 | 6 | |
| 3 | 4 | 3 | 2 | 1 | 2 | 3 | 4 | 5 | |
| 4 | 5 | 4 | 3 | 2 | 3 | 4 | 5 | 6 | |
| 5 | 6 | 5 | 4 | 3 | 2 | 3 | 4 | 5 | |
| 6 | 7 | 6 | 5 | 4 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 7 | 6 | 5 | 4 | 5 | 6 | 7 |
>>> texte =
['l', 'e', ', 'c', 'h', 'a', 't', ', 'a', ', 's', 'o', 'i', 'f']
>>> texte1 =
['l', 'e', ', 'c', 'h', 'a', 't', ', 'a', ', 't', 'r', 'è', 's',
, ', 's', 'o', 'i', 'f']
>>> texte2 =
['l', 'e', ' ', 'c', 'h', 'i', 'e', 'n', ', 'a', ', 's', 'o', 'i', 'f']
>>> diff1 = différentiel(texte, texte1, levenshtein(texte, texte1))
>>> assert diff1 == [tranche(9, [], 9, [' ', 't', 'r', 'è', 's'])]
>>> diff2 = différentiel(texte, texte2, levenshtein(texte, texte2))
>>> assert diff2 == [tranche(5, ['a', 't'], 5, ['i', 'e', 'n'])]
Question 13. Écrire une fonction fusionne(diff1, diff2) qui renvoie un nouveau différentiel représentant la mise à jour de diff2. Il est attendu que poids(fusionne(diff1, diff2))= poids(diff2). On suppose que les deux différentiels diff1 et diff2 ne sont pas en conflit. Cette fonction devra avoir une complexité
>>> assert not conflit(diff1, diff2)
>>> print(applique(applique(texte, diff1), fusionne(diff1, diff2)))
['l', 'e', ', 'c', 'h', 'i', 'e', 'n', ', 'a', ', 't', 'r', 'è', 's', ',
's', 'o', 'i', 'f']
Partie III : Calcul de différentiels par calcul de plus courts chemins
L'existence et la pondération des arcs devra permettre d'assurer la correspondance suivante entre le graphe et la matrice de distance d'édition de texte
| b | 0 | 1 | 2 | 3 | 4 | 5 |
| i | 1 | 0 | 1 | 2 | 3 | 4 |
| e | 2 | 1 | 2 | 3 | 4 | 5 |
| n | 3 | 2 | 3 | 4 | 5 | 4 |
| 4 | 3 | 4 | 3 | 4 | 5 |

>>> texte1 = ['b', 'i', 'e', 'n']
>>> texte2 = ['b', 'o', 'n', 'n', 'e']
>>> successeurs(texte1, texte2, (2,4))
[((3, 4), ...), ((2, 5), ...), ((3, 5), ...)]
- La fonction vide() construit une file vide (de cardinal 0) en
O(1) . - La fonction est_vide(file) teste si la file file est vide en
O(1) . - La fonction extraire_min(file) supprime l'élément de priorité minimale dans la file file et le renvoie. En cas d'égalité de priorités, elle renvoie le sommet
(i, j) le plus petit pour l'ordre lexicographique^5 parmi les sommets de priorité minimale. Sa complexité est enO(log( cardinal(file))). - La fonction ajoute(file, sommet, priorité) ajoute à la file file un sommet sommet avec une priorité priorité. L'opération augmente de 1 le cardinal de la file si le sommet n'est pas déjà présent avec cette priorité. Sa complexité est en
O(log(cardinal(file))) .
def dijkstra(texte1, texte2):
entrée = (0, 0)
sortie = (len(texte1), len(texte2))
file = vide()
dist = {}
vue = {}
horloge = 0
ajoute(file, entrée, 0)
dist[entrée] = 0
while not est_vide(file):
sommet = extraire_min(file)
if not sommet in vue:
vue[sommet] = horloge
horloge +=1
if sommet == sortie:
dist_final = {sommet: dist[sommet] for sommet in vue}
return dist_final
for voisin, distance in successeurs(texte1, texte2, sommet):
d = dist[sommet] + distance
if not voisin in dist or d < dist[voisin]:
dist[voisin] = d
ajoute(file, voisin, d)
assert False
def astar(texte1, texte2):
entrée = (0, 0)
sortie = (len(texte1), len(texte2))
file = vide()
dist = {}
vue = {}
horloge = 0
ajoute(file, entrée, 0)
dist[entrée] = 0
while not est_vide(file):
sommet = extraire_min(file)
vue[sommet] = horloge
horloge += 1
if sommet == sortie:
dist_final = {sommet: dist[sommet] for sommet in vue}
return dist_final
for voisin, distance in successeurs(texte1, texte2, sommet):
d = dist[sommet] + distance
if not voisin in dist or d < dist[voisin]:
dist[voisin] = d
ajoute(file, voisin, d + h(texte1, texte2, voisin))
assert False
- On rappelle qu'une structure immuable est une structure qui n'est jamais modifiée. C'est par exemple le cas des chaînes et des tuples en Python.
- Une pile est ici implémentée par une liste Python.
- Cette distance est communément appelée distance de Levenshtein.
- Dans ce sujet, nous représenterons ces matrices par des listes de listes d'entiers.
- On rappelle que l'ordre lexicographique
≺ sur les paires est défini par (i_1, j_1 )≺(i_2, j_2) si et seulement sii_1 < i_2 ou (i_1 = i_2 etj_1 < j_2 ).
- On rappelle que l'ordre lexicographique
Questions fréquentes
4 questionsSur quoi porte le sujet d'informatique commune X ENS MP PC PSI 2023 ?Afficher ou masquer la section
Questions fréquentes
4 questionsSur quoi porte le sujet d'informatique commune X ENS MP PC PSI 2023 ?
Sur la gestion de versions de textes en Python : calcul de différentiels, distance d'édition de Levenshtein par programmation dynamique, puis plus courts chemins avec l'algorithme de Dijkstra.
Quelle est la moyenne de l'épreuve d'informatique B X 2023 en filière PC ?
Le rapport de la filière PC indique 534 copies corrigées, une moyenne de 8,98 et un écart-type de 3,59. Il ne donne pas les chiffres des filières MP et PSI.
Quelles erreurs le jury d'informatique X 2023 a-t-il relevées ?
Une syntaxe Python mal connue (opérateur !=), des consignes ignorées, des conditions de boucle while dans le mauvais ordre, des complexités absentes ou fausses et des copies de listes mal faites.
Quelles questions du sujet d'informatique X 2023 étaient les plus difficiles ?
À partir de la question 9, les structures se complexifient. Les questions 11 à 13 et la partie III sur Dijkstra ont été peu traitées, et la question 17 n'a rapporté aucun point.
Pas de description pour le moment
