المسارات
/
Go
Go
/
التمارين
/
خبير اللازانيا
خبير اللازانيا

خبير اللازانيا

تمرين تعلّمي

مقدمة

تتيح لك الدالة تجميع الكود في وحدة قابلة لإعادة الاستخدام. وهي تتكون من الكلمة المفتاحية func، واسم الدالة، وقائمة مفصولة بفواصل من صفر أو أكثر من المعاملات والأنواع بين الأقواس الهلالية.

معاملات الدالة

يجب تحديد نوع جميع المعاملات صراحةً؛ فلا يوجد استنتاج للنوع بالنسبة للمعاملات. ولا توجد قيم افتراضية للمعاملات، لذا فإن جميع معاملات الدالة مطلوبة.

import "fmt"

// No parameters
func PrintHello() {
    fmt.Println("Hello")
}

// Two parameters
func PrintGreetingName(greeting string, name string) {
  fmt.Println(greeting + " " + name)
}

يمكن الإعلان عن المعاملات من النوع نفسه معًا، يتبعها إعلان واحد للنوع.

import "fmt"

func PrintGreetingName(greeting, name string) {
  fmt.Println(greeting + " " + name)
}

المعاملات مقابل الوسائط

لنستعرض سريعًا مصطلحين كثيرًا ما يُخلط بينهما: parameters وarguments. معاملات الدالة هي الأسماء المعرَّفة في توقيع الدالة، مثل greeting وname في الدالة PrintGreetingName أعلاه. أما وسائط الدالة فهي القيم الفعلية التي تُمرَّر إلى معاملات الدالة عند استدعائها. على سبيل المثال، في المثال أدناه، "Hello" و"Katrina" هما الوسيطان الممرَّران إلى المعاملين greeting وname:

PrintGreetingName("Hello", "Katrina")

قيم الإرجاع

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

func Hello(name string) string {
  return "Hello " + name
}

func HelloAndGoodbye(name string) (string, string) {
  return "Hello " + name, "Goodbye " + name
}

استدعاء الدوال

يتم استدعاء الدالة بتحديد اسم الدالة وتمرير وسائط لكل معامل من معاملات الدالة بين الأقواس الهلالية.

import "fmt"

// No parameters, no return value
func PrintHello() {
    fmt.Println("Hello")
}
// Called like this:
PrintHello()

// One parameter, one return value
func Hello(name string) string {
  return "Hello " + name
}
// Called like this:
greeting := Hello("Dave")

// Multiple parameters, multiple return values
func SumAndMultiply(a, b int) (int, int) {
    return a+b, a*b
}
// Called like this:
aplusb, atimesb := SumAndMultiply(a, b)

قيم الإرجاع المُسمّاة والإرجاع المجرّد

إلى جانب المعاملات، يمكن اختياريًا تسمية قيم الإرجاع. وإذا استُخدمت قيم إرجاع مُسمّاة، فإن عبارة return بدون وسائط ستُرجع تلك القيم. ويُعرف هذا بالإرجاع المجرّد.

func SumAndMultiplyThenMinus(a, b, c int) (sum, mult int) {
    sum, mult = a+b, a*b
    sum -= c
    mult -= c
    return
}

التمرير بالقيمة مقابل التمرير بالمرجع

من المهم أيضًا توضيح مفهوم التمرير بالقيمة والتمرير بالمرجع.

أولًا، لنوضّح التمرير بالقيمة. في المثال أدناه، عندما نمرّر المتغير val إلى الدالة MultiplyByTwo، فإننا نمرّر نسخة من val. وبسبب ذلك، تحمل newVal القيمة المحدَّثة 4، لكن المتغير الأصلي val ما زال 2. وخلف الكواليس، يُنشئ Go نسخة من القيمة الأصلية، بحيث لا تُعدّل الدالة سوى هذه النسخة، أي v.

val := 2
func MultiplyByTwo(v int) int {
    v = v * 2
    return v
}
newVal := MultiplyByTwo(val)
// newval is 4, val is still 2 because only a copy of its value was passed into the function

بالمعنى الدقيق، تُمرَّر جميع الوسائط بالقيمة في Go، أي تُصنع نسخة من القيمة أو البيانات المقدَّمة إلى الدالة. لكن إذا كنت لا تريد إنشاء نسخة من البيانات الممرَّرة إلى دالة وتريد تغيير البيانات داخل الدالة، فعليك استخدام pointers كوسائط، أي التمرير بالمرجع.

المؤشرات

نستخدم pointer لتحقيق التمرير بالمرجع. وبتمرير وسائط من نوع pointer إلى دالة، يمكننا تعديل البيانات الأساسية الممرَّرة إلى الدالة بدلًا من العمل على نسخة منها فقط.

في الوقت الحالي، يكفي أن تعرف أن أنواع المؤشرات يمكن تمييزها بعلامة * أمام النوع في توقيع الدالة.

