مسیرها
/
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. مقدار رشته‌فرنگی و سس لازم را محاسبه کنید

علاوه بر کنار گذاشتن زمان، می‌خواهید مطمئن شوید که سس و رشته‌فرنگی کافی برای پختن لازانیای رویاهایتان دارید. برای هر لایه‌ی رشته‌فرنگی در لازانیایتان به ۵۰ گرم رشته‌فرنگی نیاز دارید. برای هر لایه‌ی سس در لازانیایتان به ۰٫۲ لیتر سس نیاز دارید.

تابع 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 که برای ۲ پرس لازم است.
  • تعداد پرس‌هایی که می‌خواهید بپزید.

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