Tracks
/
Ruby
Ruby
/
Ejercicios
/
Ingeniero de locomotoras
Ingeniero de locomotoras

Ingeniero de locomotoras

Ejercicio de aprendizaje

Introducción

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 sentencia.

La asignación múltiple es la capacidad de asignar varias variables para descomponer valores en una sola sentencia. Esto permite que el código sea más conciso y legible, y se logra separando con una coma las variables que se van a asignar, así: first, second, third = [1, 2, 3].

El operador splat (*) y el operador doble splat (**) se usan a menudo en contextos de descomposición.

Caution

*<variable_name> y **<variable_name> no deben confundirse 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.

Asignación múltiple

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 dato:

>> 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 ordenamiento. Por ejemplo:

>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Note

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 adicionales se les asignará nil:

>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil

Descomposición

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 índice, 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"

Descomposición profunda

Descomponer y asignar valores de arrays 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 con una ubicación 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 notarás que lo que manda es el primer patrón, no los valores disponibles del lado derecho. El error de sintaxis no está atado a la estructura de datos.

Descomponer un array con el operador splat único (*)

Al descomponer un array puedes usar el operador splat (*) para capturar los valores «sobrantes». Esto es más claro que cortar el array (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 y agrupar 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 una descomposición profunda:

>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]

Descomponer un 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

Composición

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 combinar 2 o más arrays/hashes.

Componer un array con el operador splat (*)

Componer un array se puede hacer con el operador splat (*). Esto empaqueta 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 con el operador doble splat (**)

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.

>> 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}

Uso del operador splat (*) y del operador doble splat (**) con métodos

Componer con parámetros de métodos

Cuando creas un método que acepta una cantidad arbitraria de argumentos, puedes usar *arguments o **keyword_arguments en la definición del método. *arguments se usa para empaquetar una cantidad arbitraria de argumentos posicionales (no de palabra clave) y **keyword_arguments se usa para empaquetar una cantidad arbitraria 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 en combinación:

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 específicos. Esto funciona igual que descomponer un array.

Caution

Los argumentos tienen que estar en un orden específico:

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á como resultado 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)

Descomponer en llamadas a métodos

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

Instrucciones

Tu amigo Linus es un ingeniero de locomotoras que conduce trenes de carga entre ciudades. Aunque es increíble manejando trenes, no lo es tanto con la logística ni con las computadoras. Le gustaría contar con tu ayuda como programador para organizar los detalles de los trenes y corregir errores en los datos de las rutas.

Note

Este ejercicio se podría resolver fácilmente usando slicing, indexación y varios métodos de hash. Sin embargo, queremos que practiques el empaquetado, el desempaquetado y la asignación múltiple al resolver cada una de las tareas a continuación.

1. Crea un array con todos los vagones

Tu amigo ha estado llevando un registro del 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 una cantidad arbitraria 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]

2. Corrige la lista de vagones

En este punto, ya empiezas a tener 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 conectar dos vagones nuevos al tren y olvidó actualizar el sistema! Ahora, los dos primeros vagones del array del tren deben moverse al final, o todo quedará desordenado.

Para complicar más las cosas, tu amigo acaba de descubrir un segundo array que parece contener IDs de vagones faltantes. Lo único que recuerda es que, una vez que se muevan los vagones nuevos, los IDs de este segundo array deben colocarse justo después de la locomotora designada.

Linus te agradecería mucho que corrigieras sus errores y consolidaras los datos.

Implementa un método fix_list_of_wagons() que reciba dos arrays con IDs de vagones. Debe mover 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]

3. Agrega las paradas faltantes

Ahora que todos los datos de los vagones son correctos, Linus quiere que actualices la información de rutas del sistema. A lo largo de una ruta de transporte, un tren puede hacer paradas en varias estaciones diferentes para recoger y/o dejar carga. Cada viaje podría tener una cantidad diferente 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 sea adicional.

Implementa un método add_missing_stops() que acepte un hash de rutas seguido de una cantidad variable de argumentos de palabra clave. Estos argumentos pueden tener la forma de un hash que contenga una o más paradas, o cualquier cantidad 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 agregadas 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"]}
Note

Las paradas siempre se pasarán en el orden correcto, así que no tienes que preocuparte por ordenarlas.

4. Amplía la información de rutas

Linus ha estado trabajando en el programa de rutas y ha notado que ciertas rutas carecen de 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 pueda faltar. Cada ruta del sistema requiere detalles un poco diferentes, 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 circula 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.

Note

El segundo hash puede contener propiedades diferentes 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"}
Editar en GitHub El enlace se abre en una ventana o una pestaña nuevas
Ruby Exercism

¿Todo listo para empezar Ingeniero de locomotoras?

Regístrate en Exercism para aprender y dominar Ruby con 21 conceptos121 ejercicios y mentoría humana real, todo gratis.