مسیرها
/
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 جای گرفته است (که به آن 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"]

باز کردن یک dictionary

باز کردن یک dictionary تا حدی با باز کردن یک list/tuple تفاوت دارد. پیمایش روی dictionaryها بهطور پیشفرض روی کلیدها انجام میشود. پس هنگام باز کردن یک 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 را ببینید.

بستهبندی یک dictionary با **

بستهبندی یک dictionary با استفاده از عملگر ** انجام میشود. این کار همهی جفتهای کلید-مقدار را از یک dictionary در dictionary دیگری بستهبندی میکند، یا دو dictionary را با هم ترکیب میکند.

>>> 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 برای بستهبندی هر تعداد آرگومان کلیدواژهای بهصورت یک dictionary به کار میرود.

کاربرد *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() چندین iterable میگیرد و یک 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() پیاده‌سازی کنید که هر تعداد دلخواه از شناسه‌ی واگن را بپذیرد. هر شناسه یک عدد صحیح مثبت خواهد بود. سپس تابع باید شناسه‌های داده‌شده را به‌صورت یک list واحد return کند.

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

2. فهرست واگن‌ها را اصلاح کنید

در این مرحله، کم‌کم با داده‌ها و نحوه‌ی استفاده‌شان در برنامه‌ی لجستیک آشنا می‌شوید. سیستم شناسه‌گذاری همیشه به لکوموتیو شناسه‌ی ۱ می‌دهد و به باقی واگن‌های قطار شناسه‌ای تصادفی بزرگ‌تر از ۱ اختصاص می‌یابد.

دوست شما مجبور شد دو واگن جدید را به قطار متصل کند و یادش رفت سیستم را به‌روزرسانی کند! حالا دو واگن اول در list قطار باید به انتهای فهرست منتقل شوند، وگرنه همه‌چیز از ترتیب خارج می‌شود.

برای پیچیده‌تر شدن ماجرا، دوست شما تازه list دومی پیدا کرده است که به نظر می‌رسد شناسه‌های گم‌شده‌ی واگن‌ها را در خود دارد. تنها چیزی که به یاد دارد این است که پس از جابه‌جایی واگن‌های جدید، شناسه‌های این list دوم باید مستقیماً بعد از لکوموتیو مشخص‌شده قرار بگیرند.

لینوس از اینکه اشتباه‌هایش را اصلاح کنید و داده‌ها را یکپارچه کنید، واقعاً سپاسگزار خواهد بود.

تابعی به اسم fix_list_of_wagons() پیاده‌سازی کنید که دو lists حاوی شناسه‌ی واگن‌ها را می‌گیرد. این تابع باید دو عنصر اول list اول را به انتها منتقل کند و مقدارهای list دوم را پشتِ (سمت راستِ) شناسه‌ی لکوموتیو (۱) درج کند. سپس تابع باید یک list با تغییرات را return کند.

>>> 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. انبار واگن‌ها را اصلاح کنید

وقتی لینوس داشت انبار واگن‌ها را بررسی می‌کرد، متوجه شد که واگن‌ها به ترتیب درست ذخیره نمی‌شوند. هر واگن علاوه بر شناسه، رنگی دارد که با نوع باری که حمل می‌کند متناظر است. واگن‌ها در انبار به‌صورت شبکه‌ای ذخیره می‌شوند، طوری که هر ستون در شبکه واگن‌هایی با یک رنگ دارد.

با این حال، سیستم لجستیک نشان می‌دهد که lists واگن‌هایی که باید در انبار ذخیره شوند، _سطر_هایشان بر اساس رنگ دسته‌بندی شده است. اما برای اینکه شبکه‌ی ذخیره‌سازی درست کار کند، هر سطر باید سه رنگ متفاوت داشته باشد تا _ستون_ها بر اساس رنگ هم‌راستا شوند. دوست شما می‌خواهد lists انبار واگن‌ها را مرتب کنید تا واگن‌ها به‌درستی ذخیره شوند.

تابعی به اسم fix_wagon_depot() پیاده‌سازی کنید که یک list با سه عنصر می‌پذیرد. هر عنصر list یک زیرفهرست (یا «سطر») است که سه tuples دارد. هر tuple یک جفت (<wagon ID>, <wagon color>) است.

تابع شما باید یک list برگرداند که سه list «سطر» در آن بازچینی شده‌اند تا واگن‌ها به جایگاه درست خود منتقل شوند.

>>> 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 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.