سفارش پیتزا

سفارش پیتزا

تمرین یادگیری

مقدمه

«بازگشت» مفهومی قدرتمند در برنامه‌نویسی است که در آن یک تابع خودش را فراخوانی می‌کند. شاید در ابتدا کمی سخت به نظر برسد، اما وقتی اصول پایه را بفهمید، به ابزاری ارزشمند برای حل مسائل پیچیده تبدیل می‌شود. در این آموزش، بازگشت را در JavaScript با مثال‌هایی ساده و قابل‌فهم بررسی می‌کنیم.

بازگشت چیست؟

بازگشت زمانی رخ می‌دهد که یک تابع، به‌طور مستقیم یا غیرمستقیم، خودش را فراخوانی کند. این شبیه «حلقه» است، اما در آن یک مسئله به زیرمسئله‌های کوچک‌تر و قابل‌مدیریت‌تری شکسته می‌شود.

مثال ۱: شمارش معکوس

بیایید با یک مثال ساده شروع کنیم: یک تابع شمارش معکوس.

function countdown(num) {
  // Base case
  if (num <= 0) {
    console.log('Blastoff!');
    return;
  }

  // Recursive case
  console.log(num);
  countdown(num - 1);
}

// Call the function
countdown(5);

در این مثال:

  • حالت پایه: وقتی num کوچک‌تر یا مساوی ۰ می‌شود، تابع «Blastoff!» را چاپ می‌کند و دیگر خودش را فراخوانی نمی‌کند.
  • حالت بازگشتی: تابع مقدار فعلی num را چاپ می‌کند و خودش را با num - 1 فراخوانی می‌کند.

مثال ۲: فاکتوریل

حالا بیایید به یک مثال کلاسیک از بازگشت نگاه کنیم: محاسبه‌ی فاکتوریل یک عدد.

function factorial(n) {
  // Base case
  if (n === 0 || n === 1) {
    return 1;
  }

  // Recursive case
  return n * factorial(n - 1);
}

// Test the function
console.log(factorial(5)); // Output: 120

در این مثال:

  • حالت پایه: وقتی n برابر ۰ یا ۱ باشد، تابع ۱ را برمی‌گرداند.
  • حالت بازگشتی: تابع n را در فاکتوریل n - 1 ضرب می‌کند.

مفاهیم کلیدی

حالت پایه

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

حالت بازگشتی

حالت بازگشتی مشخص می‌کند که تابع چگونه خودش را با نسخه‌ی کوچک‌تر یا ساده‌تر مسئله فراخوانی می‌کند.

مزایا و معایب بازگشت

مزایا:

  • راه‌حلی زیبا برای برخی مسائل.
  • همانند مفهوم استقرای ریاضی است.

معایب:

  • می‌تواند از راه‌حل‌های تکراری کم‌بازده‌تر باشد.
  • در بازگشت‌های عمیق ممکن است به سرریز پشته منجر شود.

نتیجه‌گیری

بازگشت تکنیکی ارزشمند است که می‌تواند مسائل پیچیده را با شکستن آن‌ها به زیرمسئله‌های کوچک‌تر و قابل‌مدیریت‌تر ساده کند. درک حالت‌های پایه و حالت‌های بازگشتی برای پیاده‌سازی راه‌حل‌های بازگشتی مؤثر در JavaScript ضروری است.

بیشتر بدانید:

دستورالعمل‌ها

شما یک پیتزافروشی دارید و سه نوع پیتزا عرضه می‌کنید:

  • Margherita: ۷ دلار
  • Caprese: ۹ دلار
  • Formaggio: ۱۰ دلار

اگر مشتری‌ها بخواهند، می‌توانند به تعداد نامحدود گزینه‌ی اضافه سفارش دهند: یا «ExtraSauce» به قیمت ۱ دلار یا «ExtraToppings» به قیمت ۲ دلار.

وظیفه‌ی شما نوشتن کدی است که به مشتری کمک می‌کند هزینه‌اش را حساب کند.

محاسبه‌ی قیمت یک پیتزا

با دریافت اسم پیتزا به عنوان آرگومان اول و هر تعداد گزینه‌ی اضافه، قیمت پیتزا را بر حسب دلار محاسبه کنید.

pizzaPrice('Margherita');
// => 7

pizzaPrice('Caprese', 'ExtraSauce', 'ExtraToppings');
// => 12

pizzaPrice(
  'Caprese',
  'ExtraToppings',
  'ExtraToppings',
  'ExtraToppings',
  'ExtraToppings',
);
// => 17

محاسبه‌ی قیمت کل یک سفارش

تابع شما با فهرستی از PizzaOrderها فراخوانی می‌شود و باید قیمت کل سفارش را بر حسب دلار برگرداند. هر PizzaOrder یک ویژگی pizza دارد، یعنی اسم پیتزا، و یک ویژگی extras، یعنی فهرست گزینه‌های اضافه.

const margherita = new PizzaOrder('Margherita');
const caprese = new PizzaOrder('Caprese', 'ExtraToppings');
orderPrice([margherita, caprese]);
// => 18

متوجه می‌شوید که نمی‌توانید این را با بازگشت بنویسید، چون یک تست با تعداد بسیار زیادی سفارش، خطای Maximum call stack size exceeded را ایجاد می‌کند. نگران نباشید، این عمدی است؛ این تابع را با یک حلقه‌ی دستوری پیاده‌سازی کنید! گزینه‌های زیادی دارید، از جمله استفاده از reduce یا یک حلقه‌ی for، و نه فقط این‌ها.

Advanced

وقتی مفسر JavaScript در حال اجرای کد JavaScript است، روی یک ساختار داده به اسم «پشته» ثبت می‌کند که وارد چه توابعی شده است (فراخوانی کدام توابع را شروع کرده است). وقتی تابع برمی‌گردد (پایان می‌یابد)، از پشته حذف می‌شود.

اما این پشته اندازه‌ی محدودی دارد. رایج‌ترین اشتباهی که رخ می‌دهد، تابع بازگشتی‌ای است که هرگز تمام نمی‌شود. هر فراخوانی روی پشته قرار می‌گیرد، اما پیش از آنکه برگردد، فراخوانی دیگری روی پشته قرار می‌گیرد.

function kaboom() {
  kaboom()
}

kaboom()
// => RangeError: Maximum call stack size exceeded

ردیابی پشته‌ی این خطا همان خط را بارها و بارها نشان می‌دهد، که منطقی است، چون تابع خودش را فراخوانی می‌کند. گرچه در بیشتر موارد کاربرد عملی واقعی ندارد، می‌توانید بفهمید این پشته چقدر می‌تواند بلند شود.

let calls = 0;
function kaboom() {
  calls +=1 ;
  kaboom()
}

kaboom()
// => RangeError: Maximum call stack size exceeded

console.log(calls)
// => a number, generally higher than 10.000

برای خطای پشته‌ی فراخوانی که یک تابع بازگشتی همگام ایجاد می‌کند، فقط دو راه‌حل عملی وجود دارد:

  • مطمئن شوید توابع پیش از رسیدن به حد پشته برمی‌گردند، معمولاً با افزودن یا اصلاح یک حالت پایه.
  • تابع بازگشتی را به یک حلقه‌ی دستوری بازنویسی کنید؛ این حلقه بدنه‌ی حلقه را اجرا می‌کند بدون آنکه لازم باشد وارد تابعی شود، و بنابراین پشته بزرگ‌تر نمی‌شود.
ویرایش از طریق GitHub این لینک در پنجره یا زبانه‌ی جدیدی باز می‌شود
JavaScript Exercism

آماده‌اید سفارش پیتزا را شروع کنید؟

در Exercism ثبت‌نام کنید تا JavaScript را همراه با 37 مفهوم159 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.