المسارات
/
Ruby
Ruby
/
التمارين
/
مهندس القاطرة
مهندس القاطرة

مهندس القاطرة

تمرين تعلّمي

مقدمة

يشير التفكيك إلى استخراج عناصر مجموعة، مثل Array أو Hash. بعد ذلك يمكن إسناد القيم المُفكَّكة إلى متغيرات داخل العبارة نفسها.

الإسناد المتعدد هو القدرة على إسناد قيم مُفكَّكة إلى عدة متغيرات في عبارة واحدة. يتيح ذلك كتابة كود أكثر إيجازًا ووضوحًا، ويتم عبر فصل المتغيرات المراد إسنادها بفاصلة، مثل first, second, third = [1, 2, 3].

غالبًا ما يُستخدم عامل النجمة (*) وعامل النجمة المزدوجة (**) في سياقات التفكيك.

Caution

لا ينبغي الخلط بين *<variable_name> و**<variable_name> وبين * و**. فبينما يُستخدم * و** للضرب والرفع إلى قوة على الترتيب، يُستخدم *<variable_name> و**<variable_name> عاملي تركيب وتفكيك.

الإسناد المتعدد

يتيح لك الإسناد المتعدد إسناد عدة متغيرات في سطر واحد. ولفصل القيم، استخدم فاصلة ,:

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

لا يقتصر الإسناد المتعدد على نوع بيانات واحد:

>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true

يمكن استخدام الإسناد المتعدد لتبديل العناصر في المصفوفات. هذه الممارسة شائعة جدًا في خوارزميات الترتيب. على سبيل المثال:

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

يُعرف هذا أيضًا باسم «الإسناد المتوازي»، ويمكن استخدامه لتجنّب متغير مؤقت.

إذا كان عدد المتغيرات أكثر من القيم، فستُسند nil إلى المتغيرات الإضافية:

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

التفكيك

في Ruby، يمكن تفكيك عناصر المصفوفات/Hash إلى متغيرات منفصلة. ولأن القيم تظهر داخل المصفوفات بترتيب الفهارس، فإنها تُفكَّك إلى متغيرات بالترتيب نفسه:

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

إذا كانت هناك قيم غير مطلوبة، يمكنك استخدام _ للإشارة إلى «مُجمَّعة لكن غير مستخدمة»:

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

التفكيك العميق

تفكيك وإسناد القيم من مصفوفات داخل مصفوفة (تُعرف أيضًا بالمصفوفة المتداخلة) يعمل بالطريقة نفسها التي يعمل بها التفكيك السطحي، لكنه يحتاج إلى تعبير تفكيك محدَّد (()) لتوضيح سياق القيم أو موضعها:

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

يمكنك أيضًا تفكيك جزء فقط من مصفوفة متداخلة تفكيكًا عميقًا:

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

إذا كان في التفكيك متغيرات في مواضع غير صحيحة و/أو عدد غير صحيح من القيم، فستحصل على خطأ في الصياغة:

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

جرّب ذلك هنا، وستلاحظ أن النمط الأول هو الذي يفرض الترتيب، وليس القيم المتاحة في الطرف الأيمن. فخطأ الصياغة لا يرتبط ببنية البيانات.

تفكيك مصفوفة باستخدام عامل النجمة المفرد (*)

عند تفكيك مصفوفة يمكنك استخدام عامل النجمة (*) لالتقاط القيم «المتبقية». هذا أوضح من تقطيع المصفوفة (وهو أقل قابلية للقراءة في بعض الحالات). على سبيل المثال، يمكننا استخراج العنصر الأول ثم إسناد القيم المتبقية إلى مصفوفة جديدة بدون العنصر الأول:

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

يمكننا أيضًا استخراج القيم في بداية المصفوفة ونهايتها مع تجميع كل القيم الواقعة في الوسط:

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

تفكيك Hash

يختلف تفكيك Hash قليلًا عن تفكيك المصفوفة. لتتمكن من تفكيك Hash، عليك أولًا تحويله إلى مصفوفة. وإلا فلن يحدث أي تفكيك:

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

لتحويل Hash إلى مصفوفة، يمكنك استخدام الطريقة 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]

إذا أردت تفكيك المفاتيح، يمكنك استخدام الطريقة keys:

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

إذا أردت تفكيك القيم، يمكنك استخدام الطريقة values:

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

التركيب

التركيب هو القدرة على تجميع عدة قيم في مصفوفة واحدة تُسند إلى متغير. يكون هذا مفيدًا عندما تريد تفكيك القيم، وإجراء تغييرات عليها، ثم تركيب النتائج مرة أخرى في متغير. كما يتيح دمج مصفوفتين أو أكثر من المصفوفات/Hash.

تركيب مصفوفة باستخدام عامل النجمة (*)

يمكن تركيب مصفوفة باستخدام عامل النجمة (*). حيث سيحزم هذا كل القيم في مصفوفة واحدة.

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

تركيب Hash باستخدام عامل النجمة المزدوجة (**)

