0Pricing
Lua Academy · درس

الاستدعاء الذاتي في 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 يتضمن محرر أكواد مدمج، لذا تكتب وتشغل أكواداً حقيقية مباشرة في متصفحك وتحصل على تعليقات فورية من الذكاء الاصطناعي — بدون إعداد محلي.

جميع الدروس في هذه الدورة

  1. تعريف الدوال واستدعاؤها
  2. القيم المرجعة المتعددة
  3. المعامل ... والمعاملات المتغيرة العدد
  4. الاستدعاء الذاتي في Lua
← العودة إلى Lua Academy