Rutas
/
Python
Python
/
Ejercicios
/
Maquinista de locomotora
Maquinista de locomotora

Maquinista de locomotora

Ejercicio de aprendizaje

Introducción

El desempaquetado se refiere al acto de extraer los elementos de una colección, como un list, tuple o dict, mediante iteración. Los valores desempaquetados se pueden asignar después a variables dentro de la misma instrucción, lo que se conoce comúnmente como asignación múltiple.

Los operadores especiales * y ** se usan a menudo en contextos de desempaquetado y con la asignación múltiple.

Caution

No hay que 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 empaquetado y desempaquetado.

Asignación múltiple

En la asignación múltiple, el número de variables del lado izquierdo del operador de asignación (=) debe coincidir con el número de valores del lado derecho. Para separar los valores, usa una coma ,:

>>> a, b = 1, 2
>>> a
1

Si la asignación múltiple recibe un número incorrecto de variables para los valores dados, se lanzará un ValueError:

>>> x, y, z = 1, 2

ValueError: too many values to unpack (expected 3, got 2)

La asignación múltiple no se limita a un solo tipo de datos:

>>> x, y, z = 1, "Hello", True
>>> x
1

>>> y
'Hello'

>>> z
True

La asignación múltiple se puede usar para intercambiar elementos en lists. Esta práctica es bastante común en los algoritmos de ordenación. Por ejemplo:

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

Como las tuples son inmutables, no puedes intercambiar elementos en una tuple.

Desempaquetado

Note

Los ejemplos siguientes usan lists, pero los mismos conceptos se aplican a tuples.

En Python, es posible desempaquetar los elementos de list/tuple/dictionary en variables distintas. Como los valores aparecen dentro de lists/tuples en un orden específico, 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 marcarlos:

>>> fruits = ["apple", "banana", "cherry"]
>>> _, _, z = fruits
>>> z
"cherry"

Desempaquetado profundo

Desempaquetar y asignar valores de un list/tuple incluido dentro de un list o tuple (también conocidos como arrays/tuplas anidados) funciona igual que un desempaquetado superficial, pero a menudo necesita calificadores para aclarar el contexto o la posición:

>>> 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 list/tuple anidado:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [a, [c, d]] = fruits_vegetables
>>> a
["apple", "banana"]

>>> c
"carrot"

Si el desempaquetado tiene variables con una colocación incorrecta o un número incorrecto de valores, obtendrás un ValueError:

>>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>>> [[a, b], [d]] = fruits_vegetables

ValueError: too many values to unpack (expected 1)

Desempaquetar un array/tupla con *

Al desempaquetar un list/tuple puedes usar el operador * para capturar los valores «sobrantes». Esto es más claro que cortar el list/tuple (lo que en algunas situaciones es menos legible). Por ejemplo, se puede extraer el primer elemento y luego colocar los valores restantes en un list nuevo 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 al principio y al final del list mientras agrupamos todos los valores del medio:

>>> 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 el desempaquetado profundo:

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

>>> rest
["banana", "melon"]

Desempaquetar un diccionario

El desempaquetado de un diccionario es un poco diferente del desempaquetado de un list/tuple. La iteración sobre diccionarios usa las claves de forma predeterminada. Así que al desempaquetar un dict solo puedes desempaquetar las claves, y no los valores:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory
>>> x
"apple"

Si quieres desempaquetar los valores, puedes usar el método <dict>.values():

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.values()
>>> x
6

Si necesitas tanto las claves como los valores, usa el método <dict>.items(). <dict>.items() genera una vista iterable que contiene pares clave-valor. Estos se pueden desempaquetar en un tuple:

>>> fruits_inventory = {"apple": 6, "banana": 2, "cherry": 3}
>>> x, y, z = fruits_inventory.items()
>>> x
("apple", 6)

Empaquetado

El empaquetado es la capacidad de agrupar varios valores en un solo list que se asigna a una variable. Esto es útil cuando quieres desempaquetar valores, hacer cambios y luego empaquetar los resultados de nuevo en una variable. También permite combinar 2 o más lists/tuples/dicts.

