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 torna o código mais conciso e legível, e faz-se separando com uma vírgula as variáveis a atribuir, como em first, second, third = [1, 2, 3].
O operador splat (*) e o operador splat duplo (**) são frequentemente usados em contextos de decomposição.
*<variable_name> e **<variable_name> não devem ser confundidos com * e **.
Enquanto * e ** são usados para multiplicação e exponenciação, respetivamente, *<variable_name> e **<variable_name> são usados como operadores de composição e 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 em 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 extra recebem nil:
>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil
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 na mesma ordem:
>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"
Se houver valores que não são necessários, 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 com uma posição incorreta e/ou um número incorreto de valores, obténs 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
# syntax error, unexpected ')', expecting '.' or &. or :: or '['
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á ligado à estrutura de dados.
*)Ao decompor um array, podes usar o operador splat (*) para capturar os valores "restantes".
Isto é mais claro do que fatiar o array (o 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 no início e no fim do array, agrupando ao mesmo tempo 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 * em 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 de o converter primeiro 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 único array que é atribuído a uma variável. Isto é útil quando queres decompor valores, fazer alterações e depois compor novamente os resultados numa variável. Também permite juntar 2 ou mais arrays/hashes.
*)Compor um array pode ser feito com o operador splat (*).
Isto empacota 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 empacota todos os pares chave/valor de um hash noutro hash, ou combina dois hashes entre si.
>> 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 nomeados) 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 quaisquer 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 um com o outro:
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>, <keyword_arguments>, **keyword_arguments)
Se não seguires esta ordem, vais receber 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 numa ordem incorreta resulta num 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
O teu amigo Linus é maquinista e conduz comboios de mercadorias entre cidades. Apesar de ser fantástico a lidar com comboios, não é tão bom a lidar com logística ou computadores. Gostaria de contar com a tua ajuda na programação para organizar os detalhes dos comboios e corrigir erros nos dados das rotas.
Este exercício poderia ser facilmente resolvido com slicing, indexação e vários métodos de hash.
No entanto, gostaríamos que praticasses empacotamento, desempacotamento e atribuição múltipla ao resolver cada uma das tarefas abaixo.
O teu amigo tem estado a registar o identificador (ID) de cada vagão, mas nunca tem a certeza de quantos vagões o sistema vai ter de processar em cada momento. Seria muito mais fácil para o resto do programa de logística ter estes dados empacotados num único array.
Implementa um método generate_list_of_wagons() que aceita um número arbitrário de IDs de vagões.
Cada ID é um número inteiro positivo.
O método deve depois fazer return dos IDs recebidos como um único array.
LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]
A esta altura, já estás a começar a perceber os dados e a forma como são usados no programa de logística. O sistema de IDs atribui sempre à locomotiva o ID 1, e aos restantes vagões do comboio um ID escolhido aleatoriamente maior do que 1.
O teu amigo teve de ligar dois vagões novos ao comboio e esqueceu-se de atualizar o sistema! Agora, os dois primeiros vagões do array do comboio têm de ser movidos para o fim, senão fica tudo fora de ordem.
Para complicar mais as coisas, o teu amigo acabou de descobrir um segundo array que parece conter IDs de vagões em falta. Só se lembra de que, depois de mover os vagões novos, os IDs deste segundo array devem ser colocados imediatamente a seguir à locomotiva designada.
O Linus ficaria muito grato se corrigisses os erros dele e consolidares os dados.
Implementa um método fix_list_of_wagons() que recebe dois arrays com IDs de vagões.
Deve reposicionar os dois primeiros itens do primeiro array para o fim e inserir os valores do segundo array atrás (do lado direito) do ID da locomotiva (1).
O método deve depois return um array com as modificações.
LocomotiveEngineer.fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
# => [1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
Agora que todos os dados dos vagões estão corretos, o Linus gostaria que atualizasses as informações de rotas do sistema. Ao longo de uma rota de transporte, um comboio pode fazer paragens em várias estações diferentes para recolher e/ou deixar mercadorias. Cada viagem pode ter um número diferente destes pontos de entrega intermédios. O teu amigo gostaria que atualizasses o hash de rotas do sistema com qualquer informação de entrega em falta ou adicional.
Implementa um método add_missing_stops() que recebe um hash de rotas seguido de um número variável de argumentos de palavra-chave.
Estes argumentos podem ter a forma de um hash com uma ou mais paragens, ou de qualquer número de pares de palavra-chave stop_<number>: "city".
O teu método deve depois devolver o hash de rotas atualizado com uma chave adicional que contém um array com todas as paragens adicionadas, pela ordem correta.
LocomotiveEngineer.add_missing_stops({from: "New York", to: "Miami"},
stop_1: "Washington, DC", stop_2: "Charlotte", stop_3: "Atlanta",
stop_4: "Jacksonville", stop_5: "Orlando")
# => {from: "New York", to: "Miami", stops: ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
As paragens são sempre passadas pela ordem correta, por isso não precisas de te preocupar em ordená-las.
O Linus tem estado a trabalhar no programa de rotas e reparou que faltam alguns detalhes importantes em certas rotas. A informação inicial das rotas foi construída como um hash, e o teu amigo gostaria que atualizasses esse hash com tudo o que possa faltar. Cada rota do sistema exige detalhes ligeiramente diferentes, por isso o Linus preferia mesmo uma solução genérica.
Implementa um método chamado extend_route_information() que recebe dois hashes.
O primeiro hash contém as cidades de origem e de destino entre as quais o comboio circula.
O segundo hash contém outros detalhes da rota, como a velocidade, o comprimento ou a temperatura do comboio. O método deve devolver um hash consolidado com toda a informação de rotas.
O segundo hash pode conter propriedades diferentes ou mais do que as mostradas no exemplo.
LocomotiveEngineer.extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
# => {"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Inscreve-te no Exercism para aprenderes e dominares Ruby com 21 conceitos121 exercícios, e mentoria humana real, tudo grátis.