Zoom sur les tableaux (listes)

AvertissementTableaux et listes

Dans ce cours, conformément au programme, nous allons employer le terme “tableau”. Ce terme désigne une structure de données abstraite en informatique. Dans le langage Python, on utilise des objets du type list pour cette structure. Cependant, les objets de type list en Python sont bien plus que des tableaux au sens du cours. Pour éviter toute confusion, notamment par rapport à d’autres structures de données qui seront abordées en terminale, nous nous efforcerons d’employer “tableau” à la place de “liste”.

AstuceDéfinition

Un tableau est une suite ordonnée d’éléments repérés par leur indice (leur position, en commençant à 0).

Dans le modèle de tableau utilisé en NSI, les éléments sont tous du même type. Python représente ces tableaux par des objets list, qui peuvent en réalité contenir des objets de types différents : [1, "a", True] est une list valide. Dans ce cours, nous n’utiliserons que des tableaux dont les éléments sont du même type.

1. Création d’un tableau et indices

Pour créer un tableau en Python, nous utilisons un objet de type list. Un tel objet est une collection ordonnée d’éléments, repérés par des indices. Les éléments d’un tableau sont séparés par des virgules et entourés de crochets.

>>> tab1 = ["a", "b", "c"] # un tableau à 3 éléments
>>> tab2 = [1] # un tableau contenant un seul élément
>>> tab3 = [[1, 2], [3, 4]] # un tableau de tableaux
>>> tab_vide = [] # un tableau vide

Les indices commencent à 0 : il ne faut pas confondre la valeur d’un élément et son indice (sa position).

indice 0 1 2
valeur “a” “b” “c”

Pour créer rapidement un tableau d’entiers, nous pouvons utiliser les fonctions list et range.

>>> tab = list(range(2, 10, 3)) # [2, 5, 8]

2. Lire et modifier un élément

La fonction len renvoie la longueur d’un tableau, c’est-à-dire le nombre de ses éléments. Les indices valides vont donc de 0 à len(tab) - 1.

On accède à un élément avec son indice, entre crochets, comme pour les chaînes de caractères. Un indice négatif compte à partir de la fin : tab[-1] est le dernier élément.

>>> tab = ["a", "b", "c"]
>>> len(tab)
3
>>> tab[1]
'b'
>>> tab[-1]
'c'
>>> tab[3]
IndexError: list index out of range

À la différence des chaînes de caractères, un tableau est un objet modifiable (on dit mutable en anglais). On peut donc réaffecter un élément d’un tableau déjà créé.

>>> tab = [1, 2, 3, 4]
>>> tab[0] = 17
>>> tab
[17, 2, 3, 4]
NoteÀ vous de prédire

Que contient tab après les trois instructions suivantes ?

tab = [5, 8, 2]
tab[1] = tab[0] + tab[2]
tab[2] = len(tab)

tab vaut [5, 7, 3].

3. Parcourir un tableau avec une boucle for

Une boucle for permet de parcourir chacun des éléments d’un tableau. On peut soit boucler sur les éléments eux-mêmes, soit sur les indices.

>>> t = [1, 2, 3, 4]
>>> for k in t:  # boucle sur les éléments
        print(k)
1
2
3
4
>>> for i in range(len(t)): # boucle sur les indices
        print(t[i])
1
2
3
4
ImportantQuel parcours choisir ?
  • Si on a seulement besoin des valeurs, on parcourt directement les éléments : for x in t.
  • Si on doit connaître ou modifier une position, on parcourt les indices : for i in range(len(t)).

Par exemple, modifier la valeur k ne change pas le tableau, alors que modifier t[i] le change :

>>> t = [1, 2, 3]
>>> for k in t:
        k = 2 * k   # ne modifie pas t
>>> t
[1, 2, 3]
>>> for i in range(len(t)):
        t[i] = 2 * t[i]   # modifie t
>>> t
[2, 4, 6]

Pour aller plus loin : la fonction enumerate permet d’obtenir en même temps l’indice et la valeur (ce n’est pas un attendu du programme).

>>> for i, x in enumerate(["a", "b", "c"]):
        print(i, x)
0 a
1 b
2 c
NoteÀ vous de prédire

Que fait ce programme ? Quelle est la valeur de s à la fin ?

t = [4, 7, 1]
s = 0
for x in t:
    s = s + x

