Les tableaux

Dans ce chapitre, nous allons voir un nouveau type de variables : les tableaux. Les tableaux sont un type bien spécial de variables puisqu’ils permettent de conserver en mémoire plusieurs valeurs plutôt qu’une.

Généralités sur les tableaux

Qu’est-ce qu’un tableau

C’est l’heure pour nous de voir les tableaux. Un tableau est une super variable. En fait, c’est une structure de données. Cette structure est un ensemble d’éléments ordonnés auxquels on peut accéder par un indice, d’où le nom « tableau ». Les tableaux sont des variables qui contiennent d’autres variables. Ce sont donc des « séquences » d’éléments tout comme les intervalles n..l vus dans le chapitre précédent.

Le premier indice d’un tableau est l’indice 0 et non 1.

On a donc l’élément d’indice 0, l’élément d’indice 1… Et ainsi de suite, potentiellement à l’infini.

Un tableau permet alors de rassembler des informations. Par exemple, nous pourrions vouloir les âges de cinquante personnes. Récupérer ces cinquante âges dans un tableau peut être judicieux.

En effet, les tableaux permettent d’agir très facilement sur l’ensemble des éléments, et en continuant avec l’exemple des cinquante âges, nous pourrions multiplier tous ces âges par deux en moins de dix lignes en utilisant un tableau, alors qu’avec cinquante variables, nous aurions besoin de cinquante lignes.

Les tableaux permettent donc de s’affranchir de beaucoup de contraintes (et de copier-coller) et favorisent le développement.

Et pour mettre un mot sur les choses, il nous faut préciser que les tableaux sont ce que l’on appelle un conteneur.

Déclarer un tableau

Pour déclarer un tableau, nous devons utiliser les crochets []. Cela permet de déclarer un tableau vide.

tab = []

Pour afficher le tableau, nous pouvons utiliser la méthode print comme d’habitude.

tab = []
print tab

On affiche tab, on obtient [], le tableau vide.

Les éléments du tableau sont placés entre les crochets, séparés par des virgules. Voici comment déclarer le tableau contenant les éléments 1, 2, et 3.

tab = [1, 2, 3]
print tab

On obtient cette fois cet affichage : [1, 2, 3].

Les crochets ne sont pas obligatoires. Nous pouvons ne pas les utiliser et juste séparer les éléments par des virgules. Cependant, ils sont obligatoires dans le cas où il y a moins de deux éléments.

Ainsi, nous aurions pu parfaitement faire notre dernier exemple de cette manière.

tab = 1, 2, 3
print tab

Cependant, pour des raisons de compréhension, nous conseillons de toujours utiliser les crochets.

Maintenant, nous pouvons faire des tableaux d’entiers, des tableaux de flottants et même des tableaux de chaînes de caractères. C’est bien, mais il y a mieux : en Ruby, les tableaux mixtes sont possibles. Cela veut dire que nous pouvons parfaitement faire un tableau contenant des entiers et des chaînes de caractères, voire d’autres tableaux. Ce code est parfaitement valide.

tab = [3.23, 'Trois virgule vingt-trois', [3, '.', 2, 3]]
print tab
Tableaux de chaînes de caractères

De même qu’il y a la syntaxe %q pour écrire des chaînes de caractères, nous pouvons utiliser les syntaxes %w et %W pour écrire des tableaux de chaînes de caractères. Dans ce cas, les chaînes sont séparées par des espaces. %w correspond à des chaînes en guillemets simples et %W à des chaînes en guillemets doubles.

a = %w[un deux trois]  # Équivalent à a = ['un', 'deux', 'trois'].

Ces syntaxes sont beaucoup plus simples à écrire et sont à privilégier lorsque nous voulons écrire un tableau de chaînes de caractères. La seule chose qui peut poser problème est l’écriture d’une chaîne avec des espaces. En effet, %W[une chaîne deux] ne donne pas ["une chaîne", "deux"] mais ["une", "chaîne", "deux"]. Pour que l’espace soit prise en compte comme telle et non comme séparateur, il faut l’échapper.

