TP8 : dernier TP avant les écrits

Révisions de tout le programme, séance de questions, révisions de la syntaxe et des concepts clés. Basé sur le sujet Mines-Ponts MP/PC/PSI 2015.

Le sujet étudiait un système de tests automatisés pour une usine d’imprimantes.


Documentation

TP8 au Lycée Lakanal (sujet rédigé par Arnaud Basson), révisions de tout le programme, séance de questions, révisions de la syntaxe et des concepts clés.


TP8.q1()[source]

Réponse à la question I.1.1).

On a \(2^{10} = 1024\), on code les entiers de l’intervalle \([-512, 511]\) par leur reste modulo \(1024\) (il est compris entre \(0\) et \(1023\), donc son écriture binaire comporte au plus \(10\) bits). Cette méthode de codage est appelé le complément à 2 (et ce n’est bien sur pas la seule possible).

Note

On avait déjà vu cette question dans le dernier DS.

TP8.q2()[source]

Réponse à la question I.1.2).

\(10\) bits permettent de représenter \(1024\) valeurs, donc l’intervalle \([-5V, 5V]\) est discrétisé avec \(1024\) points, soit une précision de \(10/1024\), environ égale à \(0.01 V\).

Note

Remarque : soit \(\delta = 10/1024\), les valeurs discrètes des mesures sont les \(k \times \delta\), avec \(k\) entier dans \([-512, 511]\).

TP8.generateur_car()[source]

Fonction simulant la fonction de liaison avec le capteur, non demandée par l’énoncé et non fournie.

Note

Les concepts utilisés par cette fonction ne sont pas au programme de prépa, ne vous embêtez pas à la lire ou la comprendre.

TP8.car_read(nb)[source]

Fausse fonction car_read que l’énoncé utilise.

Elle fonctionne, mais ne génère pas de séquences de caractères ayant vraiment du sens. Elle permet simplement au code suivant d’être valide, et éventuellement exécuté.

  • Exemple :
>>> random.seed(0)  # Always the same examples
>>> car_read = new_sequence()
>>> sequence = car_read(36)
>>> print(sequence)
P007-159-482+023-190+567-393-0479319
TP8.new_sequence()[source]

Réinitialise le générateur de caractères car_read().

A utiliser comme ça :

>>> random.seed(0)  # Always the same examples
>>> car_read = new_sequence()
>>> sequence = car_read(36)
>>> print(sequence)
P007-159-482+023-190+567-393-0479319
>>> car_read = new_sequence()
>>> sequence = car_read(32)
>>> print(sequence)
I003+511+237-4990249P009+620+804
TP8.q3()[source]

Réponse à la question I.2.3).

car_read(4) renvoie '+004' (c’est-à-dire la chaîne formée des \(4\) caractères qui suivent les caractères déjà lus 'U003+012').

TP8.lecture_trame(x)[source]

Réponse à la question I.2.4).

Le sujet donnait la spécification de cette fonction :

  1. On lit 3 caractères pour connaître le nombre \(n\) de blocs de données,
  2. Pour chaque bloc, on lit 4 caractères pour un bloc, qu’on stocke dans L[k] (k = 0 .. n-1),
  3. A la fin, on lit les 4 derniers caractères pour trouver la somme de contrôle S.

Avertissement

Ne pas oublier dans cette fonction de faire la conversion des chaînes de caractères renvoyées par car_read() en nombres entiers.

  • Exemple :
>>> random.seed(0)  # Always the same examples
>>> car_read = new_sequence()
>>> sequence = car_read(36)
>>> print(sequence)
P007-159-482+023-190+567-393-0479319
>>> x, L, S = lecture_trame(sequence)
>>> print(L)
[-159, -482, 23, -190, 567, -393, -47]
>>> print(S)
9319
TP8.checkSum(trame, reste=10000)[source]

Réponse à la question I.2.5).

Il s’agissait simplement d’implémenter la vérification du reste de sum(L) modulo \(10000\)

(sum(L) % 10000) == S

Note

Pour des sujets comme ça, si rien n’est indique, vous avez normalement le droit d’utiliser les fonctions Python standards min(), max(), sum() (pour la somme). Attention, pas de fonction prod() pour le produit...

  • Exemple :