يتم تركيب Hash باستخدام عامل النجمة المزدوجة (**). حيث سيحزم هذا كل أزواج المفتاح/القيمة من Hash إلى Hash آخر، أو يدمج اثنين من Hash معًا.

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

استخدام عامل النجمة (*) وعامل النجمة المزدوجة (**) مع الطرق

التركيب مع معاملات الطريقة

عندما تنشئ طريقة تقبل عددًا غير محدد من الوسائط، يمكنك استخدام *arguments أو **keyword_arguments في تعريف الطريقة. يُستخدم *arguments لحزم عدد غير محدد من الوسائط الموضعية (غير المفتاحية)، ويُستخدم **keyword_arguments لحزم عدد غير محدد من الوسائط المفتاحية.

استخدام *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"]

استخدام **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}

إذا لم تكن للطريقة المعرَّفة أي معاملات محددة للوسائط المفتاحية (**keyword_arguments أو <key_word>: <value>) فسيتم حزم الوسائط المفتاحية في Hash وإسنادها إلى المعامل الأخير.

>> def my_method(a)= a

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

يمكن أيضًا استخدام *arguments و**keyword_arguments معًا:

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"

يمكنك أيضًا كتابة وسائط قبل *arguments وبعده للسماح بوسائط موضعية محددة. وهذا يعمل بالطريقة نفسها التي يعمل بها تفكيك المصفوفة.

Caution

يجب ترتيب الوسائط بترتيب محدد:

def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)

إذا لم تتبع هذا الترتيب، فستحصل على خطأ.

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]

يمكنك كتابة وسائط موضعية قبل *arguments وبعده:

>> def my_method(a, *middle, b)= middle

>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]

يمكنك أيضًا الجمع بين الوسائط الموضعية و*arguments والوسائط المفتاحية و**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}

كتابة الوسائط بترتيب غير صحيح ستؤدي إلى خطأ:

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)

التفكيك في استدعاءات الطرق

يمكنك استخدام عامل النجمة (*) لتفكيك مصفوفة من الوسائط في استدعاء طريقة:

def my_method(a, b, c)
  p c
  p b
  p a
end

numbers = [1, 2, 3]
my_method(*numbers)
3
2
1

يمكنك أيضًا استخدام عامل النجمة المزدوجة (**) لتفكيك Hash من الوسائط في استدعاء طريقة:

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

التعليمات

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

Note

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

1. إنشاء مصفوفة تضم كل العربات

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

نفذ طريقة generate_list_of_wagons() تقبل أي عدد من معرفات العربات. سيكون كل معرف عددًا صحيحًا موجبًا. يجب أن تُرجع الطريقة بعد ذلك return المعرفات المعطاة في مصفوفة واحدة.

LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]

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

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

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

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

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

نفذ طريقة fix_list_of_wagons() تأخذ مصفوفتين تحتويان على معرفات العربات. يجب أن تعيد وضع أول عنصرين من المصفوفة الأولى في النهاية، وتدرج القيم من المصفوفة الثانية خلف (على الجانب الأيمن من) معرف القاطرة (1). يجب أن تُرجع الطريقة بعد ذلك return مصفوفة بالتعديلات.

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. إضافة محطات التوقف المفقودة

الآن بعد أن أصبحت جميع بيانات العربات صحيحة، يود لينوس منك تحديث معلومات التوجيه في النظام. على طول مسار النقل، قد يتوقف القطار في بضع محطات مختلفة لتحميل و/أو إنزال البضائع. قد يكون لكل رحلة عدد مختلف من نقاط التسليم الوسيطة هذه. يود صديقك منك تحديث هاش التوجيه في النظام بأي معلومات تسليم مفقودة أو إضافية.

نفذ طريقة add_missing_stops() تقبل هاش توجيه متبوعًا بعدد متغير من الوسائط المفتاحية. يمكن أن تكون هذه الوسائط على شكل هاش يحتوي على محطة واحدة أو أكثر، أو أي عدد من أزواج stop_<number>: "city" المفتاحية. يجب أن تُرجع طريقتك بعد ذلك هاش التوجيه محدثًا بـ مفتاح إضافي يحتوي على مصفوفة بجميع المحطات المضافة بالترتيب.

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

ستُمرر المحطات دائمًا بالترتيب الصحيح، لذا لا داعي للقلق بشأن فرزها.

4. توسيع معلومات التوجيه

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

نفذ طريقة تسمى extend_route_information() تقبل هاشين. يحتوي الهاش الأول على مدينتي الأصل والوجهة التي يسير بينهما مسار القطار.

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

Note

يمكن أن يحتوي الهاش الثاني على خصائص مختلفة أو أكثر من تلك الموضحة في المثال.

LocomotiveEngineer.extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
# => {"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
Ruby Exercism

مستعد لبدء مهندس القاطرة؟

سجّل في Exercism لتتعلّم وتتقن Ruby عبر 21 مفهومًا121 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.