Cours

Qu’est-ce qu’un tableau #

Un tableau est un type de donnée permettant de stocker plusieurs valeurs de manière séquentielle et d’y accéder à l’aide d’une seule variable.

Le moyen le plus simple de créer un tableau en python est d’énumérer l’ensemble de ses valeurs:

>>> table = [1, 0, 5, -2, 4,]
>>> table
[1, 0, 5, -2, 4]

On vient ici de créer une variable table qui est un tableau (toute énumération de valeurs entre crochets [ ] sera interprétée comme un tableau par python).

Voyez ce qu’il se passe en sous-main à l’aide de python tutor (cliquez sur ce lien !):

définition d’un tableau

  • On voit très clairement qu’une zone de mémoire a été réservée pour stocker les 5 données de notre tableau. Cette zone est pointée par la variable table.
  • Cette représentation est extrêmement importante: en soit, une variable ne contient pas de valeur en python, elle est une référence (on dit un pointeur en informatique) vers une zone de la mémoire dans laquelle la ou les valeurs sont stockées. Cela aura une grande importance dans quelques temps, lorsque ce réseau de pointeurs sera plus compliqué.
  • Comme on peut le constater, les valeurs d’un tableau sont indexées à partir de 0: la première valeur aura pour indice 0, la seconde aura pour indice 1, etc.

Pour accéder à la valeur d’indice i, on utilise la syntaxe table[i]:

>>> table[0]
1
>>> table[1]
0
>>> table[2]
5
>>> table[3]
-2
>>> table[4]
5

Notons que le dernier élément du tableau a pour indice 4 alors que le tableau contient 5 éléments. C’est tout à fait normal: de 0 à 4, il y a exactement 5 valeurs.

On peut connaître le nombre d’éléments d’un tableau à l’aide de la fonction len():

>>> len(table)
5

Attention, il n’est pas possible d’accéder à un élément dont l’indice serait supérieur au dernier indice, c’est-à-dire à la longeur moins 1. En particulier, table[len(table)] n’existe pas et provoquera une erreur.

Point très important: Le langage python autorise à stocker dans un tableau des données de type quelconque, sans aucune cohérence: on peut mettre des entiers, des nombres à virgule flottante, des chaînes de caractères… dans un même tableau.

>>> mélange = ["pomme", 12, 3.14, "pêche"]
>>> mélange
["pomme", 12, 3.14, "pêche"]

Cependant, la plupart des autres langages n’autorisent pas ce genre de lattitude: un tableau ne peut contenir qu’un type unique de données.

Afin d’éviter la prise de mauvaises habitudes, on s’astreindra à partir de maintenant à ne jamais utiliser de tableau contenant des données mixtes.

Parcours d’un tableau #

Il n’est pas envisageable d’énumérer à la main tous les éléments d’un tableau comme nous avons pu le faire dans un exemple précédent, d’autant plus que le nombre d’éléments du tableau peut fort bien varier au cours de la durée de vie d’un programme.

On utilisera avantageusement une boucle (pour ou tant que) afin d’accéder à l’ensemble des éléments d’un tableau.

Plusieurs cas de figure se présentent, selon que l’on souhaite connaître l’indice d’un élément donné ou non.

Si on n’a pas besoin de connaître l’indice d’un élément #

Dans ce cas, la boucle pour est de loin la plus adaptée. La syntaxe de cette boucle est d’ailleurs exactement conçue pour accéder aux éléments d’un tableau:

prénoms = ["pascal", "pierre", "bérénice", "isabelle", "mathieu", "joachim", "anabelle"]
for p in prénoms:
  print(p)

Le résultat sera:

pascal
pierre
bérénice
isabelle
mathieu
joachim
anabelle

Si on a besoin de connaître l’indice de chaque élément #

Dans ce cas il existe plusieurs méthodes. On peut réutiliser la boucle précédente et compter les indices à la main:

i = 0
for p in prénoms:
    print("Le prénom d'incice", i, "est", p)
    i = i + 1
Le prénom d'incice 0 est pascal
Le prénom d'incice 1 est pierre
Le prénom d'incice 2 est bérénice
Le prénom d'incice 3 est isabelle
Le prénom d'incice 4 est mathieu
Le prénom d'incice 5 est joachim
Le prénom d'incice 6 est anabelle

Notons qu’il est indispensable d’incrémenter à la main le compteur.

On peut aussi procéder autrement, en faisant boucler directement sur l’indice i plutôt que sur l’élément p. Il faudra dans ce cas utiliser la syntaxe suivante:

for i in range(len(prénoms)):
    print("Le prénom d'indice", i, "est", prénoms[i])
Le prénom d'indice 0 est pascal
Le prénom d'indice 1 est pierre
Le prénom d'indice 2 est bérénice
Le prénom d'indice 3 est isabelle
Le prénom d'indice 4 est mathieu
Le prénom d'indice 5 est joachim
Le prénom d'indice 6 est anabelle