>>> random.seed(0)  # Always the same examples
>>> car_read = new_sequence()
>>> sequence = car_read(36)
>>> print(sequence)
P007-159-482+023-190+567-393-0479319
>>> trame = lecture_trame(sequence)
>>> print(trame)
['P007-159-482+023-190+567-393-0479319', [-159, -482, 23, -190, 567, -393, -47], 9319]
>>> print(checkSum(trame))
True
TP8.lecture_intensite()[source]

Réponse à la question I.2.7).

La boucle while True tourne indéfiniment tant qu’elle n’est pas interrompue de force (par un break ou un return, ou une exception). Ceci nous permet d’attendre l’obtention d’une trame commençant par 'I', et contenant une somme de contrôle correcte (vérifiée avec checkSum()).

TP8.q7()[source]

Réponse à la question II.1.7).

L’intégrale de \(f\) sur \([a, b]\) est approchée selon la méthode des trapèzes (avec \(n+1\) points) par la somme suivante :

\[S = \frac{h}{2} \times \sum_{k=0}^{n-1} \left( f(a + k \times h) + f(a + (k+1) \times h) \right)\]

\(h = \frac{(b-a)}{n}\) est le pas, ce qui donne après simplification :

\[S = \frac{h}{2} \times \left( f(a)/2 + f(a+h) + f(a+2h) + \dots + f(a+(n-1)h) + f(b)/2 \right).\]
TP8.trapezes(L)[source]

Réponse à la question II.1.8).

Si \(L = [a_0, a_1, \dots, a_n]\) alors l’intégrale de \(f\) sur \([0,T]\) est approchée par \(\frac{T}{n} \times (a_0/2 + a_1 + a_2 + \dots + a_{n-1} + a_n/2)\) d’après q7() (\(a_k\) étant la valeur de \(f(k T / n)\) et \(T/n\) le pas de temps).

La valeur moyenne (sur \([0, 1]\)) est donc approchée par :

\[\begin{split}\frac{1}{n} \times (a_0/2 + a_1 + a_2 + \dots + a_{n-1} + a_n/2) \\ = \frac{1}{n} \times (\mathrm{sum}(a) - a_0/2 - a_n/2) \\\end{split}\]

Note

Cette expression ne fait pas intervenir directement T, ni le pas de temps (puisque les valeurs de L sont déjà discrétisées temporellement.

Attention

Avec ces notations \(a_0, \dots, a_n\), la liste L contient n+1 valeurs, attention. Donc n = len(L) - 1 !

TP8.indicateurs(mesures)[source]

Réponse à la question II.1.9).

  1. On calcule la moyenne des mesures, avec la formule des trapèzes, par la fonction trapezes().

  2. On pouvait utiliser une boucle for ou bien une liste en compréhension pour calculer les écarts quadratique.

    ecarts_quad = [(f-moy)**2 for f in mesures]

  3. On calcule l’écart-type comme la racine carrée de la moyenne de cette liste ecarts_quad.

Note

Pour la racine carrée, comme toujours, on peut faire x ** 0.5 ou bien math.sqrt(x) ou encore np.sqrt(x) (après avoir importe math ou numpy as np).

TP8.omega = 1

omega sera considéré comme une variable globale. On prend une valeur quelconque (1).

TP8.simulation(s0, k, dt=0.002, n=500)[source]

Réponse à la question II.2.10).

Ici il fallait utiliser un schéma d’Euler, dans le cas le plus simple : linéaire d’ordre 1, pour résoudre l’équation différentielle suivante :

(1)\[\frac{\mathrm{d} s(t)}{\mathrm{d} t} = - \frac{k}{10} (s(t) - e(t))\]

Note

Le sujet suggérait d’utiliser numpy, donc on utilise np.sin() pour calculer le signal d’entrée e(t).

Rappel : si on note dt le pas de temps et t[0],...,t[n] les dates discrétisées (t[i] = i*dt), la méthode d’Euler a pour but de fournir des approximations s[i] des valeurs de la solution exacte aux instants t[i]. Ces approximations sont calculées par la relation de récurrence

s[i+1] = s[i] - dt \times f(s[i], e[i]) = s[i] - dt \times k/10 \times (s[i] - e(t[i])).

Les paramètres à passer à la fonction simulation sont :

  • la valeur initiale s0 = s(0),
  • et le coefficient k (constante dans l’équation (1)),
  • (omega sera considéré comme une variable globale).

Avertissement

Bien sur, on ne pouvait pas utiliser une fonction du module scipy (e.g. scipy.integrate.odeint()) pour calculer directement la solution de l’ED, il fallait implémenter soi-même le schéma d’Euler (les sujets le rappellent généralement).