func HandlePointers(x, y *int) {
    // Some logic to handle integer values referenced by pointers x and y
}

إذا كان مفهوم pointer مربكًا، فلا تقلق. لدينا قسم مخصص لاحقًا في المنهج لمساعدتك على إتقان المؤشرات.

الاستثناءات

لاحظ أن slices وmaps استثناءات من القاعدة المذكورة أعلاه. فعندما نمرّر slice أو map كوسائط إلى دالة، فإنها تُعامَل كأنواع مؤشرات حتى لو لم توجد علامة * صريحة في النوع. وهذا يعني أننا إذا مرّرنا slice أو map إلى دالة وعدّلنا بياناته الأساسية، فستنعكس التغييرات على slice أو map الأصليين.

التعليمات

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

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

1. قدّر وقت التحضير

بالنسبة إلى اللازانيا التالية التي ستُعدّها، تريد أن تتأكد من أنك خصصت وقتًا كافيًا لتستمتع بالطهي. لقد خططت مسبقًا للطبقات التي ستتكون منها لازانيتك. والآن تريد تقدير المدة التي سيستغرقها التحضير بناءً على ذلك.

أنشئ دالة PreparationTime تقبل شريحة من الطبقات من النوع []string ومتوسط وقت التحضير لكل طبقة بالدقائق من النوع int. ينبغي أن تُرجع الدالة تقدير الوقت الإجمالي للتحضير بناءً على عدد الطبقات، من النوع int. لا توجد في لغة Go قيم افتراضية للدوال. إذا مُرِّر متوسط وقت التحضير بالقيمة 0 (القيمة الابتدائية الافتراضية للنوع int)، فيجب استخدام القيمة الافتراضية 2.

layers := []string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"}
PreparationTime(layers, 3)
// => 18
PreparationTime(layers, 0)
// => 12

2. احسب كميات المعكرونة والصلصة اللازمة

إلى جانب حجز الوقت، تريد أيضًا أن تتأكد من توفر ما يكفي من الصلصة والمعكرونة لطهي لازانيا أحلامك. ستحتاج إلى 50 غرامًا من المعكرونة لكل طبقة معكرونة في لازانيتك. وستحتاج إلى 0.2 لتر من الصلصة لكل طبقة صلصة فيها.

عرّف دالة Quantities تأخذ شريحة من الطبقات كمعامل من النوع []string. ستحدد الدالة بعد ذلك كمية المعكرونة والصلصة اللازمة لإعداد وجبتك. ينبغي إرجاع النتيجة كقيمتين: noodles من النوع int وsauce من النوع float64.

Quantities([]string{"sauce", "noodles", "sauce", "meat", "mozzarella", "noodles"})
// => 100, 0.4

3. أضف المكوّن السري

منذ فترة زرت صديقًا وتناولت اللازانيا عنده. كانت رائعة وكان فيها شيء مميز. أرسل إليك الصديق قائمة المكوّنات وأخبرك أن العنصر الأخير في القائمة هو «المكوّن السري» الذي جعل الوجبة مميزة إلى هذا الحد. والآن تريد أن تضيف ذلك المكوّن السري إلى وصفتك أيضًا.

اكتب دالة AddSecretIngredient تقبل شريحتين من المكوّنات من النوع []string كمعاملين. المعامل الأول هو القائمة التي أرسلها إليك صديقك، والثاني هو قائمة مكوّنات وصفتك. العنصر الأخير في قائمة مكوّناتك هو دائمًا "?". ينبغي أن تستبدله الدالة بالعنصر الأخير من قائمة صديقك. ملاحظة: لا تُرجع دالة AddSecretIngredient أي شيء؛ ينبغي أن تعدّل قائمة مكوّناتك مباشرة. أما القائمة التي تحتوي على مكوّنات صديقك فيجب ألا تُعدَّل. كذلك، بما أن slice يُمرَّر إلى الدالة في صورة مؤشرات، فإن أي تغيير في وسيطَي []string الممرَّرين إلى AddSecretIngredient سينعكس عليهما مباشرة.

friendsList := []string{"noodles", "sauce", "mozzarella", "kampot pepper"}
myList := []string{"noodles", "meat", "sauce", "mozzarella","?"}

AddSecretIngredient(friendsList, myList)
// myList => []string{"noodles", "meat", "sauce", "mozzarella", "kampot pepper"}

4. عدّل مقادير الوصفة

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

أنشئ دالة ScaleRecipe تأخذ معاملين.

  • شريحة من المقادير من النوع float64 اللازمة لـ 2 حصص.
  • عدد الحصص التي تريد طهيها.

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

quantities := []float64{ 1.2, 3.6, 10.5 }
scaledQuantities := ScaleRecipe(quantities, 4)
// => []float64{ 2.4, 7.2, 21 }
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
Go Exercism

مستعد لبدء خبير اللازانيا؟

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