La décomposition désigne l'action d'extraire les éléments d'une collection, comme un Array ou un Hash.
Les valeurs décomposées peuvent ensuite être affectées à des variables dans la même instruction.
L'affectation multiple désigne la possibilité d'affecter plusieurs variables afin de décomposer des valeurs dans une seule instruction.
Elle permet d'obtenir un code plus concis et plus lisible, et s'effectue en séparant par une virgule les variables à affecter, comme dans first, second, third = [1, 2, 3].
L'opérateur splat (*) et l'opérateur double splat (**) sont souvent utilisés dans des contextes de décomposition.
L'opérateur splat (*) permet de combiner plusieurs tableaux en un seul tableau en décomposant chacun d'eux dans un nouveau tableau commun.
L'opérateur double splat (**) permet de combiner plusieurs hashs en un seul hash en décomposant chacun d'eux dans un nouveau hash commun.
Cette syntaxe sert à distinguer le cas de plusieurs valeurs acceptées comme arguments positionnels de celui où l'on accepte un nombre quelconque d'arguments nommés.
Quand on utilise l'opérateur splat (*) sans collection, il regroupe (ou compose) un certain nombre de valeurs dans un tableau.
On l'utilise souvent dans l'affectation multiple pour regrouper dans une seule variable tous les éléments « restants » qui ne font pas l'objet d'une affectation individuelle.
En Ruby, il est courant d'adopter ce comportement de décomposition/composition quand on utilise ou définit des méthodes qui prennent un nombre arbitraire d'arguments positionnels ou nommés.
Tu verras souvent ces arguments définis sous la forme def some_method(*arguments, **keyword_arguments) et utilisés sous la forme some_method(*some_array, **some_hash).
Il ne faut pas confondre *<variable_name> et **<variable_name> avec * et **.
Alors que * et ** servent respectivement à la multiplication et à l'exponentiation, *<variable_name> et **<variable_name> servent d'opérateurs de composition et de décomposition.
L'affectation multiple te permet d'affecter plusieurs variables sur une seule ligne.
Pour séparer les valeurs, utilise une virgule , :
>> a, b = 1, 2
=> [1, 2]
>> a
=> 1
L'affectation multiple n'est pas limitée à un seul type de donnée :
>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true
L'affectation multiple permet d'échanger des éléments dans des tableaux. Cette pratique est assez courante dans les algorithmes de tri. Par exemple :
>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
On parle aussi d'« affectation parallèle », ce qui permet d'éviter une variable temporaire.
S'il y a plus de variables que de valeurs, les variables en trop se voient affecter nil :
>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil
S'il y a plus de valeurs que de variables, les valeurs en trop sont ignorées :
>> a, b, c = 1, 2, 3, 4
>> b
=> 2
>> c
=> 3
En Ruby, il est possible de décomposer les éléments de tableaux/hashs dans des variables distinctes. Comme les valeurs apparaissent dans un tableau selon un ordre d'indice, elles sont réparties dans les variables dans ce même ordre :
>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"
Si certaines valeurs ne sont pas nécessaires, tu peux utiliser _ pour indiquer qu'elles sont « récupérées mais non utilisées » :
>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"
Décomposer et affecter les valeurs de tableaux à l'intérieur d'un tableau (aussi appelé tableau imbriqué) fonctionne comme une décomposition superficielle, mais nécessite une expression de décomposition délimitée (()) pour clarifier le contexte ou la position des valeurs :
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"
Tu peux aussi décomposer en profondeur une partie seulement d'un tableau imbriqué :
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"
Si la décomposition comporte des variables mal placées ou un nombre incorrect de valeurs, tu obtiendras une erreur de syntaxe :
fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
(a, b), (d) = fruits_vegetables
syntax error, unexpected ')', expecting '.' or &. or :: or '['
((a, b), (d)) = fruits_vegetables
Expérimente ici, et tu remarqueras que c'est le premier motif qui dicte la décomposition, et non les valeurs disponibles à droite. L'erreur de syntaxe n'est pas liée à la structure de données.
*)Quand tu [décomposes un tableau][decomposition], tu peux utiliser l'opérateur splat (*) pour capturer les valeurs « restantes ».
C'est plus clair que de découper le tableau (ce qui, dans certaines situations, est moins lisible).
Par exemple, on peut extraire le premier élément, puis affecter les valeurs restantes dans un nouveau tableau sans le premier élément :
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]
On peut aussi extraire les valeurs du début et de la fin du tableau tout en regroupant toutes les valeurs du milieu :
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]
On peut également utiliser * dans une décomposition profonde :
>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]
Hash
Décomposer un hash est un peu différent de décomposer un tableau. Pour pouvoir décomposer un hash, il faut d'abord le convertir en tableau. Sinon, il n'y aura pas de décomposition :
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil
Pour convertir un Hash en tableau, tu peux utiliser la méthode to_a :
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> fruits_inventory.to_a
=> [[:apple, 6], [:banana, 2], [:cherry, 3]]
>> x, y, z = fruits_inventory.to_a
>> x
=> [:apple, 6]
Si tu veux décomposer les clés, tu peux utiliser la méthode keys :
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple
Si tu veux décomposer les valeurs, tu peux utiliser la méthode values :
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6
La composition consiste à regrouper plusieurs valeurs dans un seul tableau affecté à une variable. C'est utile lorsque tu veux décomposer des valeurs, y apporter des modifications, puis recomposer les résultats dans une variable. Il permet aussi de fusionner deux tableaux/hashs ou plus.
*)On peut composer un tableau à l'aide de l'opérateur splat (*).
Cela regroupe toutes les valeurs dans un tableau.
>> fruits = ["apple", "banana", "cherry"]
>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>> combined_fruits = *fruits, *more_fruits
>> combined_fruits
=> ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
**)On compose un hash à l'aide de l'opérateur double splat (**).
Cela regroupe toutes les paires clé/valeur d'un hash dans un autre hash, ou combine deux hashs entre eux.
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> more_fruits_inventory = {orange: 4, kiwi: 1, melon: 2, mango: 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>> combined_fruits_inventory
=> {:apple=>6, :banana=>2, :cherry=>3, :orange=>4, :kiwi=>1, :melon=>2, :mango=>3}
*) et de l'opérateur double splat (**) avec les méthodesQuand tu crées une méthode qui accepte un nombre arbitraire d'arguments, tu peux utiliser *arguments ou **keyword_arguments dans la définition de la méthode.
*arguments sert à regrouper un nombre arbitraire d'arguments positionnels (non nommés) et **keyword_arguments sert à regrouper un nombre arbitraire d'arguments nommés.
Utilisation de *arguments :
# This method is defined to take any number of positional arguments
# (Using the single line form of the definition of a method.)
>> def my_method(*arguments)= arguments
# Arguments given to the method are packed into an array
>> my_method(1, 2, 3)
=> [1, 2, 3]
>> my_method("Hello")
=> ["Hello"]
>> my_method(1, 2, 3, "Hello", "Mars")
=> [1, 2, 3, "Hello", "Mars"]
Utilisation de **keyword_arguments :
# This method is defined to take any number of keyword arguments
>> def my_method(**keyword_arguments)= keyword_arguments
# Arguments given to the method are packed into a dictionary
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
Si la méthode définie ne comporte aucun paramètre pour les arguments nommés (**keyword_arguments ou <key_word>: <value>), alors les arguments nommés sont regroupés dans un hash et affectés au dernier paramètre.
>> def my_method(a)= a
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
*arguments et **keyword_arguments peuvent aussi être utilisés en combinaison l'un avec l'autre :
def my_method(*arguments, **keyword_arguments)
p arguments.sum
for (key, value) in keyword_arguments.to_a
p key.to_s + " = " + value.to_s
end
end
my_method(1, 2, 3, a: 1, b: 2, c: 3)
6
"a = 1"
"b = 2"
"c = 3"
Tu peux aussi écrire des arguments avant et après *arguments pour définir des arguments positionnels spécifiques.
Cela fonctionne comme la décomposition d'un tableau.
Les arguments doivent respecter un ordre précis :
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <key-word_arguments>, **keyword_arguments)
Si tu ne respectes pas cet ordre, tu obtiendras une erreur.
def my_method(a, b, *arguments)
p a
p b
p arguments
end
my_method(1, 2, 3, 4, 5)
1
2
[3, 4, 5]
Tu peux écrire des arguments positionnels avant et après *arguments :
>> def my_method(a, *middle, b)= middle
>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]
Tu peux aussi combiner des arguments positionnels, *arguments, des arguments nommés et **keyword_arguments :
>> def my_method(first, *many, last, a:, **keyword_arguments)
p first
p many
p last
p a
p keyword_arguments
end
>> my_method(1, 2, 3, 4, 5, a: 6, b: 7, c: 8)
1
[2, 3, 4]
5
6
{:b => 7, :c => 8}
Écrire les arguments dans un ordre incorrect provoquera une erreur :
def my_method(a:, **keyword_arguments, first, *arguments, last)
arguments
end
my_method(1, 2, 3, 4, a: 5)
syntax error, unexpected local variable or method, expecting & or '&'
... my_method(a:, **keyword_arguments, first, *arguments, last)
Tu peux utiliser l'opérateur splat (*) pour décomposer un tableau d'arguments dans un appel de méthode :
def my_method(a, b, c)
p c
p b
p a
end
numbers = [1, 2, 3]
my_method(*numbers)
3
2
1
Tu peux aussi utiliser l'opérateur double splat (**) pour décomposer un hash d'arguments dans un appel de méthode :
def my_method(a:, b:, c:)
p c
p b
p a
end
numbers = {a: 1, b: 2, c: 3}
my_method(**numbers)
3
2
1