TP8.validation(mesures, eps)[source]

Réponse à la question II.2.11).

Pour chaque k dans [0.5, 1.1, 2.0], on résout numériquement (avec simulation()), puis on teste l’écart maximal entre les valeurs mesurées (mesures[i]) et les valeurs simulées s[i].

Note

On voit qu’il fallait bien sur mettre k comme un argument de la fonction précédente simulation().

Note

Ici aussi, on peut utiliser directement la fonction de base de Python max() pour calculer cet écart minimum.

La question disait de renvoyer True si pour un des k l’écart maximal est \(\leq eps\), ou False sinon.

Note

En français, quand on ne précise pas “inférieur ou égal” ou “inférieur strict”, “inférieur” signifie <= (“inférieur ou égal”).

Avertissement

Soyez prudents, en français on utilise une virgule , pour commencer les décimales, mais en anglais (et donc en Python), c’est un point . et on utilise une virgule pour séparer les valeurs dans une liste ou un tuple. Il faut donc écrire [0.5, 1.1] et surtout pas [0,5, 1,1] ni [0,5; 1,1].

Le point-virgule ; n’est pas utilisé en Python. Il peut servir à écrire deux instructions sur la même ligne, mais il ne faut pas s’en servir...

Si vous faites aussi du caml, faites attention puisque le ; est utilise en caml pour séparer les valeurs dans une liste...

TP8.q12()[source]

Réponse SQL à la question III.12).

SELECT nSerie
FROM testfin
WHERE 0.4 <= Imoy AND Imoy <= 0.5

On peut utiliser 0.4 <= Imoy AND Imoy <= 0.5 ou Imoy BETWEEN 0.4 AND 0.5.

TP8.q13()[source]

Réponse SQL à la question III.13).

SELECT testfin.nSerie, Imoy, Iec, modele
FROM testfin JOIN production
    ON testfin.nSerie = production.nSerie
WHERE 0.4 < Imoy AND Imoy < 0.5

Ici il fallait faire la jointure (table1 JOIN table2 ON table1.attr = table2.attr2) des deux tables testfin et production (selon l’attribut nSerie).

Note

Quand attr1 = attr2, on peut utiliser le mot clé NATURAL JOIN mais il vaut mieux éviter (ce n’est pas clair de savoir si c’est au programme ou non).

TP8.q14()[source]

Réponse SQL à la question III.14).

SELECT modele, count(*) as nombreValide
FROM production
GROUP BY modele

On groupe par modèle les données de la table production (GROUP BY modele). La fonction d’agrégation COUNT permet d’obtenir l’effectif de chaque groupe.

Note

Le nommage de la deuxième colonne (as nombreValide) n’était pas requis.

TP8.q15()[source]

Réponse SQL à la question III.15).

  1. Première solution : avec une sous requête

    SELECT nSerie, fichierMes
    FROM testfin
    WHERE nSerie NOT IN
        (SELECT nSerie FROM production)
    

2. Deuxième solution : avec une différence ensembliste (EXCEPT) {imprimantes non validées} = {toutes les imprimantes} {imprimantes validées}. Subtilité : les deux tables dont on fait la différence doivent avoir exactement les mêmes attributs, ce qui oblige à faire une jointure pour ajouter les fichiers de mesures à la deuxième table (celle des imprimantes validées). Cela donne une requête plus complexe

SELECT nSerie, fichierMes
FROM testfin
EXCEPT
SELECT testfin.nSerie, fichierMes
FROM testfin JOIN production
    ON testfin.nSerie = production.nSerie
Intérêt:analyser les mesures des imprimantes non validées afin de déterminer le problème ayant empêché leur validation (en vue d’y remédier).
TP8.q16()[source]

Réponse à la question IV.1.16).

Le codage de 'EST' est 1001, et celui de 'ASE' est 1001 aussi.

Inconvénient:ce codage n’est pas injectif, donc il ne peut pas être décodé ! (Et donc, il ne sert à rien.)
TP8.q17()[source]

Réponse à la question IV.1.17).

La séquence 101101100000100101111 = 10 11 011 000 001 001 011 11 code le mot 'RETUOOTE'.

  • Ce mot de \(8\) caractères occupe donc \(8\) octets = \(64\) bits en mémoire.
  • La séquence codée occupe \(21\) bits (\(24\) si chaque caractère était sur \(3\) bits, mais un R et deux E font chacun gagner un bit).
  • Donc pour cet exemple, le taux de compression est de \(64/21\) (à peu près = \(3\)).