Empaquetar un array/tupla con *

Empaquetar un list/tuple se puede hacer con el operador *. Esto empaquetará todos los valores en un list/tuple.

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

# If there is no * on to the left of the "=" the result is a tuple
>>> combined_fruits
("apple", "banana", "cherry", "orange", "kiwi", "melon", "mango")

# If the * operator is used on the left side of "=" the result is a list.
# Note the trailing comma.
>>> *combined_fruits_too, = *fruits, *more_fruits
>>> combined_fruits_too
['apple', 'banana', 'cherry', 'orange', 'kiwi', 'melon', 'mango']

Para más información sobre el uso de * en el lado izquierdo, consulta PEP 3132.

Empaquetar un diccionario con **

Empaquetar un diccionario se hace con el operador **. Esto empaquetará todos los pares clave-valor de un diccionario en otro diccionario, o combinará dos diccionarios 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}

Uso de * y ** con funciones

Empaquetar con parámetros de función

Cuando creas una función que acepta un número arbitrario de argumentos, puedes usar *args o **kwargs en la definición de la función. *args se usa para empaquetar un número arbitrario de argumentos posicionales (no de palabra clave) como un tuple, y **kwargs se usa para empaquetar un número arbitrario de argumentos de palabra clave como un diccionario.

Uso de *args:

# This function is defined to take any number of positional arguments

>>> def my_function(*args):
...     print(args)

# Arguments given to the function are packed into a tuple

>>> my_function(1, 2, 3)
(1, 2, 3)

>>> my_function("Hello")
("Hello")

>>> my_function(1, 2, 3, "Hello", "Mars")
(1, 2, 3, "Hello", "Mars")

Uso de **kwargs:

# This function is defined to take any number of keyword arguments

>>> def my_function(**kwargs):
...   print(kwargs)

# Arguments given to the function are packed into a dictionary

>>> my_function(a=1, b=2, c=3)
{"a": 1, "b": 2, "c": 3}

*args y **kwargs también se pueden usar en combinación entre sí:

>>> def my_function(*args, **kwargs):
...   print(sum(args))
...   for key, value in kwargs.items():
...       print(str(key) + " = " + str(value))

>>> my_function(1, 2, 3, a=1, b=2, c=3)
6
a = 1
b = 2
c = 3

También puedes escribir parámetros antes de *args para permitir argumentos posicionales específicos. En ese caso, los argumentos de palabra clave individuales tienen que aparecer antes de **kwargs.

Caution

Los argumentos tienen que estructurarse así:

def my_function(<positional_args>, *args, <key-word_args>, **kwargs)

Si no sigues este orden, obtendrás un error.

>>> def my_function(a, b, *args):
...   print(a)
...   print(b)
...   print(args)

>>> my_function(1, 2, 3, 4, 5)
1
2
(3, 4, 5)

Escribir los argumentos en un orden incorrecto dará lugar a un error:

>>>def my_function(*args, a, b):
... print(args)

>>>my_function(1, 2, 3, 4, 5)
Traceback (most recent call last):
  File "c:\something.py", line 3, in <module>
    my_function(1, 2, 3, 4, 5)
TypeError: my_function() missing 2 required keyword-only arguments: 'a' and 'b'

Desempaquetar en llamadas a funciones

Puedes usar * para desempaquetar un list/tuple de argumentos en una llamada a función. Esto es muy útil para funciones que no aceptan un iterable:

>>> def my_function(a, b, c):
...   print(c)
...   print(b)
...   print(a)

numbers = [1, 2, 3]
>>> my_function(*numbers)
3
2
1

Usar el desempaquetado con * junto con la función integrada zip() es otro caso de uso habitual. La función zip() toma varios iterables y devuelve un list de tuples con los valores de cada iterable agrupados:

>>> values = (['x', 'y', 'z'], [1, 2, 3], [True, False, True])
>>> a, *rest = zip(*values)
>>> rest
[('y', 2, False), ('z', 3, True)]

Instrucciones

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 detalles de los trenes y corregir errores en los datos de las rutas.

Note

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