a = %W[une\ chaîne deux]  # Équivalent à a = ["une chaîne", "deux"].

En fait, nous n’allons privilégier ces syntaxes que dans le cas où nous voulons un tableau de mots. Notons de plus qu’il est préférable d’utiliser cette syntaxe avec les crochets comme délimiteur (notamment parce que cela rappelle bien qu’il s’agit d’un tableau) alors que dans le cas des chaînes de caractères, il est d’usage d’utiliser les parenthèses.

Opérations sur les tableaux

Accéder à un élément

Les tableaux et tout, c’est bien joli, mais pour le moment, on ne peut pas accéder à un élément du tableau. Comment faire ? Ne nous moquons pas, il faut encore utiliser les crochets []. Pour accéder à l’élément d’indice 2 du tableau tab, il nous faut juste écrire tab[2]. Voyons cela avec un exemple.

grammar = %w[mais ou et donc or ni car]
print grammar[2]

Ce code permet d’afficher la chaîne de caractères « et ».

Nous aurions pu penser que ce code afficherait « ou » mais n’oublions pas, le premier indice d’un tableau est l’indice 0. L’élément d’indice n est donc l’élément n + 1 du tableau.

Nous pouvons maintenant extraire n’importe quel élément d’un tableau. Cependant, lorsque que nous accédons à un élément du tableau, il faut faire attention qu’il y ait bien un élément qui corresponde à l’indice demandé. L’élément d’indice 5 d’un tableau de taille 3 n’existe pas. Contrairement à d’autres langages, Ruby ne provoque pas d’erreur et retourne nil lorsque cela est fait, mais cela ne veut pas dire qu’il faut le faire.

On peut même afficher une partie du tableau seulement, en utilisant les intervalles vus dans le chapitre précédent. Ils nous permettent d’obtenir un sous-tableau contenant tous les éléments du tableau dont les indices appartiennent à l’intervalle.

grammar = %w[mais ou et donc or ni car]
print grammar[1..5]

Le code précédent affiche ["ou", "et", "donc", "or", "ni"], c’est-à-dire les éléments dont l’indice est compris entre 1 et 5.

Concaténation et ajout d’éléments

La concaténation de tableaux est l’ajout de deux tableaux. Cela permet de deviner de suite comment effectuer la concaténation. Oui, la concaténation de tableau se fait avec l’opérateur +. Son utilisation nous est maintenant habituelle.

tab = [1, 2, 3]
tab = tab + [4, 5, 6]
print tab

Nous nous en doutons, avec ce code, on obtient [1, 2, 3, 4, 5, 6]. Pas compliqué, non ?

Pour concaténer plusieurs fois le même tableau, on utilise l’opérateur *. Pour avoir trois fois [1, 2, 3], on utilise ce code.

tab = [1, 2, 3]
tab = tab * 3
print tab

L’ajout d’éléments dans un tableau est un peu plus subtil. Une manière de voir les choses est de se dire que rajouter un élément à un tableau, c’est concaténer deux tableaux, l’un des tableaux n’ayant qu’un seul élément.

Rajoutons l’élément 7 à notre tableau tab.

tab = [1, 2, 3, 4, 5, 6]
tab += [7]
print tab

On obtient « [1, 2, 3, 4, 5, 6, 7] » comme prévu. L’autre méthode, à privilégier, est d’utiliser l’opérateur <<. Il modifie directement le tableau, ce n’est donc pas la peine de récupérer le résultat de l’opération.

L’opérateur << ajoute au tableau l’élément qui est à droite contrairement à l’opérateur + qui concatène les deux tableaux.

Ainsi, tab << [3] est différent de tab + [3]. Dans le premier cas, on ajoute au tableau un élément qui est un tableau, dans le second cas, on concatène deux tableaux, donc l’élément qui est ajouté au premier tableau est 3, et non [3].