Mais pour que le décodage soit possible, il faut fournir non seulement le texte codé '101101100000100101111', mais aussi la correspondance entre caractères et codes ('E':11, 'R':10, etc).

Il faut donc stocker aussi cette correspondance dans le fichier compressé, ce qui dégrade le taux de compression. Néanmoins ce codage reste intéressant pour un texte long car dans ce cas la place mémoire occupée pour stocker la correspondance entre lettres et codes est négligeable devant la taille du texte compressé.

Note

Le sujet n’etait pas clair quant à savoir s’il fallait aussi transmettre la table de correspondance lors du calcul du taux de compression (sur ce message exemple 'RETUOOTE').

TP8.q18()[source]

Réponse à la question IV.1.18).

Le codage doit être injectif (deux textes distincts doivent être codés par des séquences de bits distinctes).

Voici une condition suffisante garantissant l’injectivité du code : il n’existe pas deux lettres x1 et x2 telles que le code de x1 soit un préfixe du code de x2.

Note

On dit qu’un tel codage est un codage préfixe.

Le codage de q16() ne remplit pas cette condition (le code de E est 1, préfixe du code 10 de A) et il est ambigu. Mais par contre, le codage de q17() remplit cette condition (au moins pour les six lettres qu’on nous donne).

Remarques:
  • La condition n’est pas nécessaire, par exemple le codage suivant 'A':1, 'B':10, 'C':100 ne la remplit pas, pourtant il est injectif.
  • la condition ci-dessus (codage préfixe) permet un décodage facile des séquences de bits.
TP8.est_present(x, cars)[source]

Fonction auxiliaire, qui renvoie True si x figure dans la liste cars, False sinon.

Note

En Python, on peut écrire directement x in cars. Ce n’est pas très clair de savoir si ce morceau de syntaxe est au programme ou pas.

  • Complexité en temps : en \(\mathcal{O}(n)\) ou n = len(cars).
TP8.caracteres_presents(donnees)[source]

Réponse à la question IV.2.19).

On ne demandait pas une complexité particulièrement efficace. L’implémentation proposée à une complexité en temps en \(\mathcal{O}(n c)\) ou n = len(donnees) et c est le nombre de caractères différents.

Donc dans le pire des cas, on a un \(\mathcal{O}(n^2)\) (atteint pour une liste avec n caractères distincts). (En fait, n peut devenir très grand, mais en ASCII c est <= 256. En UTF-8 ou UTF-16, il y a beaucoup plus de caractères différents mais leur nombre reste borne).

TP8.indice(x, cars)[source]

Fonction auxiliaire, qui renvoie un indice i tel que cars[i] = x et si x n’est pas présent dans cars, elle renvoie -1.

  • Elle renvoie le plus petit i tel que cars[i] = x.

Note

En Python, on peut écrire directement cars.index(i). Ce n’est pas très clair de savoir si ce morceau de syntaxe est au programme ou pas... Notez que la méthode index ne renvoie pas -1 en cas d’échec, elle renvoie une exception (ValueError).

  • Complexité en temps : en \(\mathcal{O}(n)\) ou n = len(cars).
TP8.caracteres_frequences(donnees)[source]

Réponse à la question IV.2.20).

On ne demandait pas une complexité particulièrement efficace. L’implémentation proposée a une complexité en temps en \(\mathcal{O}(n c)\) ou n = len(donnees) et c est le nombre de caractères différents.

TP8.q21()[source]

Réponse à la question IV.2.21).

Exécution de aux(cars, freq, 2, 7) : on indique les valeurs des variables après chaque itération de la boucle while (1ère ligne = état initial)

k m  cars                                     freq
7 7 [' ', 'a', 'c', 't', 'e', 'è', 's', 'r'] [4, 6, 3, 4, 8, 1, 7, 4] (ini)
6 6 [' ', 'a', 'c', 't', 'e', 'è', 's', 'r'] [4, 6, 3, 4, 8, 1, 7, 4] (1e iter)
5 6 [' ', 'a', 'c', 't', 'e', 'è', 's', 'r'] [4, 6, 3, 4, 8, 1, 7, 4] (2e iter)
4 5 [' ', 'a', 'c', 't', 'è', 'e', 's', 'r'] [4, 6, 3, 4, 1, 8, 7, 4]
3 5 [' ', 'a', 'c', 't', 'è', 'e', 's', 'r'] [4, 6, 3, 4, 1, 8, 7, 4]
2 5 [' ', 'a', 'c', 't', 'è', 'e', 's', 'r'] [4, 6, 3, 4, 1, 8, 7, 4]