Il calcule la somme des éléments de t : s vaut 12.

4. Construire un tableau

Avec une boucle et append

Pour ajouter les éléments un par un en fin de tableau, nous pouvons utiliser la méthode append :

multiples_de_3 = [0, 3]
multiples_de_3.append(6) # [0, 3, 6]

Pour construire un tableau avec une boucle, on part d’un tableau vide :

multiples = []
for i in range(100):
    multiples.append(3 * i)

Par compréhension

Python permet de condenser ce schéma « tableau vide + boucle + append » en une seule ligne : c’est la construction par compréhension.

multiples = [3 * i for i in range(100)]

L’instruction de base s’écrit sous la forme [expression(i) for i in objet]. Ce type de construction est très spécifique au langage Python. En voici d’autres exemples :

multiples_de_3 = [3 * i for i in range(100)]
multiples_de_6 = [2 * n for n in multiples_de_3]

Si on dispose d’une fonction f et d’un tableau d’abscisses x :

images = [f(x) for x in abscisses]

On peut aussi ajouter des conditions. Par exemple l’instruction ci-dessous est une autre façon de définir le tableau des multiples de 3 :

multiples_de_3 = [n for n in range(301) if n % 3 == 0]

qui remplace la boucle :

multiples_de_3 = []
for n in range(301):
    if n % 3 == 0:
        multiples_de_3.append(n)

5. Concaténer et répéter : les opérateurs + et *

Avec les tableaux, l’opérateur + réalise la concaténation : il « met bout à bout » deux tableaux. L’opérateur * réalise la répétition : il répète un tableau un certain nombre de fois (le deuxième opérande est un entier).

>>> [1, 2] + [3, 4, 5]    # concaténation
[1, 2, 3, 4, 5]
>>> [0] * 4               # répétition
[0, 0, 0, 0]
>>> [1, 2] * 3
[1, 2, 1, 2, 1, 2]
>>> [0] * 0
[]

Ces deux opérateurs créent un nouveau tableau et ne modifient pas les tableaux de départ :

>>> a = [1, 2]
>>> b = [3]
>>> c = a + b
>>> a
[1, 2]
>>> c
[1, 2, 3]

Pour « ajouter » un élément avec +, il faut donc réaffecter le résultat (alors que append modifie directement le tableau) :

>>> t = [1, 2]
>>> t = t + [3]    # crée un nouveau tableau, qu'on affecte à t
>>> t.append(4)    # modifie t
>>> t
[1, 2, 3, 4]

L’opérateur * est très pratique pour créer un tableau de taille donnée rempli d’une même valeur :

>>> notes = [0] * 5
>>> notes
[0, 0, 0, 0, 0]
Avertissement

Les opérandes doivent être de même nature : [1, 2] + 3 provoque une erreur (TypeError), il faut écrire [1, 2] + [3]. De même, [1, 2] * [3] n’a pas de sens.

Attention aussi : [0] * 3 est sans danger, mais nous verrons à la section 8 que [[0] * 3] * 4 est un piège !

NoteÀ vous de prédire

Quelle est la valeur de len([1, 2] * 3 + [0]) ?

[1, 2] * 3 vaut [1, 2, 1, 2, 1, 2] (6 éléments), auquel on concatène [0] : le résultat est [1, 2, 1, 2, 1, 2, 0] et sa longueur est 7.

6. Méthodes : la boîte à outils

Le type d’une variable définit les valeurs qui peuvent être affectées à cette variable ainsi que les opérateurs et les fonctions utilisables. Les fonctions propres à un type donné sont appelées des méthodes. La fonction len par exemple, s’applique aux chaînes de caractères comme aux tableaux. append est une méthode du type list.

Attention à la syntaxe : on écrit len(tab), mais tab.append(...). Le nom de la variable est suivi d’un point puis du nom de la méthode. Cette notation est issue de la programmation orientée objets qui sera abordée en terminale.

Les méthodes essentielles à maîtriser sont append et la fonction len. Les autres sont rassemblées dans la « boîte à outils » ci-dessous : il n’est pas utile de les apprendre par cœur, mais il faut savoir qu’elles existent et les retrouver dans la documentation.

