المسارات
/
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 جديد بدون العنصر الأول:

>>> 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 واحد يُسند إلى متغير. يكون هذا مفيدًا عندما تريد تفكيك القيم وإجراء تغييرات ثم تغليف النتائج من جديد في متغير. كما يتيح إجراء عمليات دمج على 2 أو أكثر من lists/tuples/dicts.

تغليف مصفوفة/صف باستخدام *

يمكن تغليف 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)]

التعليمات

صديقك Linus مهندس قاطرات يقود قطارات الشحن بين المدن. ورغم أنه بارع في التعامل مع القطارات، إلا أنه ليس بارعًا في التعامل مع اللوجستيات أو الحواسيب. يودّ أن يستعين بمساعدتك البرمجية لتنظيم تفاصيل القطارات وتصحيح الأخطاء في بيانات المسارات.

Note

يمكن حل هذا التمرين بسهولة باستخدام التقطيع والفهرسة وطرق dict المختلفة. لكننا نريدك أن تتدرّب على الحزم وفك الحزم والإسناد المتعدد عند حل كل مهمة من المهام التالية.

1. أنشئ مصفوفة تضم جميع العربات

كان صديقك يتابع معرّف كل عربة (ID)، لكنه لا يعرف أبدًا عدد العربات التي سيتعيّن على النظام معالجتها في أي لحظة. سيكون الأمر أسهل بكثير على بقية برنامج اللوجستيات لو جُمعت هذه البيانات معًا في list واحدة موحّدة.

نفّذ دالة get_list_of_wagons() تقبل عددًا اعتباطيًا من معرّفات العربات. سيكون كل معرّف عددًا صحيحًا موجبًا. ثم ينبغي أن return الدالة المعرّفات المعطاة في list واحدة.

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

2. صحّح مصفوفة العربات

في هذه المرحلة، بدأت تتكوّن لديك فكرة عن البيانات وكيفية استخدامها في برنامج اللوجستيات. يخصّص نظام المعرّفات دائمًا للقاطرة المعرّف 1، بينما تُخصَّص لبقية عربات القطار معرّفات مختارة عشوائيًا أكبر من 1.

كان على صديقك أن يربط عربتين جديدتين بالقطار لكنه نسي تحديث النظام! الآن، يجب نقل أول عربتين في list القطار إلى النهاية، وإلا سيختلّ ترتيب كل شيء.

ولزيادة التعقيد، اكتشف صديقك للتو list ثانية يبدو أنها تحتوي على معرّفات عربات مفقودة. كل ما يتذكّره هو أنه بمجرد نقل العربات الجديدة، ينبغي وضع المعرّفات من هذه list الثانية مباشرة بعد القاطرة المخصّصة.

سيكون Linus ممتنًّا لك حقًّا إذا صحّحت أخطاءه ووحّدت البيانات.

نفّذ دالة fix_list_of_wagons() تأخذ lists اثنتين تحتويان على معرّفات عربات. ينبغي أن تنقل أول عنصرين من list الأولى إلى النهاية، وتُدرج القيم من list الثانية خلف معرّف القاطرة (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. أضف المحطات المفقودة

الآن بعد أن أصبحت جميع بيانات العربات صحيحة، يودّ Linus أن تحدّث معلومات التوجيه في النظام. على طول مسار النقل، قد يتوقف القطار في بضعة محطات مختلفة لتحميل البضائع و/أو إنزالها. وقد يتضمّن كل مسار عددًا مختلفًا من نقاط التسليم الوسيطة هذه. يودّ صديقك أن تحدّث 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. وسّع معلومات التوجيه

كان Linus يعمل على برنامج التوجيه ولاحظ أن بعض المسارات تنقصها تفاصيل مهمة. جُمعت معلومات المسار الأولية على شكل dict، ويودّ صديقك أن تحدّث ذلك dict بكل ما قد يكون ناقصًا. كل مسار في النظام يتطلّب تفاصيل مختلفة قليلًا، لذا يفضّل Linus حقًّا حلًّا عامًّا.

نفّذ دالة تُسمّى 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. صحّح مستودع العربات

عندما كان Linus يتفقّد مستودع العربات لاحظ أن العربات لا تُخزَّن بالترتيب الصحيح. فإلى جانب المعرّف، لكل عربة لون يقابل نوع البضاعة التي تحملها. تُخزَّن العربات في المستودع في شبكات، حيث يحتوي كل عمود في الشبكة على عربات من اللون نفسه.

غير أن نظام اللوجستيات يُظهر أن lists العربات المطلوب تخزينها في المستودع تكون صفوفها مجمّعة حسب اللون. لكن لكي تعمل شبكة التخزين بشكل صحيح، ينبغي أن يحتوي كل صف على ثلاثة ألوان مختلفة حتى تنتظم الأعمدة حسب اللون. يودّ صديقك أن ترتّب lists مستودع العربات، حتى تُخزَّن العربات بشكل صحيح.

نفّذ دالة تُسمّى fix_wagon_depot() تقبل list من ثلاثة عناصر. كل عنصر في 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 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.