الاستدعاء الذاتي في Lua
نفّذ خوارزميات استدعاء ذاتي وافهم تحسين الاستدعاء الذّيلي في Lua
الاستدعاء الذاتي في Lua درس مجاني في Lua Academy على CoddyKit. هذا هو الدرس 4 من أصل 4. يمكنك قراءة الدرس كاملاً أدناه مجاناً — ثم تمرن عليه مباشرة في المتصفح باستخدام محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7. هذا الدرس جزء من مسار التعلم في Lua Academy، وتقدمك يتزامن عبر الويب وتطبيق CoddyKit. تتضمن دورة Lua Academy 4 دروس في المجموع.
ما هو الاستدعاء الذاتي؟
الاستدعاء الذاتي هو أن تستدعي الدالة نفسها لحل نسخة أصغر من المشكلة نفسها. تحتاج كل دالة استدعاء ذاتي إلى حالة أساس (تتوقف عندها) وحالة تكرارية (تُصغّر المشكلة فيها). ومن دون حالة أساس، يؤدي الاستدعاء الذاتي إلى امتلاء المكدس.
local function countdown(n)
if n <= 0 then
print("Blast off!")
return
end
print(n)
countdown(n - 1)
end
countdown(5)
-- 5, 4, 3, 2, 1, Blast off!المضروب
المضروب هو المثال الكلاسيكي على الاستدعاء الذاتي: n! = n × (n-1)! مع حالة الأساس 0! = 1. يُنقص كل استدعاء قيمة n بمقدار 1 حتى الوصول إلى حالة الأساس. ثم تعود النتائج إلى أعلى مكدس الاستدعاءات، وتضرب القيم أثناء ذلك.
local function factorial(n)
if n <= 1 then return 1 end
return n * factorial(n - 1)
end
print(factorial(1)) -- 1
print(factorial(5)) -- 120
print(factorial(10)) -- 3628800متتالية فيبوناتشي
متتالية فيبوناتشي مثال كلاسيكي آخر: fib(n) = fib(n-1) + fib(n-2). ويكون الاستدعاء الذاتي الساذج أُسّي الزمن، إذ يعيد حساب القيم نفسها مرارًا. سنرى أن التخزين التذكّري يقدم حلًا لذلك، لكن لنبدأ بالشكل الأساسي.
local function fib(n)
if n <= 1 then return n end
return fib(n - 1) + fib(n - 2)
end
for i = 0, 10 do
io.write(fib(i) .. " ")
end
print()
-- 0 1 1 2 3 5 8 13 21 34 55الاستدعاء الذاتي مع التخزين التذكّري
يخزن التخزين التذكّري النتائج المحسوبة سابقًا في جدول. افحص ذاكرة التخزين أولًا عند كل استدعاء. فإذا كانت القيمة موجودة، فأرجِعها فورًا. وإلا فاحسبها وخزّنها. ويحوّل ذلك حساب فيبوناتشي من زمن أُسّي إلى زمن خطي.
local memo = {}
local function fib(n)
if memo[n] then return memo[n] end
if n <= 1 then return n end
local result = fib(n-1) + fib(n-2)
memo[n] = result
return result
end
print(fib(40)) -- 102334155 (fast!)تحسين الاستدعاء الذّيلي
تنفذ Lua تحسين الاستدعاء الذّيلي (TCO): عندما يكون الإجراء الأخير في الدالة هو استدعاء دالة أخرى، تعيد Lua استخدام إطار المكدس الحالي. وهذا يجعل الدوال ذات الاستدعاء الذاتي الذّيلي تستخدم مساحة مكدس مقدارها O(1). وليس كل استدعاء ذاتي ذيليًا — يجب أن يكون الاستدعاء هو آخر شيء في الدالة.
-- Tail-recursive factorial using accumulator
local function factTail(n, acc)
acc = acc or 1
if n <= 1 then return acc end
return factTail(n - 1, n * acc) -- tail call
end
print(factTail(10)) -- 3628800
-- Can handle very large n without stack overflowاجتياز الشجرة
يعبّر الاستدعاء الذاتي عن اجتياز الشجرة بصورة طبيعية. والشجرة جدول يحوي الحقول value وleft وright. ولا تختلف عمليات الاجتياز التمهيدي والوسطي والختامي إلا في توقيت معالجة القيمة بالنسبة إلى الاستدعاءات الذاتية.
local function inorder(node)
if node == nil then return end
inorder(node.left)
io.write(node.value .. " ")
inorder(node.right)
end
local tree = {
value=4,
left={value=2, left={value=1}, right={value=3}},
right={value=6, left={value=5}, right={value=7}}
}
inorder(tree) -- 1 2 3 4 5 6 7الاستدعاء الذاتي المتبادل
يمكن لدالتين استدعاء إحداهما الأخرى (استدعاء ذاتي متبادل). وفي Lua، تحتاج إلى تصريحات مسبقة: صرّح بالمتغيرات المحلية أولًا، ثم أسنِد الدوال إليها. وبهذه الطريقة يمكن لكل دالة الإشارة إلى متغير الدالة الأخرى، الذي يكون موجودًا أصلًا في النطاق.
local isEven, isOdd
isEven = function(n)
if n == 0 then return true end
return isOdd(n - 1)
end
isOdd = function(n)
if n == 0 then return false end
return isEven(n - 1)
end
print(isEven(10)) -- true
print(isOdd(7)) -- trueالنسخ العميق التكراري
يُعد الاستدعاء الذاتي مثاليًا للعمليات على البنى المتداخلة. ويعني النسخ العميق لجدول نسخ محتوياته، ثم نسخ أي جداول متداخلة فيه بصورة تكرارية (بحيث لا يؤثر تعديل النسخة في الأصل).
local function deepCopy(orig)
local copy
if type(orig) == "table" then
copy = {}
for k, v in pairs(orig) do
copy[deepCopy(k)] = deepCopy(v)
end
setmetatable(copy, getmetatable(orig))
else
copy = orig
end
return copy
end
local a = {1, {2, 3}}
local b = deepCopy(a)
b[2][1] = 99
print(a[2][1]) -- 2 (unchanged)خوارزمية التعبئة بالانتشار
تُنفذ التعبئة بالانتشار (المستخدمة في برامج الرسم وخرائط الألعاب) بصورة طبيعية باستخدام الاستدعاء الذاتي. فبدءًا من خلية، علّمها، ثم املأ كل جار غير مُزار بصورة تكرارية. وينتهي الاستدعاء الذاتي عند الوصول إلى الحدود أو إلى خلايا سبقَت زيارتها.
local grid = {
{0,0,0,1,0},
{0,1,0,1,0},
{0,1,1,1,0},
{0,0,0,0,0},
}
local function fill(g, r, c)
if r<1 or r>#g or c<1 or c>#g[r] then return end
if g[r][c] ~= 0 then return end
g[r][c] = 2 -- mark visited
fill(g,r-1,c); fill(g,r+1,c)
fill(g,r,c-1); fill(g,r,c+1)
end
fill(grid, 1, 1)
print(grid[1][1], grid[2][1]) -- 2 2الوعي بعمق المكدس
مكدس الاستدعاءات الافتراضي في Lua محدود (عادةً نحو 200 مستوى للاستدعاءات غير الذيلية). ويؤدي الاستدعاء الذاتي غير الذّيلي العميق إلى خطأ «امتلاء المكدس». ومن الحلول: تحويله إلى استدعاء ذاتي ذيلي، أو استخدام مكدس صريح (جدول)، أو استخدام coroutines للمهام التكرارية الكبيرة.
-- Simulate deep recursion danger
local function depth(n)
if n == 0 then return "done" end
return depth(n - 1) -- tail call, safe
end
print(depth(100000)) -- done (tail call, no overflow)
-- Non-tail: limited depth
local function countDown(n)
if n == 0 then return 0 end
return 1 + countDown(n - 1) -- NOT a tail call
end
-- countDown(10000) would stack overflowتحقق سريع
أي مما يلي يُعد استدعاءً ذيليًا صحيحًا في Lua؟
مراجعة: الاستدعاء الذاتي
الملخص:
- تحتاج كل دالة استدعاء ذاتي إلى حالة أساس وحالة تكرارية
- تحسّن Lua الاستدعاءات الذيلية (عندما يكون الاستدعاء هو الإجراء الأخير) — بمكدس O(1)
- يحوّل التخزين التذكّري الاستدعاء الذاتي الأُسّي إلى خطي
- يتطلب الاستدعاء الذاتي المتبادل تصريحات مسبقة
- الاستدعاء الذاتي العميق في الأشجار/الرسوم البيانية طبيعي؛ لكن راقب عمق المكدس في الاستدعاءات غير الذيلية
الأسئلة الشائعة
هل درس «الاستدعاء الذاتي في Lua» مجاني؟
نعم — نص درس «الاستدعاء الذاتي في Lua» كامل متاح مجاناً هنا على الويب. لتمرينه بشكل تفاعلي (محرر أكواد مدمج ومدرس ذكاء اصطناعي متاح 24/7) وفتح باقي دورة Lua Academy، انتقل إلى CoddyKit PRO. تتضمن دورة Lua Academy 4 دروس في المجموع.
ماذا ستتعلم في «الاستدعاء الذاتي في Lua»؟
نفّذ خوارزميات استدعاء ذاتي وافهم تحسين الاستدعاء الذّيلي في Lua تتمرن على Lua Academy مع أكواد عملية تشغلها مباشرة في المتصفح، ومدرس ذكاء اصطناعي متاح 24/7 يجيب على أسئلتك أثناء عملك.
هل أحتاج إلى خبرة سابقة لأبدأ Lua Academy؟
لا تُشترط خبرة سابقة. Lua Academy على CoddyKit منظم للمبتدئين حتى المتقدمين، لذا يمكنك البدء من هنا أو من البداية والتقدم بسرعتك الخاصة. هذا هو الدرس 4 من أصل 4.
كم من الوقت يستغرق درس «الاستدعاء الذاتي في Lua»؟
معظم دروس CoddyKit تستغرق حوالي 5–10 دقائق. كل منها موجز وتفاعلي، لذا تحرز تقدماً مستمراً وتستأنف من حيث توقفت عبر الويب والتطبيق.
هل يمكنني كتابة وتشغيل أكواد في درس Lua Academy هذا؟
نعم. كل درس في Lua Academy يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.
جميع الدروس في هذه الدورة
- تعريف الدوال واستدعاؤها
- القيم المرجعة المتعددة
- المعامل ... والمعاملات المتغيرة العدد
- الاستدعاء الذاتي في Lua