أحمد حايس
الرئيسيةمن أناالدوراتالمدونةالعروض
أحمد حايس

دورات عربية متخصصة في التقنية والبرمجة والذكاء الاصطناعي.

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

تعلم أسرعوصول مباشر للدورات والمسارات من الموبايل.
تنقل أوضحالروابط الأساسية والدعم في مكان واحد بدون تشتيت.

المنصة

  • الرئيسية
  • من أنا
  • الدورات
  • العروض
  • المدونة

الدعم

  • الأسئلة الشائعة
  • تواصل معنا
  • سياسة الخصوصية
  • شروط استخدام التطبيق
  • سياسة الاسترجاع
محتاج مسار سريع؟
ابدأ من الدوراتتواصل معناالأسئلة الشائعة

© 2026 أحمد حايس. جميع الحقوق محفوظة.

الرئيسيةالدوراتالعروضالمدونةالدخول
البرمجة بالعربي

Recursion للمبتدئ: ازاي الدالة تنادي نفسها وامتى Stack Overflow بيقعّد كل حاجة

📅 ٢٤ مايو ٢٠٢٦⏱ 8 دقائق قراءة
Recursion للمبتدئ: ازاي الدالة تنادي نفسها وامتى Stack Overflow بيقعّد كل حاجة

مستوى المقال: مبتدئ — مطلوب معرفة أساسية بـ Python أو JavaScript فقط

أكتر سؤال بيقع فيه المبتدئ أول ما يشوف دالة بتنادي نفسها هو: "هي بتعمل ايه دي بالظبط؟ ومش هتدخل في loop لا نهائي؟" الجواب القصير: لو الدالة فيها شرط توقف صح، لا. ولو مفيهاش، البرنامج هيقع في خطأ اسمه Stack Overflow بعد آلاف الاستدعاءات. Recursion مش حركة فلسفية ولا "كود ذكي"، هي تقنية حسابية ليها مكان محدد وقواعد ثابتة، ولما تستخدمها في المكان الغلط بتاكل ذاكرة من غير فايدة.

Recursion: ازاي الدالة تنادي نفسها بدون ما تقع في حلقة لا نهائية

المشكلة باختصار

لو عندك مشكلة طبيعتها متفرّعة — زي عرض شجرة فولدرات، تحليل JSON متداخل، أو حل لغز Sudoku — الحلقة العادية بـ for أو while مش هتنفع لأنك ما تعرفش العمق مقدمًا. Recursion بتحل المشكلة دي بفكرة بسيطة: الدالة بتعالج جزء صغير من المدخل، وبتنادي نفسها على الجزء الباقي. لما توصل لأبسط حالة، بتقف وتبدأ ترجع.

المشكلة إن المبتدئ بيستخدمها في مشاكل مش محتاجاها، فبيكسر الـ performance ويصعّب الكود على أي حد بعده. الهدف من المقال ده: تعرف امتى تستخدمها بثقة، وامتى تبعد عنها.

علب ماتريوشكا روسية متداخلة فوق بعضها كتمثيل بصري لمفهوم الـ Recursion في البرمجة

المثال الواضح: علب الماتريوشكا

تخيل ان عندك علبة ماتريوشكا روسية كبيرة. لما بتفتحها بتلاقي جواها علبة أصغر شبهها. تفتح الصغيرة دي بتلاقي علبة أصغر منها، وهكذا. كل علبة شبه اللي قبلها، بس أصغر. آخر علبة بتكون مصمتة — مفيش جواها حاجة، وانت هنا بتقف. ده بالظبط الـ Recursion.

الدالة بتقول لنفسها: "اعمل نفس الشغل، بس على مدخل أصغر". وفي مرحلة معينة، بتوصل لمدخل بسيط جدًا تعرف ترد عليه بدون ما تنادي نفسها تاني. الـ "أبسط حالة" دي اسمها Base Case، والاستدعاء على المدخل الأصغر اسمه Recursive Case. لو نسيت تحط Base Case، الدالة هتفضل تنادي نفسها لحد ما الذاكرة تخلص — بالظبط زي ما لو علب الماتريوشكا مفيش فيها علبة مصمتة في الآخر.

التعريف العلمي بالظبط

في كتاب CLRS (Cormen et al., Introduction to Algorithms)، الـ Recursion معرّفة كـ "دالة تستدعي نفسها على مدخل أصغر تقدم نحو حالة قاعدية تنهي السلسلة". فيه شرطين لا غنى عنهم:

  • Base Case: حالة بسيطة بترد فيها الدالة قيمة مباشرة بدون استدعاء جديد.
  • Recursive Case: استدعاء الدالة لنفسها بمدخل أقرب للـ Base Case.

