Треки
/
Python
Python
/
Вправи
/
Машиніст локомотива
Машиніст локомотива

Машиніст локомотива

Навчальна вправа

Вступ

Розпакування означає вилучення елементів колекції, як-от list, tuple чи dict, за допомогою ітерації. Розпаковані значення потім можна присвоїти змінним у межах однієї інструкції, що зазвичай називають множинним присвоєнням.

Спеціальні оператори * та ** часто використовують у контексті розпакування та разом із множинним присвоєнням.

Caution

*<variable_name> і **<variable_name> не варто плутати з * та **. Якщо * та ** використовують для множення та піднесення до степеня відповідно, то *<variable_name> і **<variable_name> слугують операторами пакування та розпакування.

Множинне присвоєння

У множинному присвоєнні кількість змінних ліворуч від оператора присвоєння (=) має збігатися з кількістю значень праворуч. Щоб розділити значення, використовуємо кому ,:

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

Якщо у множинному присвоєнні вказано неправильну кількість змінних для наведених значень, виникне ValueError:

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

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

Множинне присвоєння не обмежується одним типом даних:

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

>>> y
'Hello'

>>> z
True

Множинне присвоєння можна використати, щоб поміняти місцями елементи в lists. Цей прийом досить поширений в алгоритмах сортування. Наприклад:

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

Оскільки tuples незмінні, поміняти місцями елементи в tuple не вдасться.

Розпакування

Note

У прикладах нижче використано lists, але ті самі поняття стосуються й tuples.

У Python можна розпакувати елементи list/tuple/dictionary в окремі змінні. Оскільки значення в lists/tuples мають певний порядок, їх розпаковують у змінні в тому самому порядку:

>>> fruits = ["apple", "banana", "cherry"]
>>> x, y, z = fruits
>>> x
"apple"

Якщо якісь значення не потрібні, їх можна позначити символом _:

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

Глибоке розпакування

Розпакування й присвоєння значень із list/tuple, вкладеного в list або tuple (які ще називають вкладеними масивами/кортежами), працює так само, як і поверхневе розпакування, але часто потребує уточнень, щоб прояснити контекст або позицію:

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

>>> d
"potato"

Можна також глибоко розпакувати лише частину вкладеного list/tuple:

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

>>> c
"carrot"

Якщо під час розпакування змінні розташовані неправильно та/або кількість значень неправильна, виникне ValueError:

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

ValueError: too many values to unpack (expected 1)

Розпакування list/tuple за допомогою *

Коли ми розпаковуємо list/tuple, можна використати оператор *, щоб захопити «залишкові» значення. Це зрозуміліше, ніж робити зріз list/tuple (що в деяких ситуаціях менш читабельно). Наприклад, можна вилучити перший елемент, а потім помістити решту значень у новий list без першого елемента:

>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *last = fruits
>>> x
"apple"

>>> last
["banana", "cherry", "orange", "kiwi", "melon", "mango"]

Можна також вилучити значення на початку й у кінці list, згрупувавши всі значення посередині:

>>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>>> x, *middle, y, z = fruits
>>> y
"melon"

>>> middle
["banana", "cherry", "orange", "kiwi"]

Оператор * можна використати і під час глибокого розпакування:

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

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

Розпакування словника

Розпакування словника дещо відрізняється від розпакування list/tuple. Ітерація по словниках типово працює з ключами. Тож розпаковуючи dict, можна розпакувати лише ключі, а не значення:

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

Якщо ми хочемо розпакувати значення, можна скористатися методом <dict>.values():

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

Якщо потрібні і ключі, і значення, використовуємо метод <dict>.items(). <dict>.items() створює ітероване подання, що містить пари ключ-значення. Їх можна розпакувати в tuple:

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

Пакування

Пакування дає змогу згрупувати кілька значень в один list, який присвоюють змінній. Це стає в пригоді, коли потрібно розпакувати значення, внести зміни, а потім запакувати результат назад у змінну. Це також дає змогу зливати два чи більше lists/tuples/dicts.

Пакування list/tuple за допомогою *

Запакувати list/tuple можна за допомогою оператора *. Він запакує всі значення в 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']

Докладніше про використання * у лівій частині можна дізнатися з PEP 3132.

Пакування словника за допомогою **

Словник пакують за допомогою оператора **. Він запакує всі пари ключ-значення з одного словника в інший або поєднає два словники.

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

Використання * та ** із функціями

Пакування за допомогою параметрів функції

Коли ми створюємо функцію, що приймає довільну кількість аргументів, у її визначенні можна використати *args або **kwargs. *args використовують, щоб запакувати довільну кількість позиційних (неключових) аргументів у tuple, а **kwargs - щоб запакувати довільну кількість ключових аргументів у словник.

Використання *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")

Використання **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 і **kwargs можна також використовувати разом:

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

Можна також записати параметри перед *args, щоб дозволити конкретні позиційні аргументи. Тоді окремі ключові аргументи мають стояти перед **kwargs.

Caution

Аргументи мають бути структуровані так:

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

Якщо не дотриматися цього порядку, виникне помилка.

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

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

Якщо записати аргументи в неправильному порядку, виникне помилка:

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

Розпакування у виклики функцій

Можна використати *, щоб розпакувати list/tuple аргументів у виклик функції. Це дуже корисно для функцій, які не приймають iterable:

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

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

