Update first chapters and new questions
Updated first chapter slides. Added new questions in the new training section.
This commit is contained in:
@ -1,30 +1,39 @@
|
||||
---
|
||||
title: Découvrir le langage - types de données avancés
|
||||
title: Découvrir le langage - collections
|
||||
author: Steve Kossouho
|
||||
---
|
||||
|
||||
# Collections de données
|
||||
|
||||
Ce chapitre va présenter 4 types de données de base, qui permettent de manipuler plusieurs informations avec une seule
|
||||
variable :
|
||||
|
||||
- Listes
|
||||
- Tuples
|
||||
- Ensembles
|
||||
- Dictionnaires
|
||||
|
||||
----
|
||||
|
||||
## Les listes
|
||||
## Listes
|
||||
|
||||
Type de collection de données : `list`{.python}. Une liste peut contenir une **séquence** d'éléments
|
||||
de n'importe quel type pris en charge par Python, y compris d'autres listes. Le contenu d'une liste est modifiable (ajouter, retirer des éléments…)
|
||||
Type de collection de données le plus utile : `list`{.python}. Un objet de type liste peut contenir une **séquence** d'éléments
|
||||
de n'importe quel type en Python, y compris d'autres listes. Le contenu d'une liste est modifiable (ajouter, retirer des éléments…)
|
||||
|
||||
```python {.numberLines}
|
||||
liste1 = [1, 2, 3, 4, 5, 6] # types cohérents
|
||||
liste2 = [1, 2, 3, "a", "b", "c"] # types divers
|
||||
liste3 = [None, None, True, False]
|
||||
liste1 = [1, 2, 3, 4, 5, 6] # éléments de types cohérents
|
||||
liste2 = [1, 2, 3, "a", "b", "c"] # pas de restriction sur les types
|
||||
liste3 = [None, None, True, False] # None n'est pas une valeur spéciale
|
||||
liste4 = [[1, 2, 3], [4, 5, 6]]
|
||||
liste5 = [] # liste vide, équivalent à list()
|
||||
liste5 = [] # liste vide. Équivalent à list()
|
||||
liste6 = [liste1] # Liste contenant 1 élément, qui est lui-même une liste
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
Il est possible de manipuler une liste facilement. On peut ajouter des éléments à la fin, à une
|
||||
position précise, retirer un élément ou encore récupérer un élément seul.
|
||||
Il est possible d'exploiter une liste facilement; on peut ajouter de nouveaux éléments à la fin, les insérer à une
|
||||
position précise, retirer un élément ou encore récupérer un élément seul. Notez que les positions ([index]{.naming})
|
||||
des éléments démarrent à zéro (`0`) et qu'une liste n'a jamais d'emplacement vide.
|
||||
|
||||
```python {.numberLines}
|
||||
liste1 = [1, 2, 3, 4, 5, 6]
|
||||
@ -34,8 +43,10 @@ liste2 = list() # Créer une nouvelle liste vide
|
||||
length = len(liste1) # Récupérer le nombre d'éléments
|
||||
position = liste1.index(3) # Renvoie l'index de la valeur 3, ou erreur si introuvable
|
||||
nine_trouvable = 9 in liste1 # Renvoie si l'élément 9 existe dans la liste
|
||||
|
||||
# Récupérer des éléments
|
||||
print(liste1[0]) # Affiche le premier élément si la liste est non vide
|
||||
|
||||
# Manipuler le contenu
|
||||
liste1.append(7) # Ajoute 7 comme nouvel élément à la fin
|
||||
liste1.insert(0, 99) # Insère 99 comme nouvel élément au tout début
|
||||
@ -64,31 +75,33 @@ if len(liste1) >= 4:
|
||||
|
||||
### Erreurs de méthodes
|
||||
|
||||
La méthode `.remove(valeur)`{.python} provoque également une erreur (`ValueError`{.python}) si
|
||||
l'élément en argument n'existe pas dans la liste. De la même façon, il peut être utile de tester
|
||||
Les méthodes `list.remove()`{.python} et `list.index()`{.python} provoquent également une erreur (`ValueError`{.python}) si
|
||||
l'élément en argument est introuvable dans la liste. De la même façon, il peut être utile de tester
|
||||
qu'un élément est présent dans la liste avant d'essayer de l'en supprimer :
|
||||
|
||||
```python {.numberLines}
|
||||
liste1 = [1, 2, 3]
|
||||
if 8 in liste1:
|
||||
liste1 = ["fred", "denis", 14, 23, 32]
|
||||
if 8 in liste1: # Tester que 8 a été trouvé dans la liste
|
||||
liste1.remove(8)
|
||||
```
|
||||
|
||||
**Question** : Qu'écririez-vous pour supprimer toutes les occurrences d'une valeur en utilisant `list.remove()`{.python} ?
|
||||
|
||||
----
|
||||
|
||||
### Indices de liste négatifs
|
||||
|
||||
Il est également possible d'accéder à des éléments de liste en utilisant des indices négatifs.
|
||||
Il est également possible d'accéder à des éléments de liste **non vide** en utilisant des indices négatifs.
|
||||
|
||||
Si la liste n'est pas vide, l'élément d'indice `-1` est le dernier élément de la liste (équivalent
|
||||
à `len(liste) - 1`{.python}), et l'élément à l'indice `-len(liste)`{.python} est le premier (
|
||||
équivalent à `0`).
|
||||
|
||||
Tout nombre inférieur génère une erreur de type `IndexError`{.python}.
|
||||
Dans ce cas, l'élément à l'index `-1` d'une liste `l` est celui à l'index `len(l) - 1`{.python}, à savoir
|
||||
le dernier élément. L'élément à l'index `-len(l)`{.python} sera celui à l'index `0`, à savoir le premier
|
||||
élément. Tout index hors de la plage disponible provoquera une erreur de type `IndexError`{.python}.
|
||||
|
||||
```python {.numberLines}
|
||||
liste1 = [1, 2, 4, 6, 9, 11]
|
||||
print(liste1[-1])
|
||||
animals = ["dog", "cat", "rabbit", "hamster", "parrot"]
|
||||
print(animals[-1]) # affiche "parrot"
|
||||
print(animals[-2]) # affiche "hamster"
|
||||
print(animals[-5]) # affiche "dog"
|
||||
```
|
||||
|
||||
----
|
||||
@ -96,7 +109,7 @@ print(liste1[-1])
|
||||
## Parcourir une liste
|
||||
|
||||
Comme on l'a vu avec la boucle `for`{.python}, utilisée avec `range()`{.python}, on peut utiliser la
|
||||
boucle `for`{.python} sur une liste :
|
||||
boucle `for`{.python} sur une liste :
|
||||
|
||||
```python {.numberLines}
|
||||
prime_numbers = [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31]
|
||||
@ -110,37 +123,28 @@ les valeurs `2`{.python}, puis `3`{.python}, puis `5`{.python}, etc.
|
||||
|
||||
----
|
||||
|
||||
## Opérations sur listes
|
||||
### Opérations sur listes
|
||||
|
||||
Récupérer des portions ou partitions de listes (slicing) :
|
||||
Récupérer des portions ou partitions de listes est possible en Python avec la syntaxe suivante (slicing).
|
||||
Le comportement de cette syntaxe est basé sur le fonctionnement de la fonction `range`{.python}.
|
||||
|
||||
```python {.numberLines}
|
||||
a = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55]
|
||||
b = a[0:5] # Index 0 à 5 non inclus. Marche aussi sur les chaînes.
|
||||
c = a[5:0] # L'index de fin est inférieur au départ, renvoie une liste vide
|
||||
d = a[:] # Renvoie une copie de toute la liste
|
||||
e = a[::-1] # Tout parcourir à l'envers
|
||||
a = [10, 15, 20, 25, 30, 35, 40, 45, 50, 55] # liste de base
|
||||
b = a[0:5] # copier dans une nouvelle liste les éléments aux index 0 à 4.
|
||||
c = a[5:0] # créer une nouvelle liste, mais ne rien y copier
|
||||
d = a[:] # copier de l'extrêmité gauche à droite incluses
|
||||
e = a[::-1] # copier tous les éléments à l'envers
|
||||
f = a[::2] # Tout parcourir 2 par 2
|
||||
g = a[5:2:-1] # L'indice de début est supérieur à l'indice de fin
|
||||
h = a[2:5:-1] # Le pas est négatif, start < end, renvoie une liste vide
|
||||
```
|
||||
|
||||
Pourquoi créer des portions de listes ? Cela peut-être utile, par exemple, si
|
||||
Pourquoi créer des portions de listes ? Cela peut-être utile, par exemple, si
|
||||
vous souhaitez appliquer un calcul sur une petite partition d'un jeu de données.
|
||||
|
||||
----
|
||||
|
||||
## Autres collections
|
||||
|
||||
Outre les listes, il existe 3 types de base pour collectionner des éléments :
|
||||
|
||||
- Tuple : `tuple()`{.python}
|
||||
- Set (jeu d'éléments uniques) : `set()`{.python}
|
||||
- Dictionnaires (association) : `dict()`{.python}
|
||||
|
||||
----
|
||||
|
||||
### Tuples
|
||||
## Tuples
|
||||
|
||||
**Tuple** : Un `tuple`{.python} fonctionne quasiment trait pour trait comme une liste, à la différence qu'une
|
||||
fois que vous avez défini ses éléments, vous ne pouvez plus en changer. On parle de « collection immuable ».
|
||||
@ -157,7 +161,7 @@ ce type : [opérations des tuples](https://docs.python.org/3/library/stdtypes.h
|
||||
|
||||
----
|
||||
|
||||
### Ensembles (données uniques)
|
||||
## Ensembles (données uniques)
|
||||
|
||||
**Set** (ensemble) : Un `set`{.python} peut contenir plusieurs éléments et est modifiable au même titre
|
||||
qu'une liste, mais n'est pas une séquence; aucune notion d'ordre ou d'index.
|
||||
@ -168,11 +172,14 @@ dans votre objet.
|
||||
a = {1, 1, 2, 3, 3, 3} # équivaut à {1, 2, 3}, les valeurs étant uniques à la fin
|
||||
b = {2, 4, 5, "hello", (1, 2, 3)}
|
||||
c = set() # obligatoire pour déclarer un set vide, sinon considéré dict
|
||||
|
||||
print(len(b))
|
||||
|
||||
a.add(9.5) # ajoute la valeur 9.5 (float) au set
|
||||
a.discard(9.5) # retire la valeur 9.5, ou ne fait rien
|
||||
d = a.intersection(b) # renvoie un set avec les éléments communs à a et b
|
||||
e = a.union(b) # avec les éléments présents dans a ou b
|
||||
|
||||
d = a.intersection(b) # récupérer un ensemble avec les éléments communs à a et b
|
||||
e = a.union(b) # récupérer un ensemble avec les éléments présents dans a ou b
|
||||
...
|
||||
```
|
||||
|
||||
@ -184,18 +191,18 @@ e = a.union(b) # avec les éléments présents dans a ou b
|
||||
|
||||
----
|
||||
|
||||
#### Bonus : Particularités des ensembles
|
||||
### Bonus : Particularités des ensembles
|
||||
|
||||
Les ensembles sont des structures de données très efficaces. Techniquement, vous pouvez attendre une
|
||||
complexité en `O(1)` pour que Python sache si un élément est déjà présent ou pas; cela signifie qu'il
|
||||
faut plus ou moins le même nombre d'opérations pour trouver un élément, que votre `set`{.python} en possède un
|
||||
faut plus ou moins le même nombre d'opérations pour trouver un élément, que votre `set`{.python} en contienne un
|
||||
seul ou un milliard.
|
||||
|
||||
L'algorithme et la structure de données interne à Python derrière cette efficacité se nomme **Table de hachage**.
|
||||
L'algorithme et la structure de données interne à Python derrière cette efficacité se nomme [Table de hachage]{.naming}.
|
||||
|
||||
----
|
||||
|
||||
##### Tables de hachage
|
||||
#### Tables de hachage
|
||||
|
||||
Une table de hachage fonctionne en trois temps :
|
||||
|
||||
@ -206,13 +213,13 @@ Une table de hachage fonctionne en trois temps :
|
||||
|
||||
----
|
||||
|
||||
##### Exemple de hachage
|
||||
#### Exemple de hachage
|
||||
|
||||