tab1 = [1, 2]
tab2 = [1, 2]
tab1 << [3]
tab2 = tab2 + [3]
print "#{tab1} \n#{tab2}"

Avec ce code, on obtient ceci.

[1, 2, [3]]
[1, 2, 3]

Cela signifie que pour ajouter un élément au tableau avec <<, il faut utiliser la syntaxe tab << élément, syntaxe qui ne marche pas avec l’opérateur +, car cela signifierait additionner un tableau et un entier par exemple. Pour ajouter 3 à notre tableau précédent, il fallait écrire tab << 3.

Ajouter un élément à un indice précis

Nous avons un tableau de 3 cases. Et nous voulons ajouter un élément à la sixième case du tableau. Pour cela, nous allons utiliser les crochets (très utiles ceux-là).

tab = [1, 2, 3]
tab[5] = 6
print tab

Ce code affiche [1, 2, 3, nil, nil , 5] : la case qu’on voulait remplir a pris la bonne valeur et toutes les cases qui n’existaient pas ont pris la valeur nil.

Parcourir le tableau

Maintenant que nous savons comment accéder à chaque élément d’un tableau, nous pouvons envisager de parcourir le tableau en entier et de, par exemple, faire une même opération sur tous les éléments du tableau. Pour cela, il suffit d’utiliser une boucle for. Nous avons en effet dit qu’un tableau était une séquence, nous pouvons donc parcourir le tableau de la même manière que nous avons parcouru les intervalles.

Prenons l’exemple d’un menu de restaurant.

meat_menu = ["un steak haché", "une entrecôte", "un rôti", "un faux-filet"]
for meat in meat_menu
  puts "Voulez-vous #{meat} pour le dîner de ce soir ?"
end

Tout d’abord, nous déclarons le tableau meat_menu. Ensuite nous parcourons notre tableau ; la boucle for permet de répéter les instructions pour chaque élément du tableau. On obtient donc ceci.

Voulez-vous un steak haché pour le dîner de ce soir ?
Voulez-vous une entrecôte pour le dîner de ce soir ?
Voulez-vous un rôti pour le dîner de ce soir ?
Voulez-vous un faux-filet pour le dîner de ce soir ?

En réfléchissant bien, on peut trouver une autre manière de faire cela en jouant avec les indices.

meat_menu = ["un steak haché", "une entrecôte", "un rôti", "un faux-filet"]
4.times { |i| puts "Voulez-vous #{meat_menu[i]} pour le dîner de ce soir ?" }

Ici, nous affichons l’élément d’indice i du tableau avec i qui va de 0 à 3.

Cela nous fait deux façons de parcourir le tableau. La première est plus lisible. Cependant ce n’est pas celle que nous utiliserons.

La méthode each

Pour parcourir un tableau, nous utiliserons la méthode each. Eh oui, elle fonctionne également pour les tableaux ! Gardons l’exemple d’un menu de restaurant.

meat_menu = ["un steak haché", "une entrecôte", "un rôti", "un faux-filet"]
meat_menu.each { |meat| puts "Voulez-vous #{meat} pour le dîner de ce soir ?" }

Tout d’abord, nous déclarons le tableau menu_viande. Ensuite nous parcourons notre tableau ; do répète les instructions pour chaque élément du tableau. On obtient le même résultat que précédemment.

La méthode each_with_index

Si nous avons également besoin des indices du tableau, nous utiliserons l’itérateur each_with_index qui ressemble beaucoup à each. La seule différence est qu’il y a une seconde variable, l’indice.

meat_menu = ["un steak haché", "une entrecôte", "un rôti", "un faux-filet"]
meat_menu.each_with_index { |meat, i| puts "Voulez-vous le menu #{i} (#{meat}) pour le dîner de ce soir ?" }

Lien avec les chaînes de caractères

Les tableaux peuvent rappeler les chaînes de caractères. Après tout, la concaténation se fait de la même manière pour les tableaux que pour les chaînes de caractères. De même, l’ajout d’élément se fait de la même manière, à l’aide de l’opérateur <<. On est alors en droit de se demander si cela va plus loin.

