Données Tableaux

Identification

Infoforall

3 - Données en table


Opendata : en français, on dit données ouvertes. Il s'agit d'un concept : le fait que les données et l'information sont un bien commun à l'humanité et qu'il est important de pouvoir la diffuser et la partager. On trouve désormais des fichiers regroupant des données traitant de tout et de rien très facilement sur le Web.

Quelques critères permettant de qualifier une information OpenData :

  • Complète
  • Primaire
  • Opportune
  • Accessible
  • Exploitable
  • Non-discriminatoire
  • Non-propriétaire
  • Libre de droits
  • Permanente
  • Gratuite

Nous allons en parler un peu ici. Si vous voulez plus de renseignements après cette activité : article Wikipedia.

Prérequis : Dictionnaires et tableaux, tableaux dynamiques.

Evaluation ✎ : questions 04-06-08-09-10-11-12-18-19-21-22-23-24 (13 questions).

Logiciel nécessaire pour l'activité : Python 3 : Thonny, IDLE ...

Documents de cours : open document ou pdf

1 - OpenData

OpenData : à la fois mouvement philosophique et politique qui vise à diffuser l'information au plus grand nombre. Grossièrement, la notion possède les mêmes fondements que la liberté de la presse : avec une information libre d'accès, on peut lutter facilement contre :

  • les manipulations ;
  • les mensonges ;
  • les contre-vérités.

Un autre lien évident avec l'informatique ? Le monde du logiciel libre : l'accès au code-source permet savoir exactement ce fait un programme, plutôt que devoir faire confiance à l'éditeur du logiciel.

C'est exactement la même chose avec l'OpenData : si on a accès aux informations brutes, on peut analyser les chiffres plutôt que de faire confiance à l'éditeur de l'article.

Il s'agit normalement de publier de façon légale des informations en permettant aux autres de les lire, de les publier et de les exploiter. Il existe de multiples licences liées à ces publications.

Il existe une autre thématique proche de l'OpenData : la fuite de données. Celle des lanceurs d'alerte qui publient des données non libres. Ils savent qu'ils seront poursuivis et qu'ils risquent gros mais décident d'informer le monde d'une malversation ou plus généralement d'un problème. Ainsi le site Wikileaks a publié des données liées aux renseignements américains. Il ne s'agissait pas d'OpenData ici mais la diffusion de ces informations a clairement modifié le monde et la vision qu'ont les gens de la collecte d'informations.

Avec le Web, la diffusion d'informations est facile. Reste le problème de la fiabilité : publier des données ne veut pas dire que l'information soit réelle ! Le travail des journalistes n'est donc pas fini. Au contraire, il ne fait que commencer et il leur faudra désormais des outils informatiques puissants pour analyser différentes sources et fournir une probabilité de véracité.

Nous retrouvons ici Tim Berners-Lee. Le créateur du Web a défini une échelle dans l'OpenData.

