لو شفت كود فيه دالة جوّاها بتنادي نفس الدالة، حاسس إن ده غلط وهيدخل في لوب لا نهائي. الكود ده شغّال صح، اسمه Recursion، وبيحلّ مسائل معيّنة في 8 سطور بدل 50 سطر loop عادي. هنا هتفهم بالظبط ازاي بتشتغل، وامتى تستخدمها، وامتى تبعد عنها لأنها هتكسر تطبيقك.
Recursion: لما الدالة بتحلّ المسألة بإنها بتنادي نفسها
المشكلة باختصار
لو قلتلك "احسبلي مجموع كل أرقام الفولدرات المتداخلة في مشروعك، بما فيها الفولدرات اللي جوّا فولدرات تانية"، الـ for loop العادي مش هيعرف يوصل لكل المستويات بسهولة. لازم تكتب حلقة جوّا حلقة جوّا حلقة، وانت مش عارف الفولدرات هتنزل كام مستوى.
Recursion بتحل المشكلة دي بفكرة واحدة: الدالة بتفكّك المسألة لمسألة أصغر من نفس النوع، وبتنادي نفسها على المسألة الأصغر، لحد ما توصل لحالة بسيطة جدًا تعرف تردّ عليها مباشرة.
مثال للمبتدئ: دمى ماتريوشكا
تخيل معاك دمية ماتريوشكا روسية. بتفتحها، تلاقي جوّاها دمية أصغر، تفتحها، تلاقي أصغر منها، وهكذا لحد ما توصل لدمية صغيرة مفيهاش حاجة. الدماغ بتاعتك مش بتحسب من الأول كام دمية. كل ما تفتح واحدة، بتعمل نفس الخطوة على اللي جوّاها.
ده بالظبط اللي الـ Recursion بتعمله. كل استدعاء (call) للدالة هو "فتح دمية واحدة". لما توصل للدمية الفاضية، الـ recursion بتقف. الحالة دي بتُسمى Base Case، ومن غيرها الكود فعلاً هيدخل لوب لا نهائي ويكسر التطبيق.
التعريف الدقيق
الـ Recursion دالة بتعرّف نفسها بدلالة نفسها على إدخال أصغر. علميًا، أي دالة recursive لازم يكون فيها مكوّنين أساسيين موثقين في كل كتاب algorithms معتبر زي Introduction to Algorithms (Cormen et al., MIT Press):
- Base Case: شرط بسيط، الدالة بترجع منه قيمة مباشرة من غير ما تنادي نفسها تاني.
- Recursive Case: الجزء اللي الدالة بتنادي فيه نفسها، بشرط إن الإدخال الجديد يكون أصغر أو أقرب للـ base case من الإدخال الحالي.
لو ضيّعت أي شرط من الاتنين، هيحصل واحدة من اتنين: لوب لا نهائي يأكل الذاكرة، أو خطأ RangeError: Maximum call stack size exceeded في JavaScript أو RecursionError في Python.
كود فعلي: حساب factorial في 4 سطور
أبسط مثال شغّال. الـ factorial للرقم n هو حاصل ضرب كل الأرقام من 1 لـ n. يعني 5! = 5 × 4 × 3 × 2 × 1 = 120.