C’est vrai ça, quel est le lien entre les tableaux et les chaînes des caractères ?

Si nous disions qu’une chaîne de caractères est un tableau de caractères, nous serions en train de mentir. Mais le fait est là, on peut presque voir une chaîne de caractères comme un tableau de caractères. De là, on voit la multitude d’opérations que l’on peut effectuer sur les chaînes de caractères.

Accéder à un élément

Nous pouvions facilement le deviner, on accède à un élément d’une chaîne de caractères à l’aide des crochets. Ainsi, si on veut obtenir le cinquième caractère d’une chaîne de caractères, on peut utiliser ce code.

str = 'Bonjour'
print str[3] # Affiche « j ».

De même que pour les tableaux, l’élément d’indice i correspond à l’élément i + 1 de la chaîne de caractères.

De plus, nous pouvons toujours, à la manière des tableaux, afficher une partie d’une chaîne de caractères à l’aide des intervalles. Pour afficher « onj » de la chaîne « bonjour »…

str = 'Bonjour'
print str[1..3]
Parcourir une chaîne de caractères

Nous avons déjà vu dans le chapitre précédent qu’il était possible de parcourir une chaîne de caractères avec une boucle for. On parcourait ainsi un par un les éléments, le séparateur étant le \n. Maintenant que nous savons accéder à un caractère grâce à son indice, on peut parcourir toutes les lettres de la chaîne de caractères.

str = 'bonjour'
7.times { |i| print "#{str[i]} " }

Mais là encore, ce n’est pas cette méthode que nous utiliserons pour parcourir tous les caractères d’une chaîne. Quelle méthode utiliserons-nous dans ce cas ?

La méthode each ? Perdu, mais presque. Nous utiliserons la méthode each_char (chaque caractère). Elle s’emploie de la même manière que each (et est aussi un itérateur), mais est spécifique aux chaînes de caractères. Nous voyons bien là que même si les chaînes de caractères et les tableaux se ressemblent, ce ne sont pas les mêmes objets. Réécrivons le code précédent.

str = 'bonjour'
str.each_char { |c| print "#{c} " }

Il existe également une méthode each_line qui permet de parcourir une chaîne de caractères par groupes de caractères. Elle prend en paramètre le séparateur (l’élément qui sépare les groupes de caractères), le séparateur par défaut étant le retour chariot \n (ce qui explique le nom de la méthode qui, par défaut, permet de parcourir « chaque ligne » d’une chaîne de caractères).

str = "bonjour\nle\nmonde."
str.each_line { |l| print "#{l} " }

On peut aussi lire les groupes de mots séparés par exemple par une espace.

str = 'bonjour le monde.'
str.each_line(' ') { |l| print "#{l}" }
De la chaîne au tableau

Notons que nous pouvons convertir une chaîne de caractères en tableau de caractères avec la méthode chars. Ainsi, 'bonjour'.chars renverra ['b', 'o', 'n', 'j', 'o', 'u', 'r']. La méthode split, quant à elle donne un tableau contenant les mots de la chaîne de caractère (le délimiteur est l’espace). Ainsi, 'je suis clem'.split renverra ['je', 'suis', 'clem'].

Nous pouvons choisir un autre caractère que l’espace comme délimiteur. Pour cela, il suffit de le passer en paramètre de split. Ainsi, 'un, deux, trois'.split(', ') renverra ['un', 'deux', 'trois'] et 'bonjour'.split('') agira comme chars et renverra ['b', 'o', 'n', 'j', 'o', 'u', 'r'].

L’opération contraire (passer du tableau à la chaîne de caractère) se fait à l’aide de la méthode join. Elle crée une chaîne de caractères en joignant les éléments du tableau. On peut lui donner en paramètre une chaîne de caractère qui servira de séparateur entre les éléments du tableau.