Photo de Tim Berners-Lee
Tim Berners-Lee (2010) - CC BY 2.0 - Paul Charliee — originally posted to Flickr
Rang Signification
Données non filtrées ou non structurées (éventuellement dégradées), mises en ligne avec un format non standard ou sans format du tout
★★ Données structurées (données au format CSV, TSV, XML, ou issues d'un format dont la spécification est connue : PDF, fichier tableur...)
★★★ Données librement exploitables. Juridiquement (Cf. licences) et techniquement (les données ne sont pas lisibles uniquement avec un logiciel propriétaire (EXCEL ou autres)).
★★★★ Données datées, identifiées par des URL (avec date de mise à jour) afin que l'on puisse « pointer » un lien vers elles (et les retrouver éventuellement mises à jour).
★★★★★ Données liées à d'autres données graçe à des données communes, pour les contextualiser et les enrichir

Nous allons (re)voir ce que signifient tous ces termes.

1.1 Données non structurées

Une donnée non structurée est une donnée présente mais qu'un système automatisé aura bien du mal à retrouver.

Exemple

Le Marvel Universe regorge de super-héros emblématiques qui ont marqué plusieurs générations. Iron Man est un génie milliardaire en armure, tandis que Captain America représente l’incarnation du héros patriote. Scarlet Witch, avec ses pouvoirs mystiques, impressionne par sa puissance en tant que maitresse de la magie du Chaos. Spider-Man, le célèbre héros araignée, a été incarné par plusieurs personnages, notamment Peter Parker et Miles Morales, chacun apportant une vision unique du justicier masqué. Depuis des décennies, cet univers ne cesse de s'étendre avec des films, des séries et des comics captivants. Il existe également un grand nombre de sous-univers dans l'Univers, avec par rapport les X-Men. Tornade, l'une des héroïnes de X-Men, est capable de contrôler des éléments, et est une force redoutable au sein de l'équipe. De son côté, Malicia est une mutante puissante, capable d'absorber les pouvoirs et souvenirs d'autrui au simple toucher, ce qui fait d’elle une combattante aussi redoutable que tourmentée

01° Quel est le nom et l'archétype du deuxième héros décrit ? Quel est le nom et l'archétype du troisième héros décrit ? ?

...CORRECTION...

Captain America : héros patriote.

Scarlett Witch : maitresse de la magie du Chaos.

Vous venez d'utiliser un centre de traitement de l'information super performant : votre cerveau.

Pas possible pour un ordinateur. Il existe une branche de la recherche informatique qui traite de cette capacité de donner du sens à un texte brut mais c'est loin d'être facile et rapide.

La meilleur façon pour qu'un ordinateur puisse traiter cela est de lui mâcher le travail : nous allons structurer les données.

1.2 Données structurées

Définition et exemple
Une donnée structurée est une donnée présentée selon un format connu et fixe. Plusieurs données du même type partagent la même structure descriptive.

Il peut s'agir par exemple d'un tableau à deux entrées.

Exemple

Identifiant Nom Archétype 1er Apparition
0 Iron Man Génie milliardaire en armure 1963
1 Captain America Héros patriote 1940
2 Scarlet Witch Maîtresse de la magie du chaos 1964
3 Spider Man Justicier masqué 1963
4 Spider Man Justicier masqué 1963
5 Tornade Mutante capable de contrôler les éléments 1975
6 Malicia / Rogue Mutante capable d'absorber les pouvoirs 1981
Vocabulaire
Collection Attributs
Identifiant (0) Nom (1) Archétype (2) Date d'apparition (3)
Enregistrements 0 Iron Man Génie milliardaire en armure 1963
1 Captain America Héros patriote 1940
2 Scarlet Witch Maîtresse de la magie du chaos 1964
3 Spider Man Justicier masqué 1963
4 Spider Man Justicier masqué 1963
5 Tornade Mutante capable de contrôler les éléments 1975
6 Malicia / Rogue Mutante capable d'absorber les pouvoirs 1981
Les colonnes sont nommées descripteurs, attributs ou caractères selon les spécialités scientifiques.

Ici, on utilisera souvent attribut.

Toutes les données présentes dans ce tableau partagent les mêmes attributs et les fournissent dans le même ordre :
ID - Nom - Archétye - Date d'apparition.

Chaque ligne se nomme enregistrement, objet, ou individu selon la spécialité scientifique.

Ici, on utilisera souvent enregistrement.

La toute première ligne (d'indice 0), en bleu foncé n'est pas un enregistrement. Il s'agit de l'en-tête (header en anglais). Elle permet de connaître le nom des attributs.
L'ensemble des données se nomme la collection ou la tableen informatique (ou population ou relation en mathématique).
Une case (intersection d'un enregistrement et d'un attribut) peut porter le nom de champ.
Le contenu d'une case se nomme une valeur.
Résumé du vocabulaire
  • L'ensemble des données ➡ Collection
  • Une colonne ➡ Attribut
  • Une ligne ➡ Enregistrement
  • Une case ➡ Champ
  • Valeur d'une case ➡ Valeur
  • La toute première ligne ➡ En-tête

02° Quels sont les attributs de la collection précédente ? Combien y-a-t-il d'enregistrements dans cette collection ? Quelle est la valeur qui correspond à l'attribut d'indice 1 et à l'enregistrement d'indice 5 ?

...CORRECTION...

Il y a 4 attributs :

  • Indice 0 : Identifiant
  • Indice 1 : Nom
  • Indice 2 : Archétype
  • Indice 3 : Date d'apparition

Il y a 7 enregistrements d'indice 0 à 6.

La valeur d'indice 1 en attribut (colonne Nom) et d'indice 5 en enregistrement correspond à Tornade.

03° Quel est le seul attribut qui permette de distinguer les enregistrements des deux incarnations de Spider Man ?

...CORRECTION...

Il s'agit de l'identifiant. On rajoute TOUJOURS un identifiant qu'on définit soi-même. De cette façon, nous sommes certains de pouvoir distinguer deux enregistrements même si les contenus sont similaires.

✎ 04° Peut-on séparer deux individus uniquement à l'aide du nom et du prénom ? A quoi sert le numéro de sécurité sociale ?

...CORRECTION...

Nom et prénom ne sont pas suffisants. Deux personnes peuvent se nommer exactement pareil.

C'est pour cela qu'un adulte dispose de son numéro de sécurité social : il lui est unique (enfn, s'il n'a pas d'enfant de moins de 15 ans).

2 - Tableau de tableaux

Voyons comment gérer cela avec Python.

Nous allons créer des tableaux de tableaux ou des tableaux de dictionnaires.

Vous allez voir que c'est finalement assez intuitif.

Création d'enregistrements

Comme les données sont structurées, nous pourrions stocker les valeurs d'un enregistrement dans un tableau.

1 2 3
enr0 = ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'] enr1 = ['1', 'Captain America', 'Héros patriote', '1940'] enr2 = ['2', 'Scarlet Witch', 'Maîtresse de la magie du chaos', '1964']

05° Que va-t-on récupérer si on lance ceci dans la console Python après avoir placé les 3 variables précédentes en mémoire ?

>>> enr0[1] >>> enr1[1] >>> enr2[1]

...CORRECTION...

>>> enr0[1] 'Iron Man' >>> enr1[1] 'Captain America' >>> enr2[1] 'Scarlet Witch'

On récupère donc bien les valeurs stockées dans l'attribut TYPE qui correspond à l'index 1.

✎ 06° Que doit-on taper pour récupérer l'archétype de l'enregistrement 2 ?

Pour l'instant, nous avons encore 3 variables : une pour chaque enregistrement. C'est pénible.

Création de la collection

Nous allons stocker nos enregistrements-tableau dans un tableau. On obtient ainsi un tableau de tableaux.

1 2 3 4 5
enr0 = ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'] enr1 = ['1', 'Captain America', 'Héros patriote', '1940'] enr2 = ['2', 'Scarlet Witch', 'Maîtresse de la magie du chaos', '1964'] maCollec = [enr0, enr1, enr2]

07° Que va-t-on récupérer si on lance ceci dans la console Python après avoir placé les 4 variables précédentes en mémoire ?

>>> maCollec[0] >>> maCollec[1] >>> maCollec[2]

...CORRECTION...

>>> maCollec[0] ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'] >>> maCollec[1] ['1', 'Captain America', 'Héros patriote', '1940'] >>> maCollec[2] ['2', 'Scarlet Witch', 'Maîtresse de la magie du chaos', '1964']

Un élément maCollec[i] du tableau maCollec est donc un tableau.
Et si on veut lire uniquement un attribut ? Il suffit de rajouter un crochet supplémentaire derrière [i].

Exemple

>>> maCollec[0] ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'] >>> maCollec[0][0] '0' >>> maCollec[0][1] 'Iron Man' >>> maCollec[0][2] 'Génie milliardaire en armure' >>> maCollec[0][3] '1963'

Traduction en Français : maCollec[0][1] veut dire "va chercher l'élément d'indice 0 du tableau maCollec. Dans le tableau que tu récupères, va maintenant chercher l'élement d'indice 1.

On obtient ainsi cela :

maCollec[0][1] ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'][1] 'Iron Man'

✎ 08° Sur le même principe que l'exemple précédente, que va répondre l'interpréteur Python à l'instruction suivante ?

>>> maCollec[1][3]

Pour se passer des variables intermédiaires, nous allons taper ceci :

1 2 3 4 5
maCollec = [ ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'], ['1', 'Captain America', 'Héros patriote', '1940'], ['2', 'Scarlet Witch', 'Maîtresse de la magie du chaos', '1964'] ]

On ouvre un tableau et on y place des tableaux séparés par des virgules.

On pourrait créer ce tableau d'une nouvelle façon : par extension et ajouts successifs, en rajoutant au fur et à mesure les nouveaux enregistrements avec la méthode append() (to append se traduit par le verbe rajouter).

1 2 3 4
maCollec = [] maCollec.append( ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'] ) maCollec.append( ['1', 'Captain America', 'Héros patriote', '1940'] ) maCollec.append( ['2', 'Scarlet Witch', 'Maîtresse de la magie du chaos', '1964'] )

✎ 09° Utiliser ce programme. Demander dans la console le contenu de maCollec. Donner l'instruction permettant de rajouter un enregistrement de votre choix. Fournir le code à rajouter dans le programme et le résultat de la variable maCollec après modification.

Dernière question de cette sous-partie : voyons comment visualiser le contenu de notre collection proprement en utilisant les fStrings et leurs capacités à forcer l'affichage sur un nombre précis de caractères (20 ici).

✎ 10° Utiliser ce programme qui mémorise une collection et une fonction visualiser(). Taper ensuite visualiser(maCollec) dans la console pour voir le contenu de la collection d'enregistrements.

1 2 3 4 5 6 7 8 9 10
def visualiser(c:'list[list]') -> None: """affiche le contenu des enregistements e de la collection c""" for e in c: print(f"{e[1]:20}{e[2]:35}{e[3]:20}") maCollec = [] maCollec.append( ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'] ) maCollec.append( ['1', 'Captain America', 'Heros patriote', '1940'] ) maCollec.append( ['2', 'Scarlet Witch', 'Maîtresse de la magie du chaos', '1964'] )

Questions

  1. Rajouter l'appel à la fonction en fin de programme ou faire l'appel dans la console.
  2. Justifier qu'on puisse ici se passer de l'utilisation d'une boucle avec indice sur la ligne 4 (for e in c plutôt que for i in range(len(c))).
  3. Fournir le code de la même fonction mais en utilisant plutôt les indices.

...CORRECTION...

>>> visualiser(maCollec) Iron Man Génie milliardaire en armure 1963 Captain America Heros patriote 1940 Scarlet Witch Maîtresse de la magie du chaos 1964

On peut se passer des indices puisqu'on ne modifie pas les tableaux.

Pour le parcours par indices, voilà ce que cela pourrait donner :

1 2 3 4 5 6 7 8 9 10 11 12 13
def visualiser(c:'list[list]') -> None: """affiche le contenu des enregistements e de la collection c""" for i in range(len(c)): e = c[i] print(f"{e[1]:20}{e[2]:35}{e[3]:20}") maCollec = [] maCollec.append( ['0', 'Iron Man', 'Génie milliardaire en armure', '1963'] ) maCollec.append( ['1', 'Captain America', 'Heros patriote', '1940'] ) maCollec.append( ['2', 'Scarlet Witch', 'Maîtresse de la magie du chaos', '1964'] ) visualiser(maCollec)

Nous n'allons pas taper directement le contenu des tableaux, surtout s'il y a 30 millions de lignes. Dans l'une des activités Python, nous verrons comment récupérer les données directement depuis un fichier texte un peu particulier qu'on nomme fichier CSV.

3 - Tableau de tuples

Le problème du tableau de tableaux, c'est que normalement toutes les "cases" doivent contenir le même type d'éléments (que des integers, que des strings ...)

En Python, nous n'utilisons pas réellement des tableaux mais des objets de type list qui permettent d'avoir des types différents dans les cases. Mais, sinon, comment faire ?

Et bien, on pourrait utiliser un tableau de tuples.

Lors de la déclaration, la seule différence vient de la présence de parenthèses lorsqu'on fournit les enregistrements.

1 2 3 4 5
maCollec = [ ('0', 'Iron Man', 'Génie milliardaire en armure', 1963), ('1', 'Captain America', 'Héros patriote', 1940), ('2', 'Scarlet Witch', 'Maîtresse de la magie du chaos', 1964) ]

Deux intérêts à cette façon de faire :

  • On peut maintenant mettre ce qu'on veut dans les éléments, ils n'ont plus à être de même type (comme les dates qui sont maintenant des integers).
  • Les tuples de Python sont immuables : on ne pourra pas modifier involontairement les enregistrements.

4 - Tableau de dictionnaires

Le problème du tableau de tableaux ou du tableau de tuples, c'est qu'il faut connaitre le numéro d'indice de l'attribut pour le trouver. C'est un peu pénible.

On peut faire mieux avec les dictionnaires : il suffit de créer un tableau de dictionnaires dont la clé est ... le nom du descripteur (ou attribut) !

1 2 3 4 5
enr = {} enr['id'] = 0 enr['nom'] = 'Iron Man' enr['archétype'] = 'Génie milliardaire en armure' enr["date d'apparition"] = 1963

Une fois enr en mémoire, on peut alors lancer ceci dans la console :

>>> enr["Date d'apparition"] 1963

Avantages : on voit le nom de l'attribut et on peut mettre le type qu'on désire tant pour la clé que pour la valeur. Plus de risque de se tromper de numéro de colonnes.

Désavantage : il faudrait tout refaire alors que le tableau est déjà créé !

Nous allons donc automatiser les choses en créant une fonction creer_enregistrement() qui attend un tableau en paramètes et qui renvoie le dictionnaire correspondant.

✎ 11° Utiliser ce programme qui utilise la fonction creer() pour créer et renvoyer un dictionnaire créé à partir du tableau val des 4 valeurs transmises lors de l'appel.

Voici son prototype :

def creer(val:list) -> dict

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
def creer(val): """Renvoie un dictionnaire ayant les clés id, nom, archétype et date d'apparition :: param val(list) :: un enregistrement de super-héros :: return (dict) :: un dictionnaire-enregistrement avec les bonnes clés .. exemple .. >>> creer([0, 'Iron Man', 'Génie milliardaire en armure', 1963]) {'id': 0, 'nom': 'Iron Man', 'archétype': 'Génie milliardaire en armure', "Date d'apparition": 1963} """ d = {} d['id'] = val[0] d['nom'] = val[1] d['archétype'] = val[2] return d enr = creer([0,'Iron Man','Génie milliardaire en armure',1963]) print(enr)

Question

Expliquer pourquoi la fonction ne renvoie pas exactement le dictionnaire attendu par rapport à la documentation :

>>> creer([0, 'Iron Man', 'Génie milliardaire en armure', 1963]) {'id': 0, 'type': 'Iron Man', 'utilisation': 'Génie milliardaire en armure'}

...CORRECTION...

Aucune demande pour créer la clé correspondant à la date dans le code.

✎ 12° Rajouter la ligne permettant de gérer la clé "date d'apparition" dans la fonction creer().

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
def creer(val): """Renvoie un dictionnaire ayant les clés id, nom, archétype et date d'apparition :: param val(list) :: un enregistrement de super-héros :: return (dict) :: un dictionnaire-enregistrement avec les bonnes clés .. exemple .. >>> creer([0, 'Iron Man', 'Génie milliardaire en armure', 1963]) {'id': 0, 'nom': 'Iron Man', 'archétype': 'Génie milliardaire en armure', "Date d'apparition": 1963} """ d = {} d['id'] = val[0] d['nom'] = val[1] d['archétype'] = val[2] d["date d'apparition"] = val[3] return d enr = creer([0,'Iron Man','Génie milliardaire en armure',1963]) print(enr)

Maintenant que nous savons créer nos enregistrements, nous n'avons plus qu'à les rajouter progressivement par extension et ajouts successifs (avec la méthode append()) dans notre collection. Nous allons donc créer un tableau de dictionnaires.

13° Rajouter ces instructions sous votre fonction et afficher ensuite le contenu de la collection maCollec dans la console.

20 21 22 23 24
# Programme maCollec = [] maCollec.append( creer([0,'Iron Man','Génie milliardaire en armure',1963]) ) maCollec.append( creer([1,'Captain America','Héros patriote',1940]) ) maCollec.append( creer([2,'Scarlet Witch','Maîtresse de la magie du chaos',1964]) )

On rappelle que la structure d'un programme est la suivante

  • Les importations
  • Les déclarations de CONSTANTES
  • Les définitions de fonctions
  • Les instructions du programme principal

Vous obtenez donc un tableau de dictionnaires ayant tous les mêmes clés.

Nous aurions obtenu le même résultat si nous avions tapé ceci à la main :

1 2 3 4 5
maCollec = [ {'id': 0, 'nom': 'Iron Man', 'archétype': 'Génie milliardaire en armure', "date d'apparition": 1963}, {'id': 1, 'nom': 'Captain America', 'archetype': 'Héros patriote' ,"date d'apparition": 1940}, {'id': 2, 'nom': 'Scarlet Witch', 'archetype': 'Maîtresse de la magie du chaos', "date d'apparition": 1964} ]

Les clés des dictionnaires sont les attributs de notre collection.

Chacun des dictionnaires est l'un des enregistrements.

Comme les dictionnaires ont tous les mêmes clés, on dira que ce sont des p-uplets nommés (4-uplets nommés ici) : des séquences de 4 éléments ayant chacun un nom bien particulier.

14-A° Que va-t-on obtenir en tapant ceci dans la console :

>>> maCollec[2]

...CORRECTION...

On obtient l'indice 2 de notre tableau-collection, à savoir un dictionnaire.

{'id': 2, 'nom': 'Scarlet Witch', 'archétype': 'Maîtresse de la magie du chaos', "date d'apparition": 1964}

14-B° Que va-t-on obtenir en tapant ceci dans la console :

>>> maCollec[0]['nom']

...CORRECTION...

Avec maCollec[1], on obtient l'indice 0 de notre tableau, à savoir un dictionnaire.

{'id': 0, 'nom': 'Iron Man', 'archétype': 'Génie milliardaire en armure', "Date d'apparition": 1963}

Si on cherche la clé 'nom' dans ce dictionnaire, il va donc renvoyer la valeur associée à cette clé, et c'est 'Iron Man'.

5 - CSV

Vous avez vu en SNT qu'il existe des formats courants de données. Cela permet de créer des données et de les distribuer dans un format qui sera facilement utilisable par quelqu'un d'autre.

On pourra croire qu'on peut simplement créer des fichiers Python avec des tableaux de tableaux ou des tableaux de dictionnaires, mais comment faire si quelqu'un veut récupérer vos données avec un autre langage que Python ?

L'un des formats les plus répandus est le format CSV.

5 Fichier CSV

Description générale

C'est un moyen simple de transmettre des données : c'est un simple fichier-texte compréhensible par n'importe quel langage de programmation et n'importe quel tableur.

CSV veut dire Comma Separated Values : des valeurs séparées par des virgules.

Un tableur est capable d'ouvrir les fichiers CSV.

Structure des données dans le fichier CSV
  1. (option) La première ligne du fichier contient les intitulés des attributs : l'en-tête.
  2. Chaque ligne constitue un enregistement. Rappel : la fin de ligne correspond à un octet valant 10 (norme ASCII), représentée ci-dessous par , et par le string "\n" en Python.
  3. Chaque valeur est séparée des autres valeurs de l'enregistrement par une virgule.
Exemple

A titre d'exemple voici la représentation CSV d'un tableau contenant la collection ci-dessous :

Collection Descripteurs
Identifiant Type Utilisation Couleur
Enregistrements 0 Voiture Sport Rouge
1 Hélicoptère Tourisme Marron
2 Voiture Sport Rouge

Contenu du fichier vehicules.csv :

1 2 3 4
Identifiant, Type, Utilisation, Couleur 0, Voiture, Sport, Rouge 1, Hélicoptère, Tourisme, Marron 2, Voiture, Sport, Rouge

15° Aller à l'adresse Github suivante (ou faire une simple recherche dans un moteur de recherche sur CSV + Héros Marvel): CSV MARVEL GITHUB.

Vous devriez aboutir sur une page qui vous présente visuellement un ensemble données sous forme d'un texte respectant ce format :

  • un enregistrement par ligne ;
  • les attributs sont séparés par des virgules.

Télécharger le fichier en utilisant l'icone-flèche de téléchargement qui se situe en haut à gauche.

Ouvrir le fichier depuis votre explorateur : vous allez voir que c'est votre tableur qui va gérer le fichier.

Lorsqu'on vous le demande, dire que le séparateur des attributs est la VIRGULE et que les délimitateurs des strings sont les GUILLEMETS DOUBLES "".

Comment parvenir à créer une table dans Python avec cela ?

C'est le but d'une autre activité.

Nous allons le voir dans la partie Python une fois que nous aurons vu comment gérer les fichiers-texte depuis un programme Python.

6 - Space Invaders : algorithme de parcours sur des collections

Cette partie ne traite que de la gestion des données d'un futur jeu Pyxel de type "Space Invaders" que vous allez réaliser dans l'activité Créer un Programme 3 de la partie Python.

Quelques informations générales sur les données du jeu

Nous allons stocker les données du jeu dans plusieurs variables globales qui sont ou contiennent des dictionnaires. Les clés courantes sont :

  • 'x' et 'y' pour les coordonnées de l'objet;
  • 'c' pour la couleur que devra avoir l'objet à l'écran. Un entier entre 0 et 15.
  • 'pv' pour le nombre de points de vie de l'objet.

Voici les variables globales :

  • vaisseau : un dictionnaire contenant des informations sur le vaisseau du joueur : ses coordonnées x et y, les points gagnés, sa couleur et son état (True si actif, False si inactif). Exemple :
  • vaisseau = {'x':50, 'y':100, "points":0, 'c':2}

  • ennemis : une table sous forme d'un tableau de dictionnaires (list[dict]), contenant les ennemis, un dictionnaire par ennemi sous forme d'un dictionnaire par ennemi. Exemple ici avec 3 ennemis :
  • ennemis = [
        {'x':50, 'y':100, 'pv':2, 'c':6},
        {'x':75, 'y':200, 'pv':1, 'c':8},
        {'x':150, 'y':30, 'pv':2, 'c':8}
    ]

  • tirs : une table sous forme d'un tableau de dictionnaires, contenant les tirs actuellement à l'écran, un dictionnaire par tir. Exemple avec deux tirs :
  • tirs = [
        {'x':50, 'y':100, 'c':10},
        {'x':75, 'y':200, 'c':10},
    ]

  • explosions : une table sous forme d'un tableau de dictionnaires, contenant les explosions actuellement à l'écran, un dictionnaire par tir. Exemple avec une explosion :
  • explosions = [
        {'x':50, 'y':100, 'c':10, 'rayon':2, 'rayon max':4}
    ]

Commençons donc par quelques rappels sur les dictionnaires.

(Rappel) 6.1 DICTIONNAIRE : accés à une valeur avec [cle]

Accès (avec des crochets)

Pour accéder à la valeur d'une case, il faut connaître sa clé. On accède à une valeur en tapant le nom du dictionnaire suivi de la clé entre crochets.

Attention, on place des crochets pour accéder, les accolades c'est pour la déclaration.

>>> ds = {"Alice": 13, "Bob": 8, "Charlie": 12} >>> ds["Alice"] 13 >>> ds["Charlie"] 12 >>> ds["Bob"] 8 >>> ds["Bob l'éponge"] KeyError: "Bob l'éponge"

Comme vous le voyez, demander un accès avec une clé inconnue provoque une erreur.

Accès (dict) : coût CONSTANT par rapport au nombre n d'éléments.

Résumé
  • Si d est un dictionnaire,
  • Si c est une clé valide dans le dictionnaire,
  • Alors d[c] est le contenu associé à la clé c du dictionnaire d,
(Rappel) 6.2 DICTIONNAIRE : muable en Python (modification possible)

En Python, les dictionnaires sont muables (ou mutables en anglais) : on peut modifier le contenu d'une case après la création du dictionnaire.

Modification d'un couple existant

Imaginons qu'on ai oublié des points à Bob : il n'a pas 8 mais 11 finalement. Voici comment nous pourrions modifier le tableau APRES création

>>> {"Alice": 13, "Bob": 8, "Charlie": 12} >>> notes["Bob"] 8 >>> notes["Bob"] = 11 >>> notes["Bob"] 11 >>> notes {'Alice': 13, 'Bob': 11, 'Charlie': 12}

Notez bien que ce n'est pas une affectation sur le dictionnaire : l'affectation est faite sur l'un des contenus des associations clé-valeur du dictionnaire, pas sur le dictionnaire lui-même.

Accès en modification (dict) : coût CONSTANT par rapport au nombre n d'éléments.

Rajout d'un nouveau couple

On peut rajouter de la même façon un couple qui n'existe pas encore.

Imaginons un nouvel élève nommé David qui a eu 15.

>>> {"Alice": 13, "Bob": 11, "Charlie": 12} >>> notes["David"] = 15 >>> notes {'Alice': 13, 'Bob': 11, 'Charlie': 12, 'David': 15}

1/3 Lire ou modifier les ennemis

⚙ 16° Dans notre programme, un ennemi sera un vaisseau alien ennemi. Nous allons vouloir stocker ses données dans un dictionnaire, le type dict de Python.

  1. La clé 'x' fera référence à son abscisse, sa colonne à l'écran;
  2. La clé 'y' fera référence à son ordonnée, sa ligne à l'écran;
  3. La clé 'pv' fera référence à ses points de vie : 0 pour un ennemi détruit, positif sinon;
  4. La clé 'c' fera référence à son couleur, un nombre compris entre 1 et 15 dans Pyxel.
>>> ennemi = {'x':100, 'y':30, 'pv':2, 'c':6}

Questions

  1. Comment récupérer dans une variable x la valeur associée à la clé 'x' ?
  2. Comment incrémenter la valeur associée à la clé 'x' ?

...CORRECTION...

>>> ennemi = {'x':100, 'y':30, 'c':6} # question A >>> x = ennemi['x'] >>> x 100 # question B >>> ennemi['x'] = ennemi['x'] + 1 >>> ennemi {'x':101, 'y':30, 'c':6}

⚙ 17° Dans notre programme, il y aura plusieurs ennemis. Nous allons les stocker dans un tableau ennemis.

Compléter le programme ci-dessous pour qu'on parvienne à afficher la couleur de tous les ennemis qui ont une ordonnée y supérieure à 85.

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in ennemis: # Pour chaque ennemi du tableau ennemis if ...: # si cet ennemi a une ordonnée y supérieure à 80 print(...) # affiche sa couleur

Sur l'exemple donné, le programme doit donc afficher 8 et 2.

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in ennemis: # Pour chaque ennemi du tableau ennemis if ennemi['y'] > 80: # si cet ennemi a une ordonnée supérieure à 80 print(ennemi['c']) # affiche sa couleur

2/3 Rajouter un ennemi

⚙ 18° Nous allons vouloir faire apparaître régulièrement des ennemis à l'écran. Pour cela, il va falloir les rajouter dans le tableau ennemis (de type list en Python) ennemis avec un s.

Question : quelle solution du QCM permet effectivement de rajouter l'ennemi dans le tableau ennemis ? Critiquer les solutions non retenues.

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] nouvel_ennemi = {'x': 40, 'y': 10, 'c': 9, 'pv': 3} ...
  1. ennemis = ennemis + ennemi
  2. ennemis = ennemis + [ennemi]
  3. ennemis.append(ennemi)
  4. ennemi.append(ennemis)

...CORRECTION...

Il s'agit de la solution C.

  1. ennemis = ennemis + ennemi
  2. Erreur d'exécution : on tente de concaténer un type list et un type dict. Cela n'est pas autorisé car ca ne veut rien dire.

  3. ennemis = ennemis + [ennemi]
  4. L'instruction fonctionne mais crée un nouveau tableau plutôt que de modifier le tableau en place. On concaténe un tableau avec un tableau qui ne contient qu'une seule case.

  5. ennemis.append(ennemi)
  6. C'est la bonne solution : on agit sur le tableau ennemis en lui ajoutant une case à la fin.

  7. ennemi.append(ennemis)
  8. C'est faux : ennemi sans s est un dictionnaire.Ici, on demande de modifier le dictionnaire en lui 'ajoutant' le tableau des ennemis. Cela provoque une erreur d'exécution car le type dict ne dipose pas de cette méthode append().

3/3 Supprimer un ennemi

⚙ 19-A° Reste à voir comment faire disparaître les ennemis qui sortent de l'écran par exemple. Pour cela, il va falloir les supprimer du tableau ennemis à l'aide de la méthode remove(x).

Compléter le programme pour qu'il permette de supprimer du tableau les ennemis dans l'ordonnée y est supérieur à 85 par exemple.

Lancer pour vérifier : vous devriez constaté que vous n'avez pas supprimé tous les ennemis mais un seul... Nous verrons comment faire mieux dans la question 19-B.

1 2 3 4 5 6 7 8 9 10 11 12 13
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in ennemis: # Pour chaque ennemi du tableau ennemis if ...: # si cet ennemi a une ordonnée supérieure à 85 ... # Supprime l'ennemi du tableau des ennemis print(ennemis)

...CORRECTION...

1 2 3 4 5 6 7 8 9 10 11 12 13
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in ennemis: # Pour chaque ennemi du tableau ennemis if ennemi['y'] > 85: # si cet ennemi a une ordonnée supérieure à 85 ennemis.remove(ennemi) # Supprime l'ennemi du tableau des ennemis print(ennemis)

Vous devriez constater qu'on ne supprime que l'ennemi dont l'ordonnée est 110. Pas le 90... Bizarre, non ?

[{'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}]

⚙ 19-B° Le code précédent ne fait pas ce qu'on désire tout simplement à cause du principe des tableaux : lorsque vous supprimez une case, Python va décaler le contenu des cases de droite pour remplir la case devenue vide.

Ainsi, imaginons le tableau des ennemis suivants (je ne représente que l'ordonnée dans les cases) et la situation, lorsqu'on tombe sur le 110 :

X X Ici ennemis = [ {50}, {80}, {110}, {90}, {20} ]

Puisqu'on demande de le supprimer, voici ce que devient le tableau temporairement :

X X Ici ennemis = [ {50}, {80}, {90}, {20} ]

Et c'est maintenant que le problème arrive : nous avons fini de traiter cette case rouge de son point de vue : il passe donc à la case suivante :

X X X Ici ennemis = [ {50}, {80}, {90}, {20} ]

Et donc, Python zappe le 90 en passant à la case suivante...

Utiliser ce nouveau programme qui itére sur une copie de tableau initial (créée avec list(ennemis)) et pas directement sur le tableau.

1 2 3 4 5 6 7 8 9 10 11 12 13
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for ennemi in list(ennemis): # Pour chaque ennemi initialement dans le tableau ennemis if ennemi['y'] > 85: # si cet ennemi a une ordonnée supérieure à 85 ennemis.remove(ennemi) # Supprime l'ennemi du tableau des ennemis print(ennemis)

...CORRECTION...

Vous devriez constater qu'on supprime bien tous les ennemis qui vont trop loin maintenant.

[{'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}]

✔ 20° Dernier point, plus informatif qu'autre chose : on ne peut pas modifier un tableau qu'on est en train de lire par indices : en supprimant cette case, on réduit de 1 le nombre d'indices disponibles et on finira par aller en dehors des indices autorisés. Il faut donc nécessairement utiliser le parcours par valeurs.

Tester le programme ci-dessous pour vérifier qu'il NE fonctionne PAS alors qu'il est plutôt similaire à celui de la question 19 si ce n'est qu'on passe par un parcours par indices.

1 2 3 4 5 6 7 8 9 10 11
ennemis = [ {'x': 100, 'y': 50, 'c': 3, 'pv': 2}, {'x': 120, 'y': 80, 'c': 5, 'pv': 2}, {'x': 80, 'y': 110, 'c': 8, 'pv': 2}, {'x': 50, 'y': 90, 'c': 2, 'pv': 2}, {'x': 40, 'y': 20, 'c': 14, 'pv': 2}, ] for i in range(len(ennemis)): # Pour chaque indice du tableau ennemis if ennemis[i]['y'] > 85: # si cet ennemi a une ordonnée supérieure à 85 ennemis.remove(ennemis[i]) # Supprime l'ennemi du tableau des ennemis

7 - FAQ

Rien pour le moment.

Nous avons vu qu'une collection peut être :

  • un tableau de tableaux (si tous les éléments des enregistrements sont de même type)
  • un tableau de tuples (si les éléments ne sont pas de même type)
  • un tableau de dictionnaires (si vous voulez accéder aux descripteurs par leurs noms et pas avec un numéro d'index)

Activité publiée le 26 01 2020
Dernière modification : 22 03 2025
Auteur : ows. h.