|
||||

|
||||
|
||||
----
|
||||
|
||||
##### Hachage
|
||||
#### Hachage
|
||||
|
||||
La signature est un nombre calculé qui est sensé dépendre du contenu d'un objet. Python propose une fonction
|
||||
à cet effet, `hash()`{.python}, pour laquelle chaque classe propose sa propre implémentation.
|
||||
@ -223,7 +230,7 @@ il n'y a pas de collision.
|
||||
|
||||
----
|
||||
|
||||
##### Hachage en général dans Python
|
||||
#### Hachage en général dans Python
|
||||
|
||||
Entre deux lancements d'un interpréteur Python, le hash de la majorité des objets est différent;
|
||||
l'algorithme utilise une valeur aléatoire pour générer les hashs, de façon qu'il soit impossible de créer un
|
||||
@ -238,7 +245,7 @@ Le hachage est en général imprévisible pour les valeurs suivantes :
|
||||
|
||||
----
|
||||
|
||||
##### Hashes prévisibles
|
||||
#### Hashes prévisibles
|
||||
|
||||
Le hash est par contre prévisible pour les valeurs suivantes :
|
||||
|
||||
@ -255,7 +262,7 @@ Les objets `int`{.python} ont généralement un `hash()`{.python} identique, sau
|
||||
|
||||
----
|
||||
|
||||
##### Hashes impossibles
|
||||
#### Hashes impossibles
|
||||
|
||||
L'algorithme de la table de hachage nécessite qu'un objet stocké dans un bucket possède en
|
||||
permanence un `hash` correspondant à son bucket. Cela pose un problème avec les objets modifiables
|
||||
@ -263,50 +270,57 @@ permanence un `hash` correspondant à son bucket. Cela pose un problème avec le
|
||||
|
||||
La solution adoptée par Python consiste à interdire le calcul de signature desdits objets.
|
||||
Les `tuple`{.python} demeurent des données valides, puisque nous avons la garantie de ne jamais pouvoir
|
||||
changer leur contenu, et ainsi leur signature.
|
||||
changer leur contenu, et par là-même leur signature.
|
||||
|
||||
```python {.numberLines}
|
||||
# La fonction hash est disponible par défaut en Python
|
||||
print(hash([1, 2, 3])) # Provoque une exception
|
||||
print(hash({1, 2, 3})) # Même problème
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### Dictionnaires : associations entre clés et valeurs
|
||||
## Dictionnaires : associations entre clés et valeurs
|
||||
|
||||
**Dictionnaires** : (`dict`{.python}) C'est un **ensemble** d'associations où l'on définit clés et valeurs. C'est le même
|
||||
fonctionnement qu'un dictionnaire lexicographique, où, lorsque vous avez le
|
||||
mot (la clé), vous retrouvez la définition s'il y en a une (la valeur). D'autres langages ont des structures
|
||||
similaires et appellent ça des `HashMap`{.java} ou des `object`{.javascript}.
|
||||
**Dictionnaires** : (`dict`{.python}) C'est un **ensemble** d'associations où l'on définit [clés]{.naming} et [valeurs]{.naming}.
|
||||
C'est le même fonctionnement qu'un dictionnaire lexicographique, où, lorsque vous avez le
|
||||
mot (la clé), vous retrouvez la définition s'il y en a une (la valeur).
|
||||
D'autres langages ont des structures similaires et appellent ça des `HashMap`{.java} ou des `object`{.javascript}.
|
||||
|
||||
```python {.numberLines}
|
||||
a = {"server1": "192.168.1.2", "server2": "192.168.1.3", "server3": "192.168.1.5"} # serveurs et adresses IP
|
||||
b = {8: "Mme Garnier", 10: "M. Dubois", 11: "Mlle Yousfi", 12: "Mme Préjean"} # rendez-vous horaires
|
||||
d = {1.2: "flottant", True: "booléen", None: "rien", (1, 2, 3): "tuple"} # clés de plusieurs types
|
||||
c = {} # ou dict(), ceci est un dictionnaire vide
|
||||
|
||||
print(a["server1"]) # affiche "192.168.1.2"
|
||||
print(b[10]) # affiche "M. Dubois"
|
||||
print(b[9]) # provoque une erreur
|
||||
```
|
||||
|
||||
Les dictionnaires ont les mêmes contraintes que les ensembles uniquement au niveau de leurs clés; vous
|
||||
pouvez uniquement utiliser des valeurs de types immuables, tels que `int`{.python}, `float`{.python}, `tuple`{.python}...
|
||||
|
||||
----
|
||||
|
||||
#### Parcourir un dictionnaire
|
||||
|
||||
Il est possible de parcourir un dictionnaire avec une boucle `for`{.python}. De base, ce sont les
|
||||
clés du dictionnaire qui sont parcourues. Mais il existe des variantes assez pratiques pour
|
||||
parcourir un dictionnaire :
|
||||
Il est possible de parcourir un dictionnaire avec une boucle `for`{.python}. Dans ce cas, ce sont
|
||||
uniquement les clés du dictionnaire qui sont parcourues. Mais il existe des variantes assez pratiques pour
|
||||
parcourir un dictionnaire :
|
||||
|
||||
```python {.numberLines}
|
||||
a = {
|
||||
"Jérémy": (25, "M", "Lille"),
|
||||
"Hélène": (30, "F", "Ambérieu-en-Bugey"),
|
||||
"Gwladys": (35, "F", "Nyons"),
|
||||
profils = {
|
||||
"Jérémy": (25, "M", "Lille"),
|
||||
"Hélène": (30, "F", "Ambérieu-en-Bugey"),
|
||||
"Gwladys": (35, "F", "Nyons"),
|
||||
"Thierry": (40, "M", "Montceau-les-Mines"),
|
||||
"Carole": (45, "F", "Saint-Rémy-lès-Chevreuse"),
|
||||
}
|
||||
|
||||
for prenom in a:
|
||||
for prenom in profils:
|
||||
print(prenom) # affiche uniquement une clé
|
||||
print(a[prenom]) # retrouve la valeur associée à la clé
|
||||
print(profils[prenom]) # retrouve la valeur associée à la clé
|
||||
```
|
||||
|
||||
----
|
||||
@ -316,11 +330,12 @@ de `n` éléments, vous pouvez les dépaqueter (**unpacking**) :
|
||||
|
||||
```python {.numberLines}
|
||||
# Avancé, unpacking via la méthode `items`
|
||||
profils = {...}
|
||||
|
||||
for item in a.items(): # a.items() renvoie ((clé1, valeur1), (clé2, valeur2), …)
|
||||
for item in profils.items(): # a.items() renvoie ((clé1, valeur1), (clé2, valeur2), …)
|
||||
print(item) # est un tuple
|
||||
|
||||
for key, value in a.items():
|
||||
for key, value in profils.items():
|
||||
# on peut utiliser 2 variables de boucle si chaque élément parcouru est
|
||||
# une séquence de taille 2. Merci l'unpacking !
|
||||
print(key, value)
|
||||
@ -334,18 +349,22 @@ Pour changer la valeur associée à une clé existante, ou par la même occasion
|
||||
une nouvelle clé, il suffit d'écrire :
|
||||
|
||||
```python {.numberLines}
|
||||
a = {"Bordeaux": 250000}
|
||||
a["Marseille"] = 800000
|
||||
a["Bordeaux"] = 90
|
||||
print(a["Bordeaux"]) # Affiche 90
|
||||
populations = {"Bordeaux": 250000, "Cherbourg": 80000}
|
||||
print(populations)
|
||||
|
||||
# Modifier le contenu
|
||||
populations["Marseille"] = 800000
|
||||
populations["Bordeaux"] = 90
|
||||
print(populations["Bordeaux"]) # Affiche 90
|
||||
|
||||
# On peut supprimer du dictionnaire une association en écrivant
|
||||
del a["Marseille"] # plus de clé pour Marseille !
|
||||
print("Marseille" in a) # renvoie si la clé "Marseille" existe
|
||||
del populations["Marseille"] # plus de clé pour Marseille !
|
||||
print("Marseille" in populations) # renvoie si la clé "Marseille" existe
|
||||
```
|
||||
|
||||
----
|
||||
|
||||
### Plus : méthodes de dictionnaires
|
||||
### Extra : méthodes de dictionnaires
|
||||
|
||||
La méthode `get(key, default)`{.python} des dictionnaires renvoie la valeur associée à une clé. Si
|
||||
l'on ne passe pas de valeur pour l'argument `default`{.python}, pour une clé introuvable, la méthode
|
||||
@ -415,9 +434,9 @@ Nous n'avons pas vu grand chose sur les chaînes de caractères, mais certaines
|
||||
- [Documentation officielle sur le format des interpolations](https://docs.python.org/3/library/string.html#format-string-syntax)
|
||||
|
||||
```python {.numberLines}
|
||||
a = 19
|
||||
b = f"Le serveur qui doit être vérifié aujourd'hui est le numéro {a}"
|
||||
c = f"Formatage de la variable : {a:f}" # affiché comme flottant (6 chiffres après la virgule)
|
||||
number = 2_643
|
||||
print(f"Le serveur qui doit être vérifié aujourd'hui est le numéro {number}")
|
||||
print(f"Formatage de la variable : {number:f}") # affiché comme flottant (6 chiffres après la virgule)
|
||||
```
|
||||
|
||||
----
|
||||
@ -430,7 +449,7 @@ print(chaine.upper(), chaine.lower())
|
||||
print(chaine.center(50, " ")) # Centre la chaîne originale sur 50 caractères
|
||||
```
|
||||
|
||||
Méthodes qui renvoient une copie modifiée d'une chaîne
|
||||
Méthodes qui renvoient une **copie** modifiée d'une chaîne
|
||||
|
||||
----
|
||||
|
||||
@ -457,37 +476,4 @@ print("17 juillet" in chaine)
|
||||
|
||||
Exemple de `str.split()`{.python} et `str.join()`{.python}
|
||||
|
||||
----
|
||||
|
||||
## Bonus : Convertir des données d'un type à un autre
|
||||
|
||||
Nous avons vu, ici et là, quelques fonctions pour déclarer des valeurs de base, ou convertir des
|
||||
valeurs. En voici une liste plus complète :
|
||||
|
||||
- `bool(val)`{.python}
|
||||
- `int(val)`{.python}, `float(val)`{.python}
|
||||
- `str(val)`{.python}
|
||||
- `list(val)`{.python}, `tuple(val)`{.python}
|
||||
- `set(val)`{.python}, `dict(val)`{.python}
|
||||
|
||||
Toutes ces fonctions renvoient un nouveau booléen, entier, flottant etc. correspondant à une
|
||||
conversion de l'expression passée en argument. Cela fonctionne uniquement lorsque la conversion a du sens.
|
||||
|
||||
----
|
||||
|
||||
Appelées sans argument, ces fonctions vous renvoient `False`{.python}, `0`{.python}, une chaîne ou une
|
||||
collection vide (des valeurs considérées neutres, qui renvoient toujours `False`{.python} lorsqu'on les convertit en booléen).
|
||||
|
||||
Lorsque vous passez un argument à ces fonctions, elles vous renvoient une nouvelle valeur,
|
||||
qui est une conversion de l'argument passé vers le type
|
||||
représenté par la fonction. Ce n'est pas toujours possible d'effectuer une conversion; par exemple, il est impossible de
|
||||
convertir une liste vers un nombre flottant, ou encore de convertir la chaîne `"bonjour"`{.python} vers un nombre entier.
|
||||
|
||||
```python {.numberLines}
|
||||
converted1 = int(3.14159) # tronque le flottant / retire la partie décimale
|
||||
converted3 = float("3.14159") # comprend le texte et génère un flottant
|
||||
converted2 = list(1) # erreur
|
||||
converted4 = float("salut") # aucun caractère valide pour représenter un nombre, échoue
|
||||
```
|
||||
|
||||
|
||||
|
Reference in New Issue
Block a user