Ще один поширений випадок: розпакування * разом із вбудованою функцією zip(). Функція zip() приймає кілька ітерованих обʼєктів і повертає list із tuples, у якому значення з кожного iterable згруповано:

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

Вказівки

Наш друг Лінус - машиніст локомотива, який водить вантажні потяги між містами. Хоча він чудово дає раду потягам, із логістикою та компʼютерами він не такий майстерний. Він хотів би залучити нашу допомогу як програмістів, щоб упорядкувати дані про потяги та виправити помилки в даних маршрутів.

Note

Цю вправу можна було б легко розвʼязати за допомогою зрізів, індексації та різних методів dict. Однак нам хотілося б, щоб, розвʼязуючи кожну з наведених нижче задач, ми практикували пакування, розпакування та множинне присвоєння.

1. Створення масиву всіх вагонів

Наш друг веде облік ідентифікаторів (ID) кожного вагона, але ніколи не знає, скільки вагонів системі доведеться обробити в той чи інший момент. Решті програми логістики було б набагато простіше, якби ці дані були запаковані в єдиний list.

Реалізуйте функцію get_list_of_wagons(), яка приймає довільну кількість ID вагонів. Кожен ID буде додатним цілим числом. Далі функція має return задані ID як один list.

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

2. Виправлення масиву вагонів

На цьому етапі ми вже потроху розуміємо дані та те, як вони використовуються в програмі логістики. Система ID завжди присвоює локомотиву ID 1, а решті вагонів у потязі - випадково вибраний ID, більший за 1.

Нашому другові довелося приєднати до потяга два нових вагони, і він забув оновити систему! Тепер перші два вагони в list потяга треба перемістити в кінець, інакше все буде не в тому порядку.

Щоб усе ще більше ускладнити, наш друг щойно виявив другий list, який, схоже, містить зниклі ID вагонів. Він памʼятає лише те, що після переміщення нових вагонів ID із цього другого list мають опинитися одразу після позначеного локомотива.

Лінус буде дуже вдячний, якщо ми виправимо його помилки й упорядкуємо дані.

Реалізуйте функцію fix_list_of_wagons(), яка приймає два lists з ID вагонів. Вона має перемістити перші два елементи першого list у кінець і вставити значення з другого list позаду (праворуч від) ID локомотива (1). Далі функція має return list зі змінами.

>>> 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. Додавання пропущених зупинок

Тепер, коли всі дані про вагони правильні, Лінус хотів би, щоб ми оновили інформацію про маршрути в системі. На транспортному маршруті потяг може робити зупинки на кількох різних станціях, щоб забрати та/або залишити вантаж. У кожної подорожі може бути різна кількість таких проміжних пунктів доставки. Наш друг хотів би, щоб ми оновили dict маршрутизації в системі, додавши будь-яку відсутню чи додаткову інформацію про доставку.

Реалізуйте функцію add_missing_stops(), яка приймає dict маршрутизації, а далі довільну кількість іменованих аргументів. Ці аргументи можуть бути у вигляді dict, що містить одну або більше зупинок, або будь-якої кількості пар stop_number=city. Далі ваша функція має повернути dict маршрутизації, оновлений додатковим key, який містить list усіх доданих зупинок у порядку.

>>> 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. Розширення інформації про маршрут

Лінус працював над програмою маршрутизації й помітив, що деяким маршрутам бракує важливих деталей. Початкова інформація про маршрут була створена як dict, і наш друг хотів би, щоб ми оновили цей dict усім, чого може бракувати. Кожен маршрут у системі потребує трохи інших деталей, тож Лінус дуже хотів би мати загальне рішення.

Реалізуйте функцію extend_route_information(), яка приймає два dicts. Перший dict містить міста відправлення та призначення, між якими проходить маршрут потяга.

Другий dict містить інші деталі маршруту, як-от швидкість потяга, довжину чи температуру. Функція має повернути обʼєднаний dict з усією інформацією про маршрут.

Note

Другий dict може містити інші або додаткові властивості, ніж ті, що показані в прикладі.

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

5. Виправлення вагонного депо

Коли Лінус оглядав вагонне депо, він помітив, що вагони зберігаються не в правильному порядку. Окрім ID, кожен вагон має колір, який відповідає типу вантажу, який він перевозить. Вагони зберігаються в депо у вигляді сіток, де в кожному стовпці сітки стоять вагони одного кольору.

Однак система логістики показує, що в lists вагонів, які треба зберегти в депо, рядки згруповані за кольором. Але щоб сітка зберігання працювала правильно, у кожному рядку мають бути три різні кольори, щоб стовпці вирівнювалися за кольором. Наш друг хотів би, щоб ми впорядкували lists вагонного депо, аби вагони зберігалися правильно.

Реалізуйте функцію fix_wagon_depot(), яка приймає list із трьох елементів. Кожен елемент list - це вкладений масив (або «рядок»), який містить три tuples. Кожен tuple - це пара (<wagon ID>, <wagon color>).

Функція має повернути list, де три lists-«рядки» переставлено так, щоб вагони опинилися на своїх правильних місцях.

>>> 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")]
]
Редагувати через GitHub Посилання відкривається в новому вікні або вкладці
Python Exercism

Час розпочати Машиніст локомотива?

Зареєструйтеся на Exercism, щоб вивчати й опановувати Python, а також 17 концепцій146 вправ та справжнє наставництво від людей, і все це безкоштовно.