Le résultat est exactement le même, mais l’algorithme sous-jacent bien différent: on utiliser la fonction range() avec pour paramètre la longueur len(prénoms) du tableau: la variable i parcourera donc bien les entiers de 0 à len(prénoms) - 1. Pour accéder à l’élément du tableau correspondant, on utilise alors la syntaxe prénoms[i] vue dans la partie précédente.

Notons qu’il n’est pas nécessaire ici d’incrémenter le compteur, c’est la boucle pour qui s’en charge.

On pourrait aussi remplacer celle-ci par une boucle tant que:

i = 0
while i < len(prénoms):
    print("Le prénom d'indice", i, "est", prénoms[i])
    i = i + 1
Le prénom d'indice 0 est pascal
Le prénom d'indice 1 est pierre
Le prénom d'indice 2 est bérénice
Le prénom d'indice 3 est isabelle
Le prénom d'indice 4 est mathieu
Le prénom d'indice 5 est joachim
Le prénom d'indice 6 est anabelle

Cela paraît plus compliqué parce qu’on est à nouveau obligé d’incrémenter le compteur, mais la boucle tant que offre d’autres avantages: on peut utiliser une condition plus compliquée, arrêter le parcours du tableau avant la fin (par exemple lorsque l’on aura trouvé un élément que l’on recherche). Nous verrons cela en exercice.

Modifier le contenu d’un tableau #

En python, un tableau n’est pas une structure figée en mémoire. On peut notamment:

  • ajouter ou supprimer des éléments, à la fin, au début, voire même au milieu d’un tableau
  • modifier une des valeurs stockée dans le tableau (ce qui revient en général à la remplacer par une autre).

Nous ne verrons pas toutes ces possibilités dans un premier temps, mais examinons les plus courantes.

Modifier un élément d’un tableau #

Pour modifier un élément dans un tableau, on utilise la syntaxe suivante:

>>> prénoms
['pascal', 'pierre', 'bérénice', 'isabelle', 'mathieu', 'joachim', 'anabelle']
>>> prénoms[1] = 'archibald'
>>> prénoms
['pascal', 'archibald', 'bérénice', 'isabelle', 'mathieu', 'joachim', 'anabelle']

On constate que le contenu du tableau a bien été modifié: son deuxième élément (donc d’indice 1) a été remplacé par un autre. On dit en informatique qu’un tableau est une donnée variable, c’est-à-dire que son contenu peut être modifié en mémoire (ce n’est pas toujours le cas, comme nous le verrons plus tard, il y a aussi en python des données immuables comme par exemple les chaînes de caractères).

Ajouter un ou plusieurs éléments à la fin d’un tableau #

L’ajout d’élements n’est pas la même chose que la modification d’un élément vue au paragraphe précédent: le contenu d’une des cases mémoire était simplement remplacé par un autre, alors qu’ici nous voulons modifier la taille du tableau. Python est extrêmement souple sur ce point, et autorise tous les changements de taille, tant qu’il reste de la mémoire disponible.

Pour ajouter un unique élément à un tableau, on peut utiliser la fonction append(élément) appliquée directement au tableau, comme suit:

>>> prénoms
['pascal', 'pierre', 'bérénice', 'isabelle', 'mathieu', 'joachim', 'anabelle']
>>> prénoms.append('maximilien')
>>> prénoms
['pascal', 'pierre', 'bérénice', 'isabelle', 'mathieu', 'joachim', 'anabelle', 'maximilien']

On constate que la fonction append(donnée) ne renvoie aucune valeur significative. Par contre elle agit bien sur le tableau, et en modifie la structure. On dit que append est une fonction qui possède un effet de bord (c’est-à-dire que son but n’est pas de renvoyer une valeur, mais d’affecter son environnement, ici la mémoire de l’ordinateur).

Pour supprimer le dernier élément d’un tableau, il y a la fonction pop() (sans arguments): celle-ci supprime effectivement l’élément de plus grand indice, et renvoie sa valeur. Attention, pop() utilisé sur un tableau vide [] renverra une erreur puisqu’il n’y a pas d’élément à supprimer.

>>> prénoms.pop()
'maximilien'
>>> prénoms
['pascal', 'pierre', 'bérénice', 'isabelle', 'mathieu', 'joachim', 'anabelle']

Nous constatons que pop() a bien renvoyé la valeur du dernier élément du tableau, et l’a effacé de celui-ci: c’est une fonction à effet de bord, qui renvoie en outre une valeur intéressante.

Concaténation de deux tableaux #

On a souvent besoin de prendre deux tableaux et de les concaténer, c’est-à-dire les mettre bout à bout. Il existe deux manière de procéder en python.

Supposons que l’on ait deux tableaux:

>>> t1 = [1, 2, 3, 4]
>>> t2 = [5, 6, 7, 8]

L’opération + entre deux tableaux ne réalise pas une addition (cela n’aurait aucun sens), mais crée un nouveau tableau contenant la concaténation des deux premiers. Il est important de remarquer qu’aucun des deux tableaux initiaux n’est modifié par cette opération.

>>> t1 + t2
[1, 2, 3, 4, 5, 6, 7, 8]
>>> t1
[1, 2, 3, 4]
>>> t2
[5, 6, 7, 8]

Observez ces manipulations basiques à l’aide de python tutor.

Si on ne veut pas perdre immédiatement le résultat de t1 + t2, il est tout à fait possible de le stocker dans une nouvelle variable, voire même dans une variable existante. Par exemple, on peut vouloir remplacer t1 par la concaténation de t1 et t2, comme suit:

>>> t1 = t1 + t2
>>> t1
[1, 2, 3, 4, 5, 6, 7, 8]
>>> t2
[5, 6, 7, 8]

Observez cette différence fondamentale de comportement par rapport à l’exécution dynamique précédente: python tutor.

Ajout d’un tableau au bout d’un autre #

Il est possible de réaliser directement l’opération précédente à l’aide de la fonction extend(tableau):

>>> t1 = [1, 2, 3, 4]
>>> t2 = [5, 6, 7, 8]
>>> t1.extend(t2)
>>> t1
[1, 2, 3, 4, 5, 6, 7, 8]
>>> t2
[5, 6, 7, 8]

Le résultat semble être le même que pour la syntaxe t1 = t1 + t2 précédente, mais ce n’est pas exactement le cas: extend() va modifier le tableau t1 pour lui rajouter directement les éléments de t2 (un peu comme si on avait utilisé append pour chacun des éléments de t2 sur t1). En revanche, t1 = t1 + t2 crée d’abord un nouveau tableau, qui est ensuite stocké dans t1. L’ancien tableau qui était dans t1 n’étant plus référencé par aucune variable, il sera effacé de la mémoire par python dès que possible.

Pour ce rendre compte de cette différence fondamentale, examinons une situation où une autre variable pointe sur même tableau que t1:

Avec une concaténation #

Déroulé avec python tutor lorsque l’on utilise la concaténation par l’opérateur +.

Après avoir créé la variable copie_t1 pointant sur le même tableau que t1, on observe très clairement le partage des données en mémoire (il est très courant que plusieurs variables pointent sur une même valeur, mais comme nous allons le voir, ce n’est pas sans dangers).

Après avoir exécuté la ligne t1 = t1 + t2, la variable t1 pointe bien sur la concaténation des deux tableaux, mais la variable copie_t1 est restée sur sa valeur initiale (c’est probablement ce que l’on souhaitait obtenir de toutes façons):

Avec extend #

Déroulé avec python tutor lorsque l’on utilise extend.

Avant l’exécution d'extend, la situation est 100% identique à la précédente:

Par contre, la fonction extend modifie le tableau pointé par t1 (et donc aussi par copie_t1) en place, et ce sans modifier aucun pointeur. On aboutit donc à la situation suivante:

La variable copie_t1 a elle aussi été affectée par extend, et ce n’est probablement pas l’effet recherché par le programmeur.

À retenir: Lorsque l’on modifie des variables en place (c’est-à-dire avec un effet de bord), cela peut potentiellement engendrer des erreurs dans le programme dès lors que des données sont partagées, ce qui est extrêmement courant.

Création de tableaux de taille arbitraire #

On peut vouloir créer un tableau contenant par exemple 100 éléments, initialement tous à zéro. Il existe déjà un moyen de faire cela en l’état de nos connaissances: utiliser un tableau initialement vide, et lui rajouter 100 fois l’élément 0 à l’aide de append

t = []
for i in range(100):
    t.append(0)

Le tableau t contient alors les valeurs suivantes:

'[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0]'

Il existe cependant une syntaxe spéciale en python qui permet de réaliser cela: l’opération * appliqué entre un tableau et un nombre (dans cet ordre) va dupliquer le tableau autant de fois qu’indiqué par le nombre.

>>> t = [0] * 25
>>> repr(t)
'[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]'

Note: La fonction repr permet d’afficher une valeur telle que l’on aurait dû la taper pour la saisir en python (cela évite notamment que le tableau soit affiché sur 100 lignes dans la cellule. Essayez sans le repr() pour vous en rendre compte…)

À noter que ce mécanisme de duplication fonctionne quelle que soit la taille du tableau initial:

>>> t = [0, 1] * 25
>>> repr(t)
'[0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1]'

On utilisera souvent cette syntaxe pour initialiser des tableaux dont on sait à l’avance (en début d’algorithme) quelle sera leur taille.

Mécanisme de compréhension de listes #