A decomposição se refere ao ato de extrair os elementos de uma coleção, como um Array ou um Hash.
Os valores decompostos podem então 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 em uma única instrução.
Isso deixa o código mais conciso e legível, e é feito separando as variáveis a serem atribuídas com uma vírgula, como em first, second, third = [1, 2, 3].
O operador splat (*) e o operador splat duplo (**) são usados com frequência 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, respectivamente, *<variable_name> e **<variable_name> são usados como operadores de composição e decomposição.
A atribuição múltipla permite atribuir várias variáveis em uma linha.
Para separar os valores, use 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 dado:
>> 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. Essa prática é bem 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]
Isso 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 extras receberão 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 dentro dos arrays em uma ordem de índices, eles são desempacotados nas 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, você pode usar _ para indicar "coletado 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 superficial, mas precisa de uma expressão de decomposição delimitada (()) para deixar claro o contexto ou a posição dos valores:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"
Você também pode desempacotar profundamente 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 posicionamento incorreto e/ou um número incorreto de valores, você vai receber 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 '['
Experimente aqui e você vai perceber 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, você pode usar o operador splat (*) para capturar os valores "que sobram".
Isso é 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 do início e do fim do array enquanto agrupamos 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 * na 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 conseguir desempacotar um hash, você precisa primeiro convertê-lo em um array. Caso contrário, não haverá 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 em um array, você pode 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 você quiser desempacotar as chaves, pode usar o método keys:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple
Se você quiser desempacotar os valores, pode 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 em um único array que é atribuído a uma variável. Isso é útil quando você quer decompor valores, fazer alterações e depois compor os resultados de volta em uma variável. Também torna possível fazer mesclagens de 2 ou mais arrays/hashes.
*)Compor um array pode ser feito usando o operador splat (*).
Ele empacota todos os valores em um 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 é feito usando o operador splat duplo (**).
Ele empacota todos os pares chave/valor de um hash em outro 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 você cria um método que aceita um número arbitrário de argumentos, pode usar *arguments ou **keyword_arguments na definição do método.
*arguments é usado para empacotar um número arbitrário de argumentos posicionais (que não são de palavra-chave) e
**keyword_arguments é usado para empacotar um número arbitrário de argumentos de palavra-chave.
Uso 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"]
Uso 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 nenhum parâmetro definido para argumentos de palavra-chave (**keyword_arguments ou <key_word>: <value>), os argumentos de palavra-chave serão empacotados em um 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 combinação 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"
Você também pode escrever argumentos antes e depois de *arguments para permitir argumentos posicionais específicos.
Isso funciona da mesma forma que decompor um array.
Os argumentos precisam ser estruturados em uma ordem específica:
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)
Se você não seguir essa ordem, vai 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]
Você pode 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]
Você também pode 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 em uma ordem incorreta resulta em 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)
Você pode usar o operador splat (*) para desempacotar um array de argumentos em uma 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
Você também pode usar o operador splat duplo (**) para desempacotar um hash de argumentos em uma 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
Seu amigo Linus é maquinista e conduz trens de carga entre cidades. Apesar de ser incrível conduzindo trens, o mesmo não se pode dizer de logística ou computadores. Linus gostaria de contar com a sua ajuda com programação para organizar os detalhes dos trens e corrigir erros nos dados das rotas.
Este exercício poderia ser resolvido facilmente com fatiamento, indexação e vários métodos de hash. Mas queremos que você pratique empacotamento, desempacotamento e atribuição múltipla para resolver cada uma das tarefas abaixo.
Seu amigo vem anotando o identificador (ID) de cada vagão, mas nunca tem certeza de quantos vagões o sistema vai ter que processar em um dado momento. Para o resto do programa de logística, seria muito mais fácil ter esses dados empacotados em um único array.
Implemente um método generate_list_of_wagons() que recebe uma quantidade arbitrária de IDs de vagões.
Cada ID será um número inteiro positivo.
O método deve então return os IDs informados como um único array.
LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]
Neste ponto, você já está começando a entender os dados e como eles são usados no programa de logística. O sistema de IDs sempre atribui à locomotiva o ID 1, e o restante dos vagões do trem recebe um ID escolhido aleatoriamente, maior que 1.
Seu amigo precisou acoplar dois vagões novos ao trem e esqueceu de atualizar o sistema! Agora, os dois primeiros vagões do array do trem precisam ser movidos para o final, senão tudo vai ficar fora de ordem.
Para complicar mais, seu amigo acabou de encontrar um segundo array que parece conter IDs de vagões que estão faltando. Linus só se lembra de que, depois que os vagões novos forem movidos, os IDs desse segundo array devem ser colocados logo depois da locomotiva indicada.
Linus vai te agradecer muito por corrigir os erros e consolidar os dados.
Implemente um método fix_list_of_wagons() que recebe dois arrays contendo IDs de vagões.
Ele deve reposicionar os dois primeiros itens do primeiro array para o final e inserir os valores do segundo array atrás (no lado direito) do ID da locomotiva (1).
O método deve então 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, Linus gostaria que você atualizasse as informações de roteamento do sistema. Ao longo de uma rota de transporte, um trem pode parar em algumas estações diferentes para pegar e/ou deixar carga. Cada viagem pode ter uma quantidade diferente desses pontos de entrega intermediários. Seu amigo gostaria que você atualizasse o hash de roteamento do sistema com qualquer informação de entrega que esteja faltando ou que seja adicional.
Implemente um método add_missing_stops() que recebe um hash de roteamento seguido de um número variável de argumentos de palavra-chave.
Esses argumentos podem estar na forma de um hash contendo uma ou mais paradas, ou de qualquer número de pares de palavra-chave stop_<number>: "city".
Seu método deve então retornar o hash de roteamento atualizado com uma chave adicional que contém um array com todas as paradas adicionadas, na ordem.
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 paradas sempre serão passadas na ordem correta, então você não precisa se preocupar em ordená-las.
Linus tem trabalhado no programa de roteamento e percebeu que algumas rotas estão sem detalhes importantes. As informações iniciais da rota foram montadas como um hash, e seu amigo gostaria que você atualizasse esse hash com tudo o que estiver faltando. Cada rota do sistema exige detalhes um pouco diferentes, então Linus preferiria muito uma solução genérica.
Implemente um método chamado extend_route_information() que recebe dois hashes.
O primeiro hash contém as cidades de origem e destino entre as quais a rota do trem passa.
O segundo hash contém outros detalhes da rota, como velocidade, comprimento ou temperatura do trem. O método deve retornar um hash consolidado com todas as informações da rota.
O segundo hash pode conter propriedades diferentes ou em maior número 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"}
Crie sua conta no Exercism para aprender e dominar Ruby com 21 conceitos121 exercícios e mentoria humana de verdade, tudo de graça.