En programmation, l'itération désigne le processus qui consiste à répéter un bloc de code plusieurs fois. On peut y parvenir à l'aide de concepts comme les boucles while et les boucles until. On peut aussi le faire à l'aide de la récursivité, c'est-à-dire une fonction qui s'appelle elle-même.
Cependant, le plus souvent, on veut itérer sur une collection d'éléments, comme un Array, un Range ou une String.
En Crystal, les classes Array et Range incluent toutes les deux le module Enumerable, qui fournit un certain nombre de méthodes pour itérer sur les éléments.
Quant à la classe String, elle possède son propre ensemble de méthodes pour itérer sur les caractères.
Crystal n'a pas non plus d'instruction for comme les autres langages.
À la place, il dispose de plusieurs méthodes pour itérer.
La façon la plus courante d'itérer sur une collection est d'utiliser la méthode each, qui transmet chaque élément de la collection à un bloc.
Cela se fait facilement avec un Range.
Supposons que tu veuilles boucler entre 1 et 3 : tu peux utiliser la méthode each pour itérer sur ce Range.
(1..3).each do |n|
puts n
end
# Output:
# 1
# 2
# 3
Encore plus simple, si tu veux simplement itérer un certain nombre de fois, tu peux utiliser la méthode times, qui existe sur la classe Int.
3.times do |n|
puts n
end
# Output:
# 0
# 1
# 2
String
Une String est une séquence de caractères et n'appartient pas au module Enumerable, ce qui signifie qu'elle possède son propre ensemble de méthodes pour itérer sur les caractères.
La façon la plus courante d'itérer sur une String est d'utiliser la méthode each_char, qui transmet chaque caractère de la String à un bloc.
La méthode each_char transmet un objet Char et non un objet String au bloc.
str = "hello"
str.each_char { |char| puts char }
# Output:
# h
# e
# l
# l
# o
Une autre façon d'itérer sur une String est d'utiliser la méthode each_line.
Cette méthode sert surtout lorsqu'on lit un fichier ligne par ligne.
str = "hello\nworld"
str.each_line do |line|
puts line
end
# Output:
# hello
# world
Enumerable
Le module Enumerable fournit un certain nombre de méthodes pour itérer sur les éléments d'une collection.
Les collections qui incluent le module Enumerable sont Array, Range, Hash, Set et d'autres ; ces dernières seront abordées dans des concepts ultérieurs.
La façon la plus courante d'itérer sur un Array est d'utiliser la méthode each, qui transmet chaque élément de l'Array à un bloc.
arr = [5, 2, 3]
arr.each do |element|
puts element
end
# Output:
# 5
# 2
# 3
La méthode map est une autre façon d'itérer sur un Array : elle renvoie un nouveau Array contenant les résultats de l'application du bloc à chaque élément.
Elle est similaire à la méthode each, mais elle renvoie un nouveau Array avec les éléments transformés.
La transformation est très utile lorsqu'on veut appliquer une fonction à chaque élément de la collection, ce qui est une opération courante.
arr = [1, 2, 3]
new_arr = arr.map do |element|
element * 2
end
new_arr
# => [2, 4, 6]
Avec une structure de contrôle telle que if, tu peux filtrer les éléments à transformer.
arr = [1, 2, 3]
new_arr = arr.map do |element|
if element.odd?
element * 2
else
element
end
end
new_arr
# => [2, 2, 6]
Parfois, on a besoin de connaître l'indice de l'élément sur lequel on itère ; on peut pour cela utiliser la méthode each_with_index ou map_with_index.
Elle transmet chaque élément et son indice à un bloc, et la méthode accepte aussi un argument facultatif pour préciser l'indice de départ.
arr = [1, 2, 3]
arr.each_with_index(4) do |element, index|
puts "Element: #{element}, Index: #{index}"
end
# Output:
# Element: 1, Index: 4
# Element: 2, Index: 5
# Element: 3, Index: 6
À noter que la méthode each_char_with_index est également disponible pour les objets String.
La méthode sum renvoie la somme de tous les éléments de la collection ; elle accepte aussi un bloc facultatif pour transformer les éléments avant de les additionner.
Elle accepte également un argument facultatif pour préciser la valeur initiale de la somme.
arr = [1, 2, 3]
arr.sum
# => 6
arr.sum(2) { |n| n * 2 }
# => 14
reduce, ou fold comme on l'appelle dans d'autres langages, est une méthode qui, avec une méthode de combinaison et une valeur initiale, combine tous les éléments de la collection.
La méthode reduce utilise un accumulateur qui est transmis au bloc ; cet accumulateur est le résultat de l'itération précédente.
Cela forme un processus récursif qui combine tous les éléments de la collection.
arr = [1, 2, 3]
arr.reduce(0) do |acc, n|
acc + n
end
# => 6
reduce peut sembler similaire à sum, mais reduce est plus flexible, car il permet de préciser la valeur initiale de l'accumulateur et la méthode de combinaison.
reduce peut servir à implémenter sum, count et d'autres méthodes.
Dans le jeu de cartes Spellbound Steel, on dispose d'un paquet de cartes comportant soit différents personnages tels que Warrior, Mage et Rogue, soit différents sorts tels que Fireball, Ice Storm et Lightning Bolt. Une partie réunit entre deux et quatre joueurs, et le but est de vaincre les autres joueurs en jouant des cartes dont les niveaux de puissance sont plus élevés.
Tu veux prendre le dessus dans la partie en créant différentes méthodes pour calculer, trier et comparer les niveaux de puissance des cartes.
Tu ne sais pas avec certitude si une carte se trouve dans le paquet ou non. Tu veux donc une méthode qui t'indique où trouver la première carte du paquet correspondant à celle que tu cherches. Et si la carte n'est pas dans le paquet, tu veux le savoir aussi.
Implémente une méthode SpellboundSteel.find_card? qui prend un Array de cartes String ainsi qu'une carte String à trouver.
La méthode doit renvoyer l'indice de la première carte du paquet qui correspond à la carte recherchée.
Si la carte n'est pas dans le paquet, la méthode doit renvoyer nil.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt", "Mage"]
SpellboundSteel.find_card?(cards, "Mage")
# => 1
Pour rendre les cartes plus faciles à lire, tu veux mettre une majuscule à tous les caractères des noms de cartes. Ainsi, tu verras facilement le nom des cartes quand tu les joueras.
Implémente une méthode SpellboundSteel.capitalize_cards qui prend un Array de cartes String et renvoie les cartes avec tous les caractères en majuscule.
cards = ["warrior", "mage", "rogue", "fireball", "ice storm", "lightning bolt"]
SpellboundSteel.capitalize_cards(cards)
# => ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
Dans le jeu, il est important de calculer le niveau de puissance de toutes les cartes. La puissance de toutes les cartes est indiquée dans le tableau suivant :
| Type de carte | Niveau de puissance |
|---|---|
| Warrior | 10 |
| Mage | 20 |
| Rogue | 30 |
| Fireball | 15 |
| Ice Storm | 25 |
| Lightning Bolt | 35 |
Implémente une méthode SpellboundSteel.calculate_power_level qui prend un Array de cartes String et renvoie le niveau de puissance total de toutes les cartes.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
SpellboundSteel.calculate_power_level(cards)
# => 135
Dans le jeu, il existe ce qu'on appelle des cartes maudites. Ce sont des cartes cachées qui ne peuvent être décodées que par le joueur qui les a jouées. Pour décoder la carte, il faut retirer un caractère sur deux dans le nom de la carte. Cela donne le nom de la carte.
Implémente une méthode SpellboundSteel.decode_card qui prend une carte String et renvoie la carte décodée.
SpellboundSteel.decode_card("Wbalrrrlimoqr")
# => "Warrior"
Inscris-toi sur Exercism pour apprendre et maîtriser Crystal avec 26 concepts142 exercices, et un vrai mentorat humain, le tout gratuitement.