لو فقدت أي شرط من الاتنين دول، الـ Recursion كارثة. بدون Base Case → loop لا نهائي → Stack Overflow. وبدون التقدم نحو الـ Base Case (يعني المدخل بيقل في كل استدعاء) → نفس الكارثة. القاعدة دي بتنطبق على أي لغة برمجة، مش Python بس.

أول كود: حساب factorial

أبسط مثال كلاسيكي هو حساب factorial (المضروب). تعريفه: n! = n × (n-1) × (n-2) × ... × 1. مثلاً 5! = 5 × 4 × 3 × 2 × 1 = 120. لاحظ ان 5! = 5 × 4!، وان 4! = 4 × 3!، وهكذا. ده تعريف ركركورسي بطبيعته:

Python
def factorial(n):
    # Base Case: لما n = 0 أو 1، الإجابة 1
    if n <= 1:
        return 1
    # Recursive Case: n * (n-1)!
    return n * factorial(n - 1)

print(factorial(5))  # 120
print(factorial(10)) # 3628800

السطر return n * factorial(n - 1) هو اللي بيخلّيها Recursive. الدالة بتنادي نفسها بمدخل أصغر بـ 1. لما n توصل لـ 1، بتنزل بالقيمة 1 وبتبدأ السلسلة تتفك من تحت لفوق. لاحظ ان الـ Base Case مكتوب قبل الـ Recursive Case — ده مش تفصيلة، ده ضروري عشان الدالة تعرف توقف.

ازاي الـ Call Stack بيشغّل الدالة من جوه

كل لما الدالة تنادي نفسها، الـ Python (أو أي لغة) بتحفظ الاستدعاء ده في حاجة اسمها Call Stack. الـ Stack ده عبارة عن منطقة في الذاكرة، كل إطار (frame) فيها فيه: المتغيرات المحلية، مكان الرجوع للكود، والقيمة المرتقبة من الاستدعاء التالي.

لو نفّذت factorial(5)، اللي بيحصل في الذاكرة:

  1. الـ Stack بيحط frame لـ factorial(5) بيستنى نتيجة factorial(4).
  2. فوقه frame لـ factorial(4) بيستنى نتيجة factorial(3).
  3. فوقه factorial(3) بيستنى factorial(2).
  4. فوقه factorial(2) بيستنى factorial(1).
  5. factorial(1) بترد 1 مباشرة (Base Case).
  6. الـ Stack بيبدأ يتفك: factorial(2) ترد 2، factorial(3) ترد 6، factorial(4) ترد 24، factorial(5) ترد 120.
شاشة كود Python بتعرض دالة بتنادي نفسها مع تتبع Call Stack لتنفيذ Recursion

ده بالظبط ليه Recursion بتاكل ذاكرة. كل استدعاء بيحجز frame جديد، وكل frame بياخد حوالي 500 بايت في CPython 3.12 على نظام 64-bit. ده مش رقم كبير لـ 5 استدعاءات، بس لما توصل لـ 100,000 استدعاء بتبقى 50MB من الـ Stack لوحده.

المثال الجامد: التنقّل في شجرة الفولدرات

Recursion بتلمع في المشاكل اللي طبيعتها متفرّعة وغير معروفة العمق. أبسط مثال عملي: عد كل الملفات في فولدر وكل الفولدرات الفرعية اللي جواه، مهما كان عمق التفرّع:

Python
import os

def count_files(path):
    count = 0
    for entry in os.scandir(path):
        if entry.is_file():
            count += 1
        elif entry.is_dir():
            # Recursive Case: عد ملفات الفولدر الفرعي
            count += count_files(entry.path)
    return count

print(count_files("/home/user/projects"))

الـ Base Case هنا ضمني: لو الفولدر مفيهوش subfolders، الـ for loop بيخلص بدون استدعاء جديد. مفيش طريقة تكتب الكود ده بحلقة while عادية وانت ما تعرفش عدد الـ nested levels مقدمًا. الـ Recursion هي الأداة الطبيعية لأي structure شجري: ملفات، DOM، JSON، شجرة قرارات في AI، أو parsing لمعادلات رياضية.

Stack Overflow: ليه ودلوقتي بيظهر

RecursionError: maximum recursion depth exceeded هو الكابوس الأشهر للمبتدئ. السبب: نسيت الـ Base Case، أو الـ Base Case مش بيتحقق في كل المسارات. مثال مكسور بسيط:

Python
def broken(n):
    if n == 0:
        return 0
    return broken(n - 2)

broken(5)  # 5 → 3 → 1 → -1 → -3 → ... → RecursionError