1. Crea un array con todos los vagones

Tu amigo ha ido registrando 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 list.

Implementa una función get_list_of_wagons() que acepte un número arbitrario de IDs de vagones. Cada ID será un número entero positivo. La función debe entonces return los IDs dados como un único list.

>>> get_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
[1, 7, 12, 3, 14, 8, 5]

2. Corrige el array de vagones

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, mientras que 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 list del tren al final, o todo quedará desordenado.

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

Linus te estaría muy agradecido si corrigieras sus errores y consolidaras los datos.

Implementa una función fix_list_of_wagons() que reciba dos lists con IDs de vagones. Debe recolocar los dos primeros elementos del primer list al final, e insertar los valores del segundo list detrás (a la derecha de) del ID de la locomotora (1). La función debe entonces return un list con las modificaciones.

>>> 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. Añade las paradas que faltan

Ahora que todos los datos de los vagones son correctos, a Linus le gustaría 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 distintas para recoger y/o dejar mercancía. Cada trayecto puede tener un número distinto de estos puntos intermedios de entrega. Tu amigo quiere que actualices el dict de rutas del sistema con cualquier información de entrega que falte o que sea adicional.

Implementa una función add_missing_stops() que acepte un dict de rutas seguido de un número variable de argumentos de palabra clave. Estos argumentos pueden tener la forma de un dict que contenga una o más paradas, o de cualquier número de pares de palabra clave stop_number=city. Tu función debe entonces devolver el dict de rutas actualizado con un key adicional que contenga un list con todas las paradas añadidas, en orden.

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

4. Amplía la información de rutas

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 dict y tu amigo quiere que actualices ese dict con lo que pueda faltar. Cada ruta del sistema requiere detalles ligeramente distintos, así que Linus preferiría una solución genérica.

Implementa una función llamada extend_route_information() que acepte dos dicts. El primer dict contiene las ciudades de origen y destino entre las que discurre la ruta del tren.

El segundo dict contiene otros detalles de la ruta, como la velocidad, la longitud o la temperatura del tren. La función debe devolver un dict consolidado con toda la información de la ruta.

Note

El segundo dict puede contener propiedades distintas o más propiedades que las mostradas en el ejemplo.

>>> extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
{"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}

5. Corrige el depósito de vagones

Cuando Linus estaba inspeccionando el depósito de vagones, se dio cuenta de que los vagones no se estaban guardando en el orden correcto. Además de un ID, cada vagón tiene un color que se corresponde con el tipo de mercancía que transporta. Los vagones se guardan en el depósito formando cuadrículas, donde cada columna de la cuadrícula tiene vagones del mismo color.

Sin embargo, el sistema de logística muestra que los lists de vagones que se van a guardar en el depósito tienen sus filas agrupadas por color. Pero para que la cuadrícula de almacenamiento funcione correctamente, cada fila debe tener tres colores distintos, de modo que las columnas queden alineadas por color. Tu amigo quiere que ordenes los lists del depósito de vagones, para que los vagones se guarden correctamente.

Implementa una función llamada fix_wagon_depot() que acepte un list de tres elementos. Cada elemento del list es una sublista (o «fila») que contiene tres tuples. Cada tuple es un par (<wagon ID>, <wagon color>).

Tu función debe devolver un list con los tres lists de «fila» reordenados para que los vagones queden intercambiados en sus posiciones correctas.

>>> fix_wagon_depot([
                    [(2, "red"), (4, "red"), (8, "red")],
                    [(5, "blue"), (9, "blue"), (13,"blue")],
                    [(3, "orange"), (7, "orange"), (11, "orange")],
                    ])

[
[(2, "red"), (5, "blue"), (3, "orange")],
[(4, "red"), (9, "blue"), (7, "orange")],
[(8, "red"), (13,"blue"), (11, "orange")]
]
Editar en GitHub El enlace se abre en una ventana o pestaña nueva
Python Exercism

¿Listo para empezar Maquinista de locomotora?

Regístrate en Exercism para aprender y dominar Python con 17 conceptos146 ejercicios y mentoría humana real, todo gratis.