>>> tab = ["a", "b", "c"]
>>> tab.insert(1, "d") # insertion à l'indice 1 de l'élément "d"
>>> tab
['a', 'd', 'b', 'c']
>>> tab.remove("b") # suppression de la première occurrence de l'élément "b"
>>> tab
['a', 'd', 'c']
>>> x = tab.pop() # supprime et retourne le dernier élément
>>> x
'c'
>>> tab
['a', 'd']
>>> tab.reverse() # renverse l'ordre des éléments
>>> tab
['d', 'a']
>>> tab.sort() # trie les éléments dans l'ordre croissant ou alphabétique
>>> tab
['a', 'd']

On peut aussi tester l’appartenance d’un élément avec in :

>>> "a" in tab
True
>>> "z" in tab
False

Toutes les méthodes ci-dessus modifient le tableau initial, contrairement à la concaténation avec + et à la répétition avec * qui créent un nouveau tableau. Une conséquence importante : ces méthodes ne renvoient rien (append, insert, reverse et sort renvoient None). L’erreur suivante est très fréquente :

>>> tab = [3, 1, 2]
>>> tab = tab.sort()   # ERREUR : sort() modifie tab et renvoie None
>>> print(tab)
None

Il est aussi possible de trier un tableau sans le modifier avec la fonction sorted, qui crée un nouveau tableau.

>>> tab = [5, 2, 7, 4]
>>> tri = sorted(tab)
>>> tab
[5, 2, 7, 4]
>>> tri
[2, 4, 5, 7]

7. Mutation, références et copies

Un tableau peut donc être modifié par une méthode. On peut aussi modifier l’un de ses éléments par affectation.

>>> tab = ["a", "b", "c"]
>>> tab[1] = "d"
>>> tab
['a', 'd', 'c']

Ceci oblige à une grande attention, en particulier lorsqu’on veut copier un tableau.

NoteÀ vous de prédire

Observons le code suivant. À votre avis, que contiendra tab2 à la fin ?

tab1 = ["a", "b", "c"]
tab2 = tab1
tab1[1] = "d"

Vérifions-le :

>>> tab1 = ["a", "b", "c"]
>>> tab2 = tab1
>>> tab1[1] = "d"
>>> tab2
['a', 'd', 'c']

Une variable ne contient pas le tableau lui-même mais une référence vers celui-ci (une « étiquette » qui pointe vers un emplacement de la mémoire). Dans cet exemple, un même tableau a deux noms : tab2 = tab1 ne copie pas le tableau, c’est un alias. Modifier le tableau par l’un des noms le modifie pour l’autre.

Copie superficielle

Pour obtenir un nouveau tableau, on peut utiliser list. Si tab1 est un tableau de nombres ou de chaînes de caractères, cela fonctionne :

>>> tab2 = list(tab1)

Mais cette copie est dite superficielle : un nouveau tableau est créé, mais ses éléments sont les mêmes objets que ceux de l’ancien tableau (on recopie les références). Lorsque les éléments sont eux-mêmes des tableaux, ils sont donc partagés.

NoteÀ vous de prédire

Que contiendra tab1 après l’exécution de ces instructions ?

tab1 = [["a", "b"], ["c", "d"]]
tab2 = list(tab1)
tab2[1][0] = "e"

Observez ensuite le schéma ci-dessous.

tab1 vaut [["a", "b"], ["e", "d"]] : les deux tableaux partagent leurs sous-tableaux.

Copie profonde

Pour copier récursivement tous les objets contenus, on utilise la fonction deepcopy de la bibliothèque copy.

>>> from copy import deepcopy
>>> tab1 = [["a", "b"], ["c", "d"]]
>>> tab2 = deepcopy(tab1)
>>> tab2[1][0] = "e"
>>> tab2
[['a', 'b'], ['e', 'd']]
>>> tab1
[['a', 'b'], ['c', 'd']]
ImportantRécapitulatif : trois façons de « copier »
Instruction Résultat
tab2 = tab1 aucune copie : un seul tableau, deux noms (alias)
tab2 = list(tab1) nouveau tableau, mais les éléments sont partagés (copie superficielle)
tab2 = deepcopy(tab1) nouveau tableau et nouveaux éléments (copie profonde)

8. Tableaux de tableaux

Voici un exemple de tableau de tableaux :

m = [[1, 3, 4], [5 ,6 ,8], [2, 1, 3], [7, 8, 15]]

Le premier élément du tableau ci-dessus est bien un tableau ([1, 3, 4]), le deuxième élément est aussi un tableau ([5, 6, 8])…