Après la sortie de la boucle, on échange les éléments d’indices 5 et 7, d’où l’état final des deux tableaux : [' ', 'a', 'c', 't', 'è', 'r', 's', 'e'], [4, 6, 3, 4, 1, 4, 7, 8] et la valeur renvoyée est 5.

TP8.q22()[source]

Réponse à la question IV.2.22).

La fonction aux travaille sur le sous-tableau allant des indices i à j (inclus).

Il s’agit d’une fonction de partition (servant à effectuer un tri rapide), elle utilise comme pivot le dernier élément du sous-tableau : pivot = freq[j], et la partition se fait à partir de la droite (pas comme celle vue en TP, qui partait de la gauche).

La fonction partitionne le tableau freq[i:j+1] = [freq[i],...,freq[j]] de façon à placer à gauche du pivot les éléments <= pivot et à droite les éléments > pivot.

TP8.q22_preuve()[source]

Preuve formelle de la question IV.2.22) :

1. Terminaison : à chaque itération de la boucle while, k diminue de 1 donc la boucle termine en exactement j-i itérations (on aurait aussi bien pu faire une boucle for).

2. Correction (prouver que la fonction fait ce qu’on veut). Pour l’invariant : un dessin vaut mieux qu’un long discours

         i               k            m            j
-----------------------------------------------------------
         |      ...      |  <= pivot  |  > pivot   |p|
-----------------------------------------------------------

(p désigne le pivot, les points de suspension les éléments pas encore examinés).

A la fin de chaque itération de la boucle while, on a l’invariant suivant :

  • freq[r] <= pivot pour tout indice r tel que k <= r < m,
  • freq[r] > pivot pour tout indice r tel que m <= r < j.

2.a. Cet invariant est vrai avant d’entrer dans la boucle while (les 2 conditions sont vides lorsque k = m = j).

2.b. Cet invariant est conservé par les itérations : on décrémente k de 1 et on examine freq[k] (qui est alors l’élément le plus à droite de la zone contenant les points de suspension) : s’il est <= pivot il n’y a rien à faire; s’il est > pivot (i.e. freq[k] > freq[j]), on l’échange avec l’élément le plus à droite de la zone <= pivot : pour cela on décrémente m de 1 et on effectue l’échange des éléments d’indices k et m. Ainsi l’invariant est maintenu. Victoire.

2.c. En sortie de la boucle while, on a k = i, et on effectue l’échange du pivot avec l’élément d’indice m, afin de mettre le pivot à sa place définitive entre les 2 zones

         i                m                         j
-----------------------------------------------------------
         |    <= pivot    |p|        > pivot        |
-----------------------------------------------------------

La fonction aux a donc bien réalisé la partition du sous tableau [freq[i],...,freq[j]] autour du pivot p.

Quant au tableau cars, il est modifié en même temps que freq : chaque fois qu’on échange deux éléments de freq, on échange les deux mêmes éléments de cars, afin de conserver la propriété (I) de correspondance entre les deux tableaux :

  1. pour tout indice r, freq[r] est le nombre d’occurrences de cars[r] dans le texte initial.
TP8.q23()[source]

Réponse à la question IV.2.23).

L’instruction suivante effectue le tri souhaité

tri(cars, freq, 0, len(cars)-1)

La fonction tri effectue le tri rapide (“quicksort”) à l’aide de la fonction de partition aux() (le tableau freq est trié en ordre croissant et le tableau cars est modifié au fur et à mesure afin de conserver la correspondance entre les deux tableaux, l’invariant (I) utilisé dans la fonction précédente).

La complexité dans le pire cas du tri rapide est \(\mathcal{O(n^2)}\), où n est la longueur des tableaux considérés (mais en moyenne il est bien meilleur, en \(\mathcal{O(n \log(n))}\)).

Le tri par fusion a une complexité plus faible dans le pire cas : \(\mathcal{O(n \log(n))}\).

TP8.insertion(t, q, cars_ponderes)[source]

Réponse à la question IV.3.24).

