A decomposição é o ato de extrair os elementos de uma coleção, como um Array ou um Hash.
Os valores decompostos podem depois ser atribuídos a variáveis na mesma instrução.
A atribuição múltipla é a capacidade de atribuir várias variáveis para decompor valores numa única instrução.
Isto permite escrever código mais conciso e legível, e faz-se separando com uma vírgula as variáveis que vão ser atribuídas, como em first, second, third = [1, 2, 3].
O operador splat (*) e o operador splat duplo (**) são usados frequentemente em contextos de decomposição.
O operador splat (*) pode ser usado para combinar vários arrays num só array, decompondo cada um num novo array comum.
O operador splat duplo (**) pode ser usado para combinar vários hashes num só hash, decompondo cada um num novo hash comum.
É uma sintaxe usada para distinguir vários valores aceites como argumento posicional de uma situação em que aceitamos um ou vários argumentos de palavra-chave.
Quando o operador splat (*) é usado sem uma coleção, empacota (ou compõe) vários valores num array.
Isto é usado frequentemente na atribuição múltipla para agrupar numa única variável todos os elementos «restantes» que não têm uma atribuição individual.
Em Ruby, é comum usar este comportamento de decomposição/composição ao usar ou definir métodos que recebem um número arbitrário de argumentos posicionais ou de palavra-chave.
Vais ver muitas vezes estes argumentos definidos como def some_method(*arguments, **keyword_arguments) e usados como some_method(*some_array, **some_hash).
Não deves confundir *<variable_name> e **<variable_name> com * e **.
Enquanto * e ** são usados para a multiplicação e a exponenciação, respetivamente, *<variable_name> e **<variable_name> são usados como operadores de composição e de decomposição.
A atribuição múltipla permite-te atribuir várias variáveis numa só linha.
Para separar os valores, usa uma vírgula ,:
>> a, b = 1, 2
=> [1, 2]
>> a
=> 1
A atribuição múltipla não se limita a um único tipo de dados:
>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true
A atribuição múltipla pode ser usada para trocar elementos entre arrays. Esta prática é bastante comum em algoritmos de ordenação. Por exemplo:
>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Isto também é conhecido como «atribuição paralela» e pode ser usado para evitar uma variável temporária.
Se houver mais variáveis do que valores, as variáveis a mais recebem nil:
>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil
Se houver mais valores do que variáveis, os valores a mais são ignorados:
>> a, b, c = 1, 2, 3, 4
>> b
=> 2
>> c
=> 3
Em Ruby, é possível decompor os elementos de arrays/hashes em variáveis distintas. Como os valores aparecem nos arrays por ordem de índice, são desempacotados para as variáveis pela mesma ordem:
>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"
Se houver valores de que não precisas, podes usar _ para indicar «recolhido mas não usado»:
>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"
Decompor e atribuir valores de arrays dentro de um array (também conhecido como array aninhado) funciona da mesma forma que uma decomposição simples, mas precisa de uma expressão de decomposição delimitada (()) para clarificar o contexto ou a posição dos valores:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"
Também podes desempacotar apenas uma parte de um array aninhado:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"
Se a decomposição tiver variáveis mal colocadas e/ou um número incorreto de valores, vais obter um erro de sintaxe:
fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
(a, b), (d) = fruits_vegetables
syntax error, unexpected ')', expecting '.' or &. or :: or '['
((a, b), (d)) = fruits_vegetables
Experimenta aqui e vais reparar que é o primeiro padrão que manda, e não os valores disponíveis do lado direito. O erro de sintaxe não está relacionado com a estrutura de dados.
*)Ao [decompor um array][decomposition] podes usar o operador splat (*) para capturar os valores «sobrantes».
Isto é mais claro do que fatiar o array (que em algumas situações é menos legível).
Por exemplo, podemos extrair o primeiro elemento e depois atribuir os valores restantes a um novo array sem o primeiro elemento:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]
Também podemos extrair os valores do início e do fim do array, agrupando todos os valores do meio:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]
Também podemos usar * numa decomposição profunda:
>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]
Hash
Decompor um hash é um pouco diferente de decompor um array. Para conseguires desempacotar um hash, tens primeiro de o converter num array. Caso contrário, não há decomposição:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil
Para converter um Hash num array, podes usar o método 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]
Se quiseres desempacotar as chaves, podes usar o método keys:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple
Se quiseres desempacotar os valores, podes usar o método values:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6
Compor é a capacidade de agrupar vários valores num só array que é atribuído a uma variável. Isto é útil quando queres decompor valores, fazer alterações e depois compor os resultados de novo numa variável. Também torna possível fazer fusões de dois ou mais arrays/hashes.
*)Podes compor um array com o operador splat (*).
Isto vai empacotar todos os valores num array.
>> 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"]
**)Compor um hash faz-se com o operador splat duplo (**).
Isto vai empacotar todos os pares chave/valor de um hash noutro hash, ou combinar dois hashes.
>> 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}
*) e do operador splat duplo (**) com métodosQuando crias um método que aceita um número arbitrário de argumentos, podes usar *arguments ou **keyword_arguments na definição do método.
*arguments é usado para empacotar um número arbitrário de argumentos posicionais (não associados a palavras-chave) e **keyword_arguments é usado para empacotar um número arbitrário de argumentos de palavra-chave.
Utilização 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"]
Utilização 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}
Se o método definido não tiver parâmetros definidos para argumentos de palavra-chave (**keyword_arguments ou <key_word>: <value>), os argumentos de palavra-chave são empacotados num hash e atribuídos ao último parâmetro.
>> def my_method(a)= a
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
*arguments e **keyword_arguments também podem ser usados em conjunto:
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"
Também podes escrever argumentos antes e depois de *arguments para permitir argumentos posicionais específicos.
Isto funciona da mesma forma que decompor um array.
Os argumentos têm de ser estruturados por uma ordem específica:
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <key-word_arguments>, **keyword_arguments)
Se não seguires esta ordem, vais obter um erro.
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]
Podes escrever argumentos posicionais antes e depois de *arguments:
>> def my_method(a, *middle, b)= middle
>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]
Também podes combinar argumentos posicionais, *arguments, argumentos de palavra-chave e **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}
Escrever os argumentos por uma ordem incorreta dá um erro:
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)
Podes usar o operador splat (*) para desempacotar um array de argumentos numa chamada de método:
def my_method(a, b, c)
p c
p b
p a
end
numbers = [1, 2, 3]
my_method(*numbers)
3
2
1
Também podes usar o operador splat duplo (**) para desempacotar um hash de argumentos numa chamada de método:
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