Il est souvent plus pratique de présenter ces “tableaux de tableaux” comme ci-dessous :

m = [[1, 3, 4],
     [5, 6, 8],
     [2, 1, 3],
     [7, 8, 15]]

Nous obtenons ainsi quelque chose qui ressemble beaucoup à un “objet mathématique” très utilisé : une matrice, tableau de nombres composé de lignes et de colonnes.

L’instruction m[1] retourne le deuxième élément du tableau m, c’est-à-dire le tableau [5, 6, 8].

Pour accéder au troisième élément de ce dernier tableau, il faut donc entrer l’instruction m[1][2] qui va renvoyer 8.

On accède donc aux éléments du tableau de nombres en indexant m avec le numéro de la ligne puis le numéro de la colonne (comptés à partir de zéro).

NoteÀ vous de prédire

Pour le tableau m ci-dessus, que valent len(m), len(m[0]) et m[2][1] ?

len(m) vaut 4 (nombre de lignes), len(m[0]) vaut 3 (nombre de colonnes) et m[2][1] vaut 1.

Il est possible de parcourir l’ensemble des éléments d’une matrice à l’aide d’une “double boucle for”. Le nombre de lignes est len(m) et le nombre de colonnes de la ligne i est len(m[i]) :

m = [[1, 3, 4],
     [5, 6, 8],
     [2, 1, 3],
     [7, 8, 15]]
nb_lignes = len(m)
for i in range(nb_lignes):
    nb_colonnes = len(m[i])
    for j in range(nb_colonnes):
        a = m[i][j]
        print(a)

L’exécution de ce programme donnera le résultat suivant :

1
3
4
5
6
8
2
1
3
7
8
15

Nous avons bien parcouru l’ensemble des éléments du tableau m.

Cette double boucle for est une structure complexe, mais pourtant assez répandue. N’hésitez pas à consacrer quelques minutes à son analyse. Lorsqu’on n’a pas besoin des indices, on peut aussi parcourir directement les lignes puis les éléments de chaque ligne :

for ligne in m:
    for a in ligne:
        print(a)

Un piège classique : créer un tableau 2D

Pour créer une matrice de zéros à 4 lignes et 3 colonnes, on pourrait être tenté d’utiliser la répétition * :

NoteÀ vous de prédire

Quel sera le contenu de m après ces instructions ?

m = [[0] * 3] * 4
m[0][0] = 1

Observons le résultat :

>>> m = [[0] * 3] * 4
>>> m[0][0] = 1
>>> m
[[1, 0, 0], [1, 0, 0], [1, 0, 0], [1, 0, 0]]

Les quatre lignes sont en réalité le même tableau ([[0] * 3] * 4 répète quatre fois la référence vers un unique tableau [0, 0, 0]). Modifier une case d’une ligne modifie donc toutes les lignes.

La construction correcte crée une nouvelle ligne à chaque tour de boucle, grâce à une compréhension :

>>> m = [[0 for j in range(3)] for i in range(4)]
>>> m[0][0] = 1
>>> m
[[1, 0, 0], [0, 0, 0], [0, 0, 0], [0, 0, 0]]

Ce type de tableau à deux dimensions est utilisé pour coder les images sous forme d’images matricielles. Les curieux peuvent en apprendre plus à ce sujet en étudiant la vidéo suivante proposée sur Lumni (dans laquelle le narrateur dit “liste” au lieu de “tableau”).

ImportantÀ retenir
  • Les indices d’un tableau commencent à 0 ; le dernier indice valide est len(t) - 1. Il ne faut pas confondre la valeur d’un élément et son indice.
  • On sait créer un tableau ([...], append, compréhension, [v] * n), parcourir ses éléments (for x in t ou for i in range(len(t))) et modifier un élément (t[i] = valeur).
  • + concatène et * répète : ils créent un nouveau tableau, alors que append, sort… modifient le tableau et ne renvoient rien.
  • Une list Python est mutable. Un tableau peut contenir d’autres tableaux (tableau à deux dimensions : m[i][j]).
  • b = a ne copie pas un tableau : les deux noms désignent le même objet. list(a) fait une copie superficielle, deepcopy(a) une copie profonde.
  • Pour créer un tableau 2D, on n’écrit pas [[0] * 3] * 4 mais [[0 for j in range(3)] for i in range(4)].