La descomposición se refiere al acto de extraer los elementos de una colección, como un Array o un Hash.
Los valores descompuestos se pueden asignar después a variables dentro de la misma instrucción.
La asignación múltiple es la capacidad de asignar varias variables para descomponer valores dentro de una sola instrucción.
Esto permite que el código sea más conciso y legible, y se consigue separando con una coma las variables que se van a asignar, como en first, second, third = [1, 2, 3].
El operador splat (*) y el operador doble splat (**) se usan a menudo en contextos de descomposición.
No debes confundir *<variable_name> y **<variable_name> con * y **.
Mientras que * y ** se usan para la multiplicación y la exponenciación, respectivamente, *<variable_name> y **<variable_name> se usan como operadores de composición y descomposición.
La asignación múltiple te permite asignar varias variables en una sola línea.
Para separar los valores, usa una coma ,:
>> a, b = 1, 2
=> [1, 2]
>> a
=> 1
La asignación múltiple no se limita a un solo tipo de datos:
>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true
La asignación múltiple se puede usar para intercambiar elementos en arrays. Esta práctica es bastante común en los algoritmos de ordenación. Por ejemplo:
>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Esto también se conoce como «asignación paralela» y se puede usar para evitar una variable temporal.
Si hay más variables que valores, a las variables sobrantes se les asignará nil:
>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil
En Ruby, es posible descomponer los elementos de arrays/hashes en variables distintas. Como los valores aparecen dentro de los arrays en un orden de índices, se desempaquetan en variables en ese mismo orden:
>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"
Si hay valores que no necesitas, puedes usar _ para indicar «recogido pero no usado»:
>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"
Descomponer y asignar valores de arrays que están dentro de otro array (también conocido como array anidado) funciona igual que una descomposición superficial, pero necesita una expresión de descomposición delimitada (()) para aclarar el contexto o la posición de los valores:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"
También puedes desempaquetar en profundidad solo una parte de un array anidado:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"
Si la descomposición tiene variables colocadas de forma incorrecta o un número incorrecto de valores, obtendrás un error de sintaxis:
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 aquí y te darás cuenta de que lo que manda es el primer patrón, no los valores disponibles en el lado derecho. El error de sintaxis no está ligado a la estructura de datos.
*)Al descomponer un array puedes usar el operador splat (*) para capturar los valores «sobrantes».
Esto es más claro que cortar el array (lo que en algunas situaciones es menos legible).
Por ejemplo, podemos extraer el primer elemento y luego asignar los valores restantes a un nuevo array sin el primer elemento:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]
También podemos extraer los valores del principio y del final del array mientras agrupamos todos los valores intermedios:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]
También podemos usar * en la descomposición profunda:
>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]
Hash
Descomponer un hash es un poco distinto de descomponer un array. Para poder desempaquetar un hash, primero tienes que convertirlo en un array. De lo contrario, no habrá descomposición:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil
Para convertir un Hash en un array, puedes usar el 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]
Si quieres desempaquetar las claves, puedes usar el método keys:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple
Si quieres desempaquetar los valores, puedes usar el método values:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6
Componer es la capacidad de agrupar varios valores en un solo array que se asigna a una variable. Esto es útil cuando quieres descomponer valores, hacer cambios y luego componer los resultados de nuevo en una variable. También permite fusionar 2 o más arrays/hashes.
*)Puedes componer un array con el operador splat (*).
Esto empaquetará todos los valores en un 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"]
**)Componer un hash se hace con el operador doble splat (**).
Esto empaqueta todos los pares clave/valor de un hash en otro hash, o combina dos hashes entre sí.
>> 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}
*) y del operador doble splat (**) con métodosCuando creas un método que acepta un número arbitrario de argumentos, puedes usar *arguments o **keyword_arguments en la definición del método.
*arguments se usa para empaquetar un número arbitrario de argumentos posicionales (sin palabra clave) y
**keyword_arguments se usa para empaquetar un número arbitrario de argumentos de palabra clave.
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}
Si el método definido no tiene ningún parámetro definido para argumentos de palabra clave (**keyword_arguments o <key_word>: <value>), los argumentos de palabra clave se empaquetarán en un hash y se asignarán al último parámetro.
>> def my_method(a)= a
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
*arguments y **keyword_arguments también se pueden usar combinados entre sí:
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"
También puedes escribir argumentos antes y después de *arguments para permitir argumentos posicionales concretos.
Esto funciona igual que descomponer un array.
Los argumentos tienen que estructurarse en un orden concreto:
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)
Si no sigues este orden, obtendrás un error.
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]
Puedes escribir argumentos posicionales antes y después de *arguments:
>> def my_method(a, *middle, b)= middle
>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]
También puedes combinar argumentos posicionales, *arguments, argumentos de palabra clave y **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}
Escribir los argumentos en un orden incorrecto dará lugar a un error:
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)
Puedes usar el operador splat (*) para desempaquetar un array de argumentos en una llamada a un 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
También puedes usar el operador doble splat (**) para desempaquetar un hash de argumentos en una llamada a un 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
Tu amigo Linus es ingeniero de locomotoras y conduce trenes de mercancías entre ciudades. Aunque es increíble manejando trenes, no lo es tanto con la logística ni con los ordenadores. Le gustaría contar con tu ayuda como programador para organizar los datos de los trenes y corregir errores en los datos de las rutas.
Este ejercicio podría resolverse fácilmente usando slicing, indexación y varios métodos de hash.
Sin embargo, nos gustaría que practicaras el empaquetado, el desempaquetado y la asignación múltiple al resolver cada una de las tareas que vienen a continuación.
Tu amigo ha ido anotando el identificador (ID) de cada vagón, pero nunca está seguro de cuántos vagones tendrá que procesar el sistema en un momento dado. Para el resto del programa de logística sería mucho más fácil tener estos datos empaquetados en un único array.
Implementa un método generate_list_of_wagons() que acepte un número arbitrario de IDs de vagones.
Cada ID será un número entero positivo.
El método debe entonces hacer return de los IDs dados como un único array.
LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]
A estas alturas, ya empiezas a hacerte una idea de los datos y de cómo se usan en el programa de logística. El sistema de IDs siempre asigna a la locomotora el ID 1, y al resto de los vagones del tren les asigna un ID elegido al azar mayor que 1.
¡Tu amigo tuvo que enganchar dos vagones nuevos al tren y se olvidó de actualizar el sistema! Ahora hay que mover los dos primeros vagones del array del tren al final, o todo quedará desordenado.
Para complicar más las cosas, tu amigo acaba de descubrir un segundo array que parece contener los IDs de vagones que faltan. Lo único que recuerda es que, una vez movidos los vagones nuevos, los IDs de este segundo array deben colocarse justo después de la locomotora designada.
Linus te estaría muy agradecido si corrigieras sus errores y consolidaras los datos.
Implementa un método fix_list_of_wagons() que reciba dos arrays con IDs de vagones.
Debe reubicar los dos primeros elementos del primer array al final, e insertar los valores del segundo array detrás (a la derecha) del ID de la locomotora (1).
El método debe entonces hacer return de un array con las modificaciones.
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]
Ahora que todos los datos de los vagones son correctos, a Linus le gustaría que actualizaras la información de rutas del sistema. A lo largo de una ruta de transporte, un tren puede hacer paradas en varias estaciones distintas para recoger y/o dejar mercancías. Cada trayecto puede tener una cantidad distinta de estos puntos intermedios de entrega. Tu amigo quiere que actualices el hash de rutas del sistema con cualquier información de entrega que falte o que sea adicional.
Implementa un método add_missing_stops() que acepte un hash de rutas seguido de un número variable de argumentos de palabra clave.
Estos argumentos pueden tener la forma de un hash con una o más paradas, o de cualquier número de pares de palabra clave stop_<number>: "city".
Tu método debe entonces devolver el hash de rutas actualizado con una clave adicional que contenga un array con todas las paradas añadidas, en orden.
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"]}
Las paradas siempre se pasarán en el orden correcto, así que no tienes que preocuparte por ordenarlas.
Linus ha estado trabajando en el programa de rutas y se ha dado cuenta de que a ciertas rutas les faltan algunos detalles importantes. La información inicial de la ruta se ha construido como un hash y tu amigo quiere que actualices ese hash con lo que sea que falte. Cada ruta del sistema requiere detalles ligeramente distintos, así que Linus preferiría claramente una solución genérica.
Implementa un método llamado extend_route_information() que acepte dos hashes.
El primer hash contiene las ciudades de origen y destino entre las que discurre la ruta del tren.
El segundo hash contiene otros detalles de la ruta, como la velocidad, la longitud o la temperatura del tren. El método debe devolver un hash consolidado con toda la información de la ruta.
El segundo hash puede contener propiedades distintas o más que las que se muestran en el ejemplo.
LocomotiveEngineer.extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
# => {"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Regístrate en Exercism para aprender y dominar Ruby con 21 conceptos121 ejercicios y mentoría humana real, todo gratis.