["un", "deux", "trois"].join              # => "undeuxtrois"
# On place une virgule entre les éléments.
["un", "deux", "trois"].join(", ")        # => "un, deux, trois"
# On peut l’utiliser avec des éléments qui ne sont pas des chaînes de caractères.
[1, 2, 3].join(", ")                      # => "1, 2, 3"

Exercices

Exercice 1

Ce premier exercice va nous faire travailler les boucles et les tableaux. Nous devons demander à l’utilisateur combien de cases il veut pour un tableau, le nombre maximum de cases étant de 10 (nous pouvons soit lui redemander un nombre de cases, soit choisir 10 par défaut s’il ne donne pas un nombre valide). Nous devons ensuite lui demander les valeurs de son tableau. Et enfin, nous devons afficher pour chaque valeur du tableau « Votre tableau contient (la valeur en question ici) ». Un exemple.

Combien de cases (entre 1 et 10) : 2

Entrez un nombre : 2
Entrez un nombre : 4

Votre tableau contient 2.
Votre tableau contient 4.

Correction.

Nous décidons de lui redemander tant que la valeur entrée n’est pas bonne.

number = 0
while number > 10 || number <= 0
  print 'Combien de cases (entre 1 et 10) :'
  number = gets.chomp.to_i
end
puts

tab = []
numbber.times do
  print 'Entrez un nombre :'
  a = gets.chomp.to_i
  tab << a # On ajoute l’élément au tableau.
end
puts

tab.each { |x| puts "Votre tableau contient #{x}." }
Exercice 2

Maintenant, manipulons plusieurs tableaux. Notre programme doit afficher les valeurs de deux tableaux de même taille dans l’ordre croissant (les deux tableaux sont déjà triés à l’origine). Nous pourrons demander à l’utilisateur de nous donner des valeurs pour créer notre tableau.

Correction.

Pour faire cet exercice, parcourir les tableaux à l’aide de leurs indices peut être astucieux.

tab1 = [-3, 0, 5, 12, 23]
tab2 = [-2, 1, 2, 3, 8]
n = 5
i = 0
j = 0
while i < 5 || j < 5
  if j == 5 || tab1[i] < tab2[j] 
    print "#{tab1[i]} "
    i = i + 1
  else
    print "#{tab2[j]} "
    j = j + 1
  end
end

La seule chose qui pourrait nous déboussoler est le if j == 5. Ici, on vérifie la valeur de j pour rester dans le tableau. Là encore, dérouler l’algorithme à la main sur un papier aide à sa compréhension.

Exercice 3

En troisième exercice, nous allons faire un petit exercice de compression. Nous devons demander à l’utilisateur une chaîne de caractères composée de 1 et de 0 et afficher le résultat de la compression (ce résultat doit être un tableau). Voici comment nous allons procéder pour la compression : nous représenterons n chiffres 1 d’affilée par n1 et ferons de même pour les 0. Voici un exemple.

Entrez la chaîne à compresser : 110000111000110
Résultat : 214031302110

Nous avons de quoi bien travailler.

Correction.

print "Entrez la chaîne à compresser : "
str = gets.chomp 

current = chaîne[0]
n = 0
tab = []
str.each_char do |char|
  if char == current
    n = n + 1
  else
    tab << n
    tab << current
    current = char
    n = 1
  end
end
tab << n
tab << current
result = tab.join
print result

Ce que nous avons fait fonctionne avec des 1 et des 0, mais aussi avec n’importe quel autre caractère.


Ça y est, nous savons maintenant utiliser les tableaux. Grâce à ça, nous pourrons manipuler des données plus facilement.

  • Un tableau est un ensemble d’éléments indexés par des entiers. On peut mettre n’importe quel type de variable dans un tableau.
  • Pour parcourir un tableau, on utilise la méthode each ou la méthode each_with_index lorsqu’on a besoin des indices.
  • Les chaînes de caractères peuvent être vues comme des tableaux de caractères (mais n’en sont pas). On peut parcourir une chaîne de caractères en utilisant les méthodes each_char et each_line, et on peut la convertir en tableau en utilisant la méthode chars.