On peut utiliser cars_ponderes.insert(i, [t, q]) pour insérer la petite liste [t, q] à la position i, trouvée d’après ce que demande l’énoncé

i = argmin cars_ponderes[i][1] < q, i >= 0
TP8.numero(x)[source]

On avait besoin de cette fonction auxiliaire, qui donne le numéro (un entier) associe au caractère x. On utilise la fonction Python ord() (fournie par défaut).

  • Exemples :
>>> print(numero('a'))
0
>>> print(numero('z'))
25
TP8.codage(cars, freq)[source]

Réponse à la question IV.3.25).

On peut vérifier l’exemple de l’énoncé :

>>> cars = ['f','b','d','a','c','e']
>>> freq = [1, 2, 3, 4, 4, 9]
>>> print(codage(cars, freq))
['110', '1001', '111', '101', '0', '1000']
TP8.q26()[source]

Réponse à la question IV.4.26).

C’était assez direct, une fois qu’on comprend ce qu’est l’ordre lexicographique :

‘0’ < ‘1000’ < ‘1001’ < ‘101’ < ‘110’ < ‘111’

On peut vérifier avec Python :

>>> tab = ['110', '1001', '111', '101', '0', '1000']
>>> print(sorted(tab))
['0', '1000', '1001', '101', '110', '111']
TP8.q27()[source]

Réponse à la question IV.4.27). Décodage de txt_bin = '010100000011011101111011110'.

Voici le découpage

txt_bin = '010' + '10' + '000' + '001' + '10' + '1110' + '1111' + '011' + '110'

À savoir :

  • pos = 0, m = 2, code '010', lettre 'b',
  • pos = 3, m = 4, code '10', lettre 'o',
  • pos = 5, m = 0, code '000', lettre 'n',
  • pos = 8, m = 1, code '001', lettre 'j',
  • pos = 11, m = 4, code '10', lettre 'o',
  • pos = 13, m = 6, code '1110', lettre 'u',
  • pos = 17, m = 7, code '1111', lettre 'r',
  • pos = 21, m = 3, code '011', lettre ' ' (espace),
  • pos = 24, m = 5, code '110', lettre '!',

Le texte ayant été compressé était donc 'bonjour !'.

TP8.decode_car(txt_bin, pos, code)[source]

Réponse à la question IV.4.24).

C’est exactement une recherche dichotomique dans un tableau trié (le tableau code), sauf qu’au lieu de manipuler des nombres ordonnés dans l’ordre usuel on manipule des chaînes de caractères, ordonnées par ordre lexicographique.

Subtilité technique : les codes ayant des longueurs variables, au moment de tester si code[m] est présent dans la chaîne txt_bin à la position pos, il faut extraire de txt_bin une sous_chaine de longueur adéquate (la variable longueur sert à cela, cf. le code).

Le nombre de comparaisons de chaînes est du même ordre de grandeur que le nombre d’itérations de la boucle while (il y a une ou deux comparaisons à chaque itération, dans le if et le elif), donc c’est en \(\mathcal{O}(\log n)\)n = len(code).

Rappel de la preuve : À chaque itération on travaille dans le sous-tableau code[g:d+1] i.e. [code[g], ..., code[d]], de longueur d-g+1; initialement la est n, et à chaque itération cette longueur est au moins divisée par \(2\); dans le pire cas le nombre \(p\) d’itérations vérifie \(n \simeq 2^p\), soit \(p \simeq \log(n)\).

TP8.decode_txt(txt_bin, code, cars)[source]

Réponse à la question IV.4.29).

La dernière question n’était pas trop dure, en suivant à la lettre les consignes.

  • Exemple :
>>> txt_bin = '010100000011011101111011110'
>>> code = ['000', '001', '010', '011', '10', '110', '1110', '1111']
>>> cars = ['n', 'j', 'b', ' ', 'o', '!', 'u', 'r']
>>> decode_txt(txt_bin,code,cars)  # On trouve comme au dessus
'bonjour !'

Sortie du script

$ python TP8.py


I. Reception des donnees.


II. Analyse des mesures.


III. Bases de donnees.


IV. Compression d'un fichier texte.
Test automatique de toutes les doctests ecrites dans la documentation (docstring) de chaque fonction :

Plus de details sur ces doctests peut etre trouve dans la documentation de Python:
https://docs.python.org/3/library/doctest.html (en anglais)

Le fichier Python se trouve ici : TP8.py.