programmez-en-d/tableaux.whata

403 lines
17 KiB
Plaintext
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

[set
title = "Tableaux"
partAs = chapitre
translator = "Raphaël Jakse"
proofreader = "Stéphane Goujet"
]
Nous avons défini 5 variables dans un des exercices du dernier chapitre et les avons utilisés dans certains calculs. Les définitions de ces variables étaient les suivantes~ :
[code=d <<<
double valeur_1;
double valeur_2;
double valeur_3;
double valeur_4;
double valeur_5;
>>>]
Cette méthode de définir des variables individuellement n'est pas du tout efficace dans les cas où il y a besoin d'encore plus de variables. Imaginez qu'on ait besoin d'un millier de valeurs~ ; il est pratiquement impossible de définir 1000 variables de [c valeur_1] à [c valeur_1000].
Les tableaux sont utiles dans de tels cas~ : les tableaux permettent la définition de beaucoup de valeurs ensemble. Les tableaux sont aussi la structure de données la plus fréquente quand de multiples valeurs sont utilisées ensemble en tant que collection.
Ce chapitre couvre seulement quelques unes des fonctionnalités des tableaux. Plus de fonctionnalités seront introduites dans un chapitre ultérieur.
[ = Définition
La définition de tableaux est vraiment similaire à la définition de variables. La seule différence est que le nombre de variables qui sont définies en même temps est indiqué entre crochets. On peut distinguer les deux définitions suivantes :
[code=d <<<
int uneSimpleVariable;
int[10] tableauDeDixVariables;
>>>]
]
La première ligne ci-dessus est la définition d'une simple variable, tout à fait comme les variables que nous avons définies jusqu'alors. La deuxième ligne est la définition d'un tableau consistant en 10 variables.
De même, l'équivalent des cinq variables séparées de l'exercice peut être défini comme un tableau de cinq variables en utilisant la syntaxe suivante~ :
[code=d <<< double[5] valeurs;>>>]
Cette définition peut être lue comme 5 valeurs [c double]. Notez qu'un pluriel a été choisi pour le nom du tableau pour éviter de le confondre avec une variable simple.
En résumé, la définition d'un tableau se compose du type des variables, de leur nombre et du nom du tableau~ :
[code=d <<< nom_du_type[nombre_de_variables] nom_du_tableau;>>>]
Le type des variables peut aussi être un type défini par le programmeur (nous verrons les types définis par le programmeur plus tard). Par exemple~ :
[code=d <<<
// Un tableau qui stocke l'information météorologique de
// toutes les villes. Ici, les valeurs booléennes veulent dire :
// false : couvert
// true : ensoleillé
bool[nombreDeVilles] ConditionsMeteorologiques;
// Un tableau qui stocke le poids de cent boîtes
double[100] poidsDesBoites;
// Information à propos des étudiants d'une école
DonneeEtudiant[NombredDEtudiants] DonneesEtudiants;
>>>]
[ = Éléments et conteneurs
Les structures de données qui rassemblent des éléments d'un certain type sont appelés [** conteneurs]. Selon cette définition, les tableaux sont des conteneurs. Par exemple, un tableau qui stocke les températures de l'air des jours de juillet peut rassembler 31 variables [c double] et forme un conteneur d'éléments de type [c double].
Les variables d'un conteneur sont appelés éléments. Le nombre d'élements d'un tableau est appelé la longueur d'un tableau.
]
[ = Accéder aux éléments
Pour différencier les variables dans l'exercice du chapitre précédent, nous devions ajouter un tiret du bas et un nombre à leurs noms (par exemple, [c valeur_1]). Ce n'est ni possible, ni nécessaire quand les variables sont définies ensemble comme un seul tableau avec un seul nom. On accède aux éléments en indiquant leur numéro entre crochets~ :
[code=d valeurs~[0~]]
Cette expression peut être lue comme «~ l'élément numéro 0 du tableau de nom "valeurs"~ ». En d'autres termes, au lieu de taper [c valeur_1], on tape [c valeurs~[0~]] avec les tableaux. Il y a deux points importants qu'il vaut la peine de relever~ :
- Les numéros commencent à 0~ : même si les humains comptent à partir de 1, les indices de tableaux commencent à 0. Les valeurs que nous avons numérotées 1, 2, 3, 4 et 5 sont numéroté 0, 1, 2, 3 et 4 dans le tableau. Cette spécificité est une cause de nombreuses erreurs de programmation.
- Deux utilisations différentes des crochets~ : ne les confondez pas. Quand on définit un tableau, les crochets sont écrits après le type des éléments et indiquent le nombre d'éléments. Quand on accède aux éléments, les crochets sont écrits après le nom du tableau et indiquent le numéro de l'élément auquel on accède~ :
[code=d <<<
// Ceci est une définition. Elle définit un tableau qui consiste
// en 12 éléments. Ce tableau est utilisé pour stocker le nombre
// de jours de chaque mois.
int[12] joursDuMois;
// Ceci est un accès. On accède à l'élément qui
// correspond à décembre et on lui donne la valeur 31.
joursDuMois[11] = 31;
// Ceci est un autre accès. On accède à l'élément qui
// correspond à janvier, sa valeur est passé à writeln.
writeln("Janvier a ", joursDuMois[0], " jours.");
>>>]
Rappel~ : les numéros des éléments de Janvier et Décembre sont respectivement 0 et 11, non 1 et 12.
]
[ = Indice
Le numéro d'un élément est appelé son indice.
Un indice n'a besoin d'être une valeur constante~ ; la valeur d'une variable peut peut aussi être utilisée comme un indice, ce qui rend les tableaux encore plus utiles. Par exemple, le mois est déterminé par la valeur de la variable [c IndiceMois] ci-dessous~ :
[code=d <<<
writeln("Ce mois a ", joursDuMois[IndiceMois], " jours.");
>>>]
Quand la valeur de [c IndiceMois] est 2, l'expression ci-dessus affiche la valeur de [c <<<joursDuMois[2]>>>], le nombre de jours du mois de mars.
Seuls les indices entre 0 et la longueur du tableau moins 1 sont valides. Par exemple, les indices valides d'un tableau à 3 éléments sont 0, 1 et 2. Accéder à un tableau avec un mauvais indice entraîne l'arrêt du programme avec une erreur.
Les tableaux sont des conteneurs dans lesquels les élements sont placés les uns à côté des autres dans la mémoire de l'ordinateur. Par exemple, les éléments du tableau qui stocke le nombre de jours dans chaque mois peuvent être vus comme ceci~ :
[pre <<<
indices → 0 1 2 3 4 5 6 7 8 9 10 11
éléments → | 31 | 28 | 31 | 30 | 31 | 30 | 31 | 31 | 30 | 31 | 30 | 31 |
>>>]
L'élément d'indice 0 a la valeur 31 (nombre de jours en janvier)~ ; l'élément d'indice 1 a la valeur 28 (nombre de jours en février), etc.
]
[ = Tableaux de taille fixe ''vs'' tableaux dynamiques
Quand la taille du tableau est indiquée lorsque le programme est écrit, ce tableau est de taille fixe. Quand la longueur peut changer pendant l'exécution du programme, ce tableau est dynamique.
Les tableaux que nous avons définis au dessus sont des tableaux à taille fixe parce que leur nombre d'élément est indiqué lors de l'écriture du programme (5 et 12). Les longueurs de ces tableaux ne peuvent pas être changées pendant l'exécution du programme. Pour changer leur longueur, le code source doit être modifié et le programme doit être recompilé.
Définir un tableau dynamique est plus simple que définir un tableau à taille fixe~ ; il suffit d'omettre la taille et on obtient un tableau dynamique~ :
[code=d <<<
int[] tableauDynamique;
>>>]
La taille d'un tel tableau peut augmenter ou diminuer pendant l'exécution du programme.
]
[ = [c .length] pour récupérer ou changer la taille du tableau
Les tableaux ont également des attributs. Ici, nous ne verrons que [c .length], qui retourne le nombre d'éléments du tableau.
[code=d <<<
writeln("Le tableau a", tableau.length, " éléments.");
>>>]
De plus, la taille des tableaux dynamique peut être modifiée en affectant une valeur à cet attribut~ :
[code=d <<<
int[] array; // initialement vide
tableau.length = 5; // maintenant, il y a 5 éléments
>>>]
Voyons maintenant comment on pourrait réécrire l'exercice avec les cinq valeurs en utilisant un tableau ~:
[code=d <<<
import std.stdio;
void main()
{
// Cette variable est utilisée en tant que compteur dans une boucle
int compteur;
// La définition d'un tableau à taille fixe de cinq
// éléments de type double
double[5] valeurs;
// récupérer les valeurs dans une boucle
while (compteur < valeurs.length) {
write("Valeur ", compteur + 1, " : ");
readf(" %s", &valeurs[compteur]);
++compteur;
}
writeln("Le double des valeurs :");
compteur = 0;
while (compteur < valeurs.length) {
writeln(valeurs[compteur] * 2);
++compteur;
}
// La boucle qui calcule le cinquième des valeurs
// serait écrite de façon similaire
}
>>>]
Observations~ : la valeur de [c compteur] détermine combien de fois les boucles sont répétées (itérées). Itérer la boucle tant que cette valeur est strictement inférieure à [c valeurs.length] assure que la boucle est exécutée une fois par élément. Comme la valeur de cette variable est incrémentée à la fin de chaque itération, l'expression [c valeurs~[compteur~]] fait référence à chaque élément du tableau un par un~ : [c valeurs~[0~]], [c valeurs~[1~]], etc.
Pour voir à quel point ce programme est meilleur que le précédent, imaginez que vous auriez besoin de 20 valeurs. Le programme ci-dessus nécessiterait une seule modification~ : remplacer 5 par 20~ ; alors que le programme qui n'utilisait pas de tableau aurait eu besoin de 15 définitions de variables de plus, idem pour les lignes dans lesquelles elles sont utilisées.
]
[ = Initialiser les éléments
Comme toute variable en D, les élements des tableaux sont automatiquement initialisés. La valeur initiale des élements dépend du type des élements~ : 0 pour [c int], [c double.nan] pour double, etc.
Tous les élements du tableau [c valeurs] ci-dessus sont initialisés à [c double.nan]~ :
[code=d <<< double[5] valeurs; // les éléments valent tous double.nan >>>]
Évidemment, les valeurs des éléments peuvent être modifiées plus tard dans le programme. On a déjà vu cela ci-dessus quand on a affecté une valeur à un élément de tableau~ :
[code=d <<< joursDuMois[11] = 31; >>>]
Et aussi quand on récupèrait une valeur depuis l'entrée~ :
[code=d <<< readf(" %s", &valeurs[compteur]); >>>]
Parfois, les valeurs souhaitées des éléments sont connues au moment où le tableau est défini. Dans de tels cas, les valeurs initiales des élements peuvent être indiquées du côté droit de l'opérateur d'affectation, entre crochets. Voyons cela dans un programme qui demande le numéro du mois à l'utilisateur et qui affiche le nombre de jours de ce mois~ :
[code=d <<<
import std.stdio;
void main()
{
int[12] joursDuMois =
[ 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 ];
write("Veuillez saisir le numéro du mois : ");
int numeroMois;
readf(" %s", &numeroMois);
int indice = numeroMois - 1;
writeln("Le mois ", numeroMois, " a ",
joursDuMois[indice], " jours.");
}
>>>]
Comme vous pouvez le voir, le tableau joursDuMois est défini et initialisé au même moment. Notez aussi que le numéro du mois, qui est entre 1 et 12, est converti en un indice valide entre 0 et 11. Toute valeur entrée en dehors de l'intervalle 1-12 entraînerait l'arrêt du programme avec une erreur.
Quand on initialise des tableaux, il est possible d'utiliser une seule valeur sur le côté droit. Dans ce cas, tous les éléments du tableau sont initialisés à cette valeur~ :
[code=d <<<
int[10] tousUn = 1; // Tous les éléments valent 1
>>>]
]
[ = Opérations basiques sur les tableaux
Les tableaux proposent des opérations pratiques qui s'appliquent à tous leurs éléments.
- [ Copier des tableaux à taille fixe~ : l'opérateur d'affectation copie tous les éléments du tableau de droite dans le tableau de gauche~ :
[code=d <<<
int[5] source = [ 10, 20, 30, 40, 50 ];
int[5] destination;
destination = source;
>>>]
[p Note~ : | la signification de l'opération d'affectation est complètement différente pour les tableaux dynamiques. Nous verrons cela dans un chapitre ultérieur.]
]
- [ Ajouter des éléments aux tableaux~ : l'opérateur [c ~~=] ajoute un nouvel élément ou un nouveau tableau à la fin du tableau dynamique :
[code=d <<<
int[] tableau; // vide
tableau ~= 7; // un élément
tableau ~= 360; // deux éléments
tableau ~= [ 30, 40 ]; // 4 éléments
>>>]
Il n'est pas possible d'ajouter des éléments à un tableau à taille fixe~ :
[code=d <<<
int[10] tableau;
tableau ~= 7; // ← ERREUR de compilation
>>>]
]
- [ Combiner des tableaux~ : l'opérateur [c ~~] crée un nouveau tableau en combinant deux tableaux. Son équivalent [c ~~=] combine deux tableaux et affecte le résultat au tableau de gauche~ :
[code=d <<<
import std.stdio;
void main()
{
int[10] premier = 1;
int[10] second = 2;
int[] resultat;
resultat = premier ~ second;
writeln(resultat.length); // affiche 20
resultat ~= premier;
writeln(resultat.length); // affiche 30
}
>>>]
]
L'opérateur [c ~~=] ne peut pas être utilisé quand le tableau de gauche est de taille fixe~ :
[code=d <<<
int[20] resultat;
// ...
resultat ~= premier; // ← ERREUR de compilation
>>>]
Si le tableau de gauche n'a pas exactement la même taille que le tableau résultat, le programme se termine avec une erreur pendant l'affectation~ :
[code=d <<<
int[10] premier = 1;
int[10] second = 2;
int[21] resultat;
resultat = premier ~ second;
>>>]
[output <<<
object.Error: Array lengths don't match for copy: 21 != 20
>>>]
Si on traduit~ : « les longueurs ne correspondent pas pour la copie de tableau.~ »
]
[ = Trier les éléments
[c std.algorithm.sort] trie les éléments de plages à accès directs. Dans le cas des entiers, les éléments sont triés du plus petit au plus grand. Pour utiliser [c sort()], il est nécessaire d'importer le module [c std.algorithm]~ :
[code=d <<<
import std.stdio;
import std.algorithm;
void main()
{
int[] tableau = [ 4, 3, 1, 5, 2 ];
sort(tableau);
writeln(tableau);
}
>>>]
La sortie~ :
[output <<<
[1, 2, 3, 4, 5]
>>>]
[ = Inverser les elements
[c std.algorithm.reverse] inverse les élements sur place (le premier élément devient le dernier, etc.):
[code=d <<<
import std.stdio;
import std.algorithm;
void main()
{
int[] array = [ 4, 3, 1, 5, 2 ];
reverse(array);
writeln(array);
}
>>>]
La sortie~ :
[output <<<
[2, 5, 1, 3, 4]
>>>]
]
[ = Exercices
# Écrivez un programme qui demande à l'utilisateur combien de valeurs vont être entrées et ensuite les lit toutes. Le programme doit trier ces éléments en utilisant [c .sort] et [c .reverse].
# [ Écrivez un programme qui lit des nombres depuis l'entrée, et affiche les nombres pairs et impairs séparément mais dans l'ordre. La valeur spéciale -1 termine la liste des nombres et ne fait pas partie de la liste.
Par exemple, quand les nombres suivants sont entrés~ :
[input 1 4 7 2 3 8 11 -1]
Le programme affiche ceci~ :
[output 1 3 7 11 2 4 8]
Astuce~ : vous pouvez vouloir enregistrer les éléments dans des tableaux séparés. Vous pouvez déterminer si un nombre est pair ou impair en utilisant l'opérateur [c %] (modulo).
]
# [ Ce qui suit est un programme qui ne marche pas comme attendu. Le programme est écrit pour lire cinq nombre depuis l'entrée et placer les carrés de ces nombres dans un tableau. Le programme essaie ensuite d'afficher les carrés dans la sortie. Au lieu de ça, le programme se termine avec une erreur.
Corrigez les bogues de ce programme et faites le marcher comme ce qui est attendu~ :
[code=d <<<
import std.stdio;
void main()
{
int[5] carres;
writeln("Veuillez entrer 5 nombres");
int i = 0;
while (i <= 5) {
int nombre;
write("Nombre ", i + 1, ": ");
readf(" %s", &nombre);
carres[i] = nombre * nombre;
++i;
}
writeln("=== Les carrés des nombres ===");
while (i <= carres.length)
{
write(carres[i], " ");
++i;
}
writeln();
}
>>>]
]
[[part:corrections/tableaux | … Les solutions]]
]