المدخل 5 ما هيوصلش لـ 0 أبدًا لأنه فردي وبيقل بـ 2 كل مرة. الـ Base Case موجود نظريًا، بس مش بيتحقق على المسار ده. الحل: غيّر الشرط لـ if n <= 0.

قياس فعلي على Python 3.12 على لاب توب Linux عادي:

  • factorial(998) بيشتغل عادي ويرد القيمة (تحت الحد الافتراضي 1000).
  • factorial(1001) بيقع بـ RecursionError فورًا.
  • زمن تنفيذ factorial(900): حوالي 0.4 مللي ثانية.

ممكن ترفع الحد بـ sys.setrecursionlimit(10000)، بس ده بياكل ذاكرة بشكل خطي. 100 ألف استدعاء = حوالي 50MB من الـ Stack لوحده، ولو الـ thread محدود الـ stack size هتقع بـ SegmentationFault بدل RecursionError، وده أصعب 10 مرات في الـ debug. تجنّب رفع الحد كحل أساسي، وفكر في تحويل الـ Recursion لـ iteration بـ Stack صريح بدالة list.append() و list.pop().

متى Recursion يبقى اختيار غلط

Recursion مش دايماً أفضل أداة. الحالات اللي لازم تتجنبها فيها:

  1. لما العملية بسيطة وفيها loop واضح. حساب مجموع قائمة بـ Recursion دلال هندسي بدون فايدة. اكتب sum(list) خلاص.
  2. لما العمق المتوقع يتجاوز 1000. ابحث عن حل iterative بـ Stack صريح من نوع list أو collections.deque.
  3. لما نفس القيمة بتتحسب آلاف المرات. Fibonacci الـ Recursive السذج بياخد ثواني لحساب fib(35) بسبب الاستدعاءات المكررة (29.8 مليون استدعاء لرقم واحد!). الحل: Memoization أو تحويلها لـ iterative.
  4. لما الكود لازم يشتغل في environment بـ Stack محدود زي embedded systems أو AWS Lambda بحدود ذاكرة منخفضة.

الـ trade-offs بصراحة بدون تجميل

  • بتكسب: كود أنضف وأقرب للتعريف الرياضي للمشكلة. شجرة المسائل بتتفك بشكل طبيعي.
  • بتخسر: ذاكرة Stack. كل استدعاء فيه overhead حوالي 30-50 نانوثانية في Python — بطيء مقارنة بـ loop عادي.
  • الخطر الخفي: الـ debugging أصعب. الـ stack trace بيكون عميق ومتكرر وبيخفي السبب الحقيقي للخطأ.
  • الافتراض الأساسي: ان عمق الـ Recursion معروف ومحدود. لو مش متأكد، حوّل لـ iterative.

اللغات اللي بتدعم Tail Call Optimization (TCO) زي Scheme و Haskell بتعرف تخلّي Recursion الذيلية (لما الاستدعاء الأخير في الدالة هو نداء نفسها) تشتغل بدون أكل Stack. Python و JavaScript مش بيدعموا TCO رسميًا، فأي Recursion عميقة فيهم بتأكل ذاكرة فعلية مهما كان شكلها.

الخطوة التالية

افتح أي مشروع Python أو JavaScript عندك ودوّر على أي حلقة while بتعالج structure شجري (JSON متداخل، DOM، فولدرات). جرب تعيد كتابتها بـ Recursion وقارن. لو الكود بقى أوضح وأقصر، استخدمها. لو بقى أبطأ أو الحلقة العادية كانت أبسط أصلاً، ارجع لها. أهم حاجة: قبل ما تكتب أي دالة Recursive، اسأل نفسك السؤال ده — "ايه الـ Base Case؟". لو ما عندكش إجابة في 5 ثواني، متكتبهاش.

المصادر

  • Cormen, T. H., Leiserson, C. E., Rivest, R. L., & Stein, C. (2009). Introduction to Algorithms (3rd ed.). MIT Press — الفصل الرابع: Divide and Conquer.
  • Abelson, H., & Sussman, G. J. (1996). Structure and Interpretation of Computer Programs (2nd ed.). MIT Press — قسم 1.2: Procedures and the Processes They Generate.
  • Python Software Foundation. sys.setrecursionlimit() documentation. https://docs.python.org/3/library/sys.html
  • Python Software Foundation. The Python Language Reference — Execution model. https://docs.python.org/3/reference/executionmodel.html
  • Aho, A. V., Sethi, R., & Ullman, J. D. (1986). Compilers: Principles, Techniques, and Tools. Addison-Wesley — قسم 7.2: Runtime Stack.

هل استفدت من المقال؟

اطّلع على المزيد من المقالات والدروس المجانية من نفس المسار المعرفي.

تصفّح المدونة