الفريق العربي للبرمجةأرشيف المنتديات · 2000 – 2023
نسخة أرشيفية للقراءة فقط — التسجيل والمشاركة مغلقان، والمحتوى محفوظ كما كان.

تقنية الإستدعاء الذاتي

بدأه رغيد الطيب في 11 فبراير 2005 · 13 رد · 8,068 مشاهدة · في Microsoft Visual Basic.NET
مشاركة: واتساب X فيسبوك تيليجرام
#1 صاحب الموضوع

السلام عليكم ورحمة الله وبركاته ...

لقد فكرت في عمل سلسة من المواضيع ذات الاهمية للمبرمجين بحيث نناقش فيها بعض المواضيع التي نغفل عنها دائماً او لاتكون هناك معلومات بمتناول الجميع عنها وسوف اسعى فيها لفائدة المبرميجن الذين لهم خبرة لابأس بها في الفيجوال بيسك وكما اني لن انسى التركيز على المبتدئين الذين انا منهم ... كي نتشارك جميعاً في خلق روح للنقاش الجماعي البناء بعيداً عن مناقشة موضوع معين او برنامج بحد ذاته اذ اني لاحظت كون المنتدى اصبح مستودعاً لنسخ حلول المشاكل البرمجية ولم يعد مقراً للناقش حول هذة المشاكل ومحاولة كل فرد منا لطرح مالديه لفائدة الغير ... طبعاً هذا لاينفي روح التعاون التي نلمسها جميعاً بين المشاركات وما يتليها من ردود سريعة لاتدل إلا على وجود هذة الروح فينا ... ولكن ما يؤسفني هنا هو عدم محاولة فهم الحل بالقدر الذي يهتم فيه الجميع بالحصول على الحل ...

لهذا اتمنى من الجميع ان يساعدني في جعل هذه السلسة خالية من المشاركات التي لا تنتمي الى الموضوع الذي نناقشه بحيث نجعل حلقات هذة السلسة مرجع غني لكل فرد فينا في حال نسيانه بعض الطرق او التقنيات او المهارات المهمة ...

-------------------------------------------------------------------------------------------

في هذا الموضوع الجديد من مواضيع [ السلسة الذهبية في المواضيع العلمية ] .. سوف نتطرق بإذن الله تعالى الى موضوع تقنية الإستدعاء الذاتي ويطلق عليه في الانجليزية Recursion وهو موضوع مهم جداً في بناء البرامج واللوغارتيمات التي تكون درجة من التعقيد ...

طبعاً الموضوع يعتبر موضوع خارج نطاق اهتمام المبتدئ في البرمجة لذا فمن الافضل ان تكون ملماً بجميع الاساسيات ولك خبرة لا بأس بها في تصميم دوال خاصة بك حتى تستطيع إستيعاب الفكرة من الاستدعاء الذاتي ... ولكي لا يصاب من لم يفهم ما سوف يأتي معنا بعد قليل بالاحباط وجب التنبيه الى ان الامر يحتاج الى توضيح دقيق وضرب اكبر كمية ممكنة من الامثلة مع شرح لخطوات العمل بالتفصيل الممل ومع ذلك كله فأن نسبة ان تصل الفكرة للجميع هي نسبة صغيرة جداً ولا اظنها تتجاوز 30% كثيراً ... وذلك ان الدور الرئيسي في عدم فهم المعلومة هو سوء نقلها وهذا ما اظنني اعاني منه خاصة مع موضوع معقد بهذا الشكل ... لهذا كله ... همسة في أذن القارئ إن لم تفهم ما سوف يأتي معنا فقم بإلقاء اللوم اولاً على ناقل المعلومة (واضعاً في بالك طبعاً سلامة نيته ومحاولته للإفادة وإن كان الاخفاق نصيبه ) .. وثانياً ضع لومك على قلة الامثلة وعدم وضوحها وبساطتها ثم اخيراً على مدى إللمامك باللغة ( كما أن إهمالك للموضوع في هذة الفترة وعودتك اليه بعد فترة او بحثك عن مصدر آخر للمعلومات سوف يجعل الامر اكثر وضوحاً خاصة وان الامر يستحق ان تبذل مجهوداً فيه وذلك لمن يريد تعزيز فكره البرمجي بهذا السلاح خاصة و أن الاستدعاء الذاتي هو سلاح لايستهان به ابداً - في عالم البرمجة طبعاً ! - ) ....

ملاحظة اخيرة :

معظم الامثلة هي امثلة إرتجالية وليدة اللحظة لهذا فهي تحتمل ورود الخطأ فيها بشكل كبير .. كما ان معظم الامثلة التي سوف اذكرها يمكنك فيها الاستغناء عن الاستدعاء الذاتي وسوف تحقق نفس النتائج اي ان تضمين الاستدعاء الذاتي لن يزيد كفائة الكود في معظم هذة الامثلة وذلك لاني حاولت فقط تضمين اكبر قدر ممكن من الامثلة لتسهيل فهم الموضوع ... ولكن كن ذا ثقة بأنه يوجد حالات كثيرة سوف تختصر فيها عشرات الاسطر البرمجية باستخدامك الاستدعاء الذاتي ...

والان دعنا نبدء بإسم الله الرحمن الرحيم

---------------------------

مفهوم الاستدعاء الذاتي :

---------------------------

هو عبارة عن إستدعاء الاجراء او الدالة لنفسها فمثلاً نحن قد اعتدنا كثيراً ان نرى كود كالتالي :

Sub PrintOnForm(N As Integer)
    Dim I As Integer
    
    For I = 1 To N
        Print I
    Next
End Sub

حيث ان الاجراء PrintOnForm يقوم بطباعة الارقام من 1 الى N على الفورم وهو إجراء بسيط ولكن ماذا اذا كتبنا نفس الاجراء بالصيغة التالية :

Sub PrintOnForm(N As Integer)
    If N > 0 Then
       PrintOnForm N - 1
       Print N
    End If
End Sub

اذا جربت الكود السابق بالشكل التالي :

    PrintOnForm 5

سوف تلاحظ ان كلا الاجرائين قد قاما بطباعة الارقام من واحد الى خمسة والاجراء الاول كان مفهوماً اما الثاني ففيه بعض الغموض حيث لا نرى اننا قد قمنا باستخدام اوامر الدوارن فيه ( For او Until او While ) كما ان الاجراء يستدعي نفسه اي انه داخل شفرة الاجراء نجد إستدعاء لنفسه ولكن بتغير المدخلات :

PrintOnForm N - 1

لا تشغل بالك عند هذة اللحظة بكيفية عمل هذا الاجراء وكيف قام بطباعة الارقام ! وذلك لاننا سوف نتطرق لذلك لاحقاً ... المهم هنا التركيز على عملية جعل الاجراء او الدالة تستدعي نفسها ...

انظر الى المثال التالي :

Function Sum(N As Integer) As Long
    If N < 2 Then
       Sum = 1
    Else
       Sum = N + Sum(N - 1)
    End If
End Function

تقوم هذة الدالة بإستدعاء نفسها مرة اخرى في السطر

    Sum = N + Sum(N - 1)

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

--------------------------------------

لماذا نحتاج تقنية الاستدعاء الذاتي :

--------------------------------------

نحتاج الى هذة التقنية بشكل كبير عند الحاجة الى القيام بعدد متكرر من العمليات خذ مثلاً البحث عن جميع الملفات داخل القسم C: مثلاً كيف يتم ؟

يتم اولاً بالبحث عن جميع الملفات داخل الـ C: يلي ذلك البحث عن جميع الملفات داخل اول مجلد في السي لنقل مثلاً C:\Windows ثم بعد ذلك البحث عن الملفات داخل جميع المجلدات التي بداخل C:\Windows ثم العودة الى السي مرة اخرى والبحث في المجلد الثاني لنقل مثلاً C:\Program Files وفي جميع المجلدات الفرعية التي بداخله وهكذا ... والان اذا فكرت قليلاً سوف ترى ان جميع العمليات تتم بصورة مكررة فعندما ينتقل الى مجلد معين نقوم اولاً بمعرفة جمع الملفات التي فيه ثم جميع المجلدات التي فيه بحيث نكرر نفس العملية على جميع المجلدات التي بداخله والان اذا كان لدينا دالة بسيطة وضيفتها البحث عن جميع الملفات في المجلد الحالي ثم تستدعي نفسها بعدد المجلدات الفرعية التي في المجلد وفي كل استدعاء تكرر نفس العلمية ..

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

المهم في هذة اللحظة فهم ضرورة اللجوء الى الاستدعاء الذاتي لتسهيل العمليات المعقدة ...

----------------------------------------

اساسيات بناء دوال الاستدعاء الذاتي :

----------------------------------------

ان اهم شيء ينبغي التفكير فيه عند تطوير دالة إستدعاء ذاتي هو معرفة الشرط الذي سوف يتوقف عنده الاستدعاء فمن غير المنطقي ابداً ان يتم الاستدعاء الى ما لا نهاية ولهذا سوف تجد ان معظم دوال الاستدعاء الذاتي تتأكد من شرط النهاية الذي سيتوقف عنده الاستدعاء خذ مثلاً اذا كان لدينا الدالة Sum ووجدنا فيه السطر التالي :

Sum = 5

فهذا معناه نهاية الدالة وإعطائها قيمة ولكن اذا وجدنا هذا السطر داخل الدالة Sum :

Sum = Sum(12)

فأن هذا يدل على ان الدالة Sum سوف تستدعي نفسها مع تمرير القيمة 12 في هذا المثال ...

والان كيف يمكن ان نعرف ان هذا الاستدعاء هو الاستدعاء الاخير ؟

الاجابة على هذا السؤال تكمن في فحص المدخلات الى الدالة اي اننا سوف نفحص المدخلات في كل مرة الى ان نجد الشرط الذي ينبغي عنده التوقف ...

----------------------------------

امثلة و دوال بسيطة مع الشرح :

----------------------------------

دعنا نأخد الدالة Sum التي قمنا بها في المرة الاولى وكودها كان كالتالي :

Function Sum(N As Integer) As Long
    If N < 2 Then
       Sum = 1
    Else
       Sum = N + Sum(N - 1)
    End If
End Function

الان اذا ركزت قليلاً سوف تجد ان اول شرط تقوم به الدالة هو فحص هل المدخل (البارميتر) N يحمل قيمة اصغر من 2 فان الناتج حينها هو :

Sum = 1

و الواحد هنا هو قيمة صحيحة لايوجد فيها اي استدعاء ولكن اذا لم يتحقق هذا الشرط فان الناتج هو

    Sum = N + Sum(N - 1)

نلاحظ هنا ان الناتج في هذة الحالة سوف يكون المتغير N مضاف اليه ناتج الدالة نفسها ولكن مع تمرير N-1 في هذة المرة فاذا كانت قيمة N=4 فان السطر سوف يكون كالتالي :

 
    Sum = 4 + Sum(3)

ما سوف تلاحظه انه طالما الـ N اكبر من اثنين فانه سوف يتم استدعاء الدالة بتقليل قيمة الـ N وبالتالي فان في الاخير لابد وان يصل الى اقل من اثنين وينتهي عندها الاستدعاء الذاتي ..

والان دعنا نفهم ماذا سيحصل بالضبط عند استدعاء الدالة Sum طبعاً هذة الدالة وظيفتها ان تجمع القيم من الواحد والى القيمة N فاذا كانت القيمة N=4 فان الناتج هو :

N = 4
1 + 2 + 3 + 4 = 10

اي ان الناتج هو 10 وكذلك الحال اذا كان N=6 فان الناتج ينبغي ان يكون :

N = 6
1 + 2 + 3 + 4 + 5 + 6 = 21

طبعاً يمكن كتابة الدالة بدون الاستدعاء الذاتي بالشكل التالي:

Function Sum(N As Integer) As Long
    Dim I As Integer
    Dim R As Long
    
    For I = 1 To N
        R = R + I
    Next I
    
    Sum = R
End Function

وهي دالة بسيطة وواضحة ولكننا سوف نستغل بساطتها في توضيح مفهوم الاستدعاء الذاتي و تتبع خطواته ...

والان سوف نكتب الدالة بشكل الاستدعاء الذاتي ثم نتبع الخطوات التي سوف تحصل اذا كانت قيمة N=5 :

Function Sum(N As Integer) As Long
    If N < 2 Then
       Sum = 1
    Else
       Sum = N + Sum(N - 1)
    End If
End Function

والان عند الاستدعاء الاول للدالة بواسطتة كتابة السطر التالي مثلاً :

 Print Sum(5)

الان سوف تكون قمية N=5 وعندها فان اول خطوة سوف يفحص قيمة N هل اصغر من 2 وإلا فان الناتج هو :

   Sum = N + Sum(N-1)
   وهذا يعني 
   Sum = 5 + Sum(5-1)

وبالتالي فاننا يمكن ان نستخدم الجدول التالي :

    1) Sum(5) = 5 + Sum(4)
    2) Sum(4) = 4 + Sum(3)
    3) Sum(3) = 3 + Sum(2)
    4) Sum(2) = 2 + Sum(1)
    5) Sum(1) = 1

ما نلاحظه من الجدول السابق انه في الاستدعاء الاول كانت قيمة الدالة هي 5 مضاف اليها الدالة نفسها ولكن بتمرير 4 اي ان ناتج Sum(5) هو 5 مضاف الى Sum(4) ولاننا لانعرف كم ناتجSum(4) تقوم باستدعاء نفسها مرة اخرى الى ان نصل الى الخطوة الخامسة وهي الخطوة الحاسمة حيث تتنج قيمة عن الدالة لان المتغير N يساوي واحد اي اصغر من اثنين وبهذا فان ناتج الدالة Sum(1)=1 وعندئذ يتم تعويضه في الخطوة الرابعة فيتنج Sum(2)=2 +1 ومنها فان ناتج Sum(2)=3 وبالتالي يمكن تعويضه في الخطوة الثالثة حيث ان Sum(3)= 3+ 3 .... وهكذا حتى نحصل على قيمة للمعادلة Sum(5)=15 وذلك بالشكل التالي :

    1) Sum(5) = 5 + 10 = 15
    2) Sum(4) = 4 + 6 = 10
    3) Sum(3) = 3 + 3 = 6
    4) Sum(2) = 2 + 1 = 3
    5) Sum(1) = 1

حيث ان كل خطوة تنتج بجمع N الى الناتج من الخطوة السابقة وهكذا ...

لنفترض الان ان قيمة N=9 فان الخطوات ستكون كالتالي :

    1) Sum(9) = 9 + Sum(8)
    2) Sum(8) = 8 + Sum(7)
    3) Sum(7) = 7 + Sum(6)
    4) Sum(6) = 6 + Sum(5)
    5) Sum(5) = 5 + Sum(4)
    6) Sum(4) = 4 + Sum(3)
    7) Sum(3) = 3 + Sum(2)
    8) Sum(2) = 2 + Sum(1)
    9) Sum(1) = 1

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

    1) Sum(9) = 9 + 36 = 45
    2) Sum(8) = 8 + 28 = 36
    3) Sum(7) = 7 + 21 = 28
    4) Sum(6) = 6 + 15 = 21
    5) Sum(5) = 5 + 10 = 15
    6) Sum(4) = 4 + 6  = 10
    7) Sum(3) = 3 + 3  = 6
    8) Sum(2) = 2 + 1  = 3
    9) Sum(1) = 1

وسنحصل في الاخير على 45 وهو بالطبع ناتج :

N=9
1 + 2 + 3 + 4 + 5 + 6 + 7 + 8 + 9 = 45

المثال السابق كان لايجاد المجموع للعدد N وسوف اترك ايجاد المضروب لاي عدد N كاتمرين وبالطبع هو مشابه تماماً لفكرة المجموع باختلاف انه مضروب اي يستخدم الضرب وليس الجمع والدالة بصيغتها العادية هي :

Function Fact(N As Integer) As Long
    Dim I As Integer
    Dim R As Long
    
    R = 1
    For I = 1 To N
        R = R * I
    Next I
    
    Fact = R
End Function

ويبقى على القارئ تحويلها الى صيغة الاستدعاء الذاتي ( وهي ابسط من تعتبر تمرين ذلك لشبهها الكبير إن لم يكن لتماثلها مع الدالة Sum ) ...

-------------

مثال آخر :

-------------

والان دعنا نأخد مثال آخر نفرض ان لدينا نص معين ونريد تصميم دالة تقوم بعكس هذا النص من النقطة التي نريدها ولنسميها Rev بحيث يكون لها مدخلان الاول هو النص والثاني هو الموقع الذي سوف نعكس من عنده خذ مثلاً :

    Print Rev("Rgheed", 1)
    Print Rev("Rgheed", 3)
    Print Rev("Rgheed", 5)

بحيث يكون الناتج من السطر الاول هو "deehgR" .

ومن الثاني هو : "deeh"

ومن الثالث هو : "de"

لاحظ ان العكس في السطر الثاني من الموقع الثالث اي من الحرف h في كلمة Rgheed وبالتالي ينتج deeh وهكذا بالنسبة للثالث ...

والان لكي نعكس النص يجب ان نستخرج حرف بعد حرف ويمكن ذلك عن طريق الدالة Mid مثلاً لكي استخرج الحرف الثالث من كلمة Rgheed اكتب :

Mid("Rgheed",3,1)

وتعني من الكلمة Rgheed استخرج من الموقع الثالث حرف واحد وبالتالي فهو الـ h ...

وهكذا اذا اردنا ان نستخرج الحرف d من الموقع السادس:

Mid("Rgheed",6,1)

والان دعنا نكتب الدالة بصيغتها العادية كالتالي :

Function Rev(S As String, Pos As Integer) As String
    Dim T As String
    Dim I As Integer
    
    For I = Len(S) To Pos Step -1
        T = T + Mid(S, I, 1)
    Next
    
    Rev = T
End Function

ومن تركيب الدالة يتضح انها تبدء من آخر عنصر Len(S) الى الموقع الذي نريد العكس من عنده وهو Pos ثم نستخرج حرف بحرف وبحيث نستخرج الحرف الاخير ثم الذي قبله وهكذا وينتج في الاخير النص معكوس ...

والان دعنا نفكر كيف سنحولها الى صيغة الاستدعاء الذاتي ... اولاً وكما اسلفنا يجب التفكير في الشرط الذي سوف ننتهي عنده وذلك اعتماداً على المتغير المدخل وسوف نستخدم هنا المدخل Pos للفحص (كما استخدمنا المدخل N في المثال السابق ) ...

والفكرة التي سوف نستخدمها هنا هي اننا سوف نقوم بالعكس طالما الموقع Pos اصغر من موقع آخر عنصر بالشكل التالي :

Function Rev(S As String, Pos As Integer) As String
    If Pos <= Len(S) Then
       Rev = Rev(S, Pos + 1) + Mid(S, Pos, 1)
    End If
End Function

حيث اننا فحصنا هنا انه اذا كان Pos اصغر من موقع آخر عنصر Len(S) بالتالي نستمر بالعكس .... ولكي نفهم الامر دعنا نتتبع المثال لنفرض ان الكلمة هي Rgheed ونريد عكسها من الحرف الاول فاننا سوف نكتب التالي :

Print Rev("Rgheed",1)

من الواضح ان طول النص هو ستة حروف Len(S)=6 .... لاحظ ان الدالة كلها تعتمد على السطر :

Rev = Rev(S, Pos + 1) + Mid(S, Pos, 1)

اي انه اذا كانت قيمة الـ Pos هي واحد فان السطر السابق سوف يبدو كالتالي :

Rev = Rev(S, 1 + 1) + Mid(S, 1,1)
وهذا يعني
Rev = Rev(S,2) + "R"

لاحظ ان الحرف R جاء كناتج من الامر Mid(S,1,1)ويعني استخرج الحرف الاول ...

اما اذا كان الـ Pos=2 فان السطر سوف يبدو :

Rev = Rev(S, 2 + 1) + Mid(S, 2,1)
وهذا يعني
Rev = Rev(S,3) + "g"

وهكذا الى ان يكون الـ pos اكبر من الستة عندها يكون الراجع من الدالة هو فراغ اي ان Rev="" وذلك لان الشرط If Pos <= Len(S) Then لن يتحقق وبالتالي سوف نخرج من الدالة مباشرة عند الخطوة السابعة ...

والخطوات كاملة سوف تكون كالتالي

   1) Rev = Rev("Rgheed",2) + "R"
   2) Rev = Rev("Rgheed",3) + "g"
   3) Rev = Rev("Rgheed",4) + "h"
   4) Rev = Rev("Rgheed",5) + "e"
   5) Rev = Rev("Rgheed",6) + "e"
   6) Rev = Rev("Rgheed",7) + "d"
   7) Rev = ""

بعد الوصول الى الخطوة الاخيرة ( الخطوة السابعة) فان قيمة الدالة عندها هي فراغ "" ويمكن هنا ان نأخذ هذة القيمة صعوداً ونعوض فيها بالخطوة السادسة ثم الناتج نعوضه في الخامسة وهكذا .. وسوف يكون الحل كالتالي :

   1) Rev = "deehg" + "R"  = "deehgR"
   2) Rev = "deeh" + "g"    = "deehg"
   3) Rev = "dee" + "h"      = "deeh"
   4) Rev = "de" + "e"        = "dee"
   5) Rev = "d" + "e"         = "de"
   6) Rev = "" + "d"           = "d"
   7) Rev = ""

وبالتالي فأن الناتج الذي يهمنا هو عند الخطوة الاولى اي النص المعكوس "deehgR" ... ويمكنك ان تجرب نصوص اخرى او تبدء من اي مكان في النص ...

------------

مثال آخر :

------------

لنفرض الان اننا نريد ان نقوم بالبحث في مصفوفة خطية اسمها A(8) مكونة من ثمانية عناصر ومحتوياتها كالتالي :

    A(1) = 10
    A(2) = 20
    A(3) = 30
    A(4) = 40
    A(5) = 50
    A(6) = 60
    A(7) = 70
    A(8) = 80

والان لكي نقوم بعمل بحث عن اي عنصر في المصفوفة ابتداءً من موقع معين بحيث نرجع رقم الموقع الذي فيه العنصر او نرجع صفر في حال عدم وجود العنصر يمكن عندها كتابة دالة مثل هذة :

Function Find(A() As Integer, Item As Integer, Pos As Integer) As Integer

    Dim Max As Integer
    Dim I As Integer
    
    Max = UBound(A)
    
    For I = Pos To Max
        If A(I) = Item Then
           Find = I
           Exit Function
        End If
    Next I
    
End Function

في الدالة السابقة نقوم بتمرير المصفوفة والعنصر المراد البحث عنه Item وموقع البداية Pos ... خذ مثلاً :

Print Find(A(),60,1)
Print Find(A(),20,5)
Print Find(A(),45,1)

الناتج من السطر الاول هو 6 لان الموقع السادس في المصفوفة يساوي 60 اي ان A(6)=60 ولكن الناتج من السطر الثاني هو صفر فعلى الرغم من المصفوفة تحتوي على الرقم 20 في الموقع الثاني إلا اننا اخبرنا الدالة بان تبدء البحث من الموقع الخامس لهذا فأن الناتج هو 0 .... واما السطر الثالث فان الناتج فيه فهو صفر ايضاً وذلك لان العنصر 45 غير موجود بين عناصر المصفوفة A() .....

والان لكي نكتب المصفوفة بالشكل الذي نستخدم فيه الاستدعاء الذاتي يجب علينا اولاً ان نفكر بالشرط الذي يجب عنده الخروج .... وهو في هذة الحالة ان يتجاوز الموقع الذي نريد البحث فيه موقع آخر عنصر (Max=UBound(A) وفي هذة الحالة يكون الناتج من الدالة صفراً .... والشرط التالي ان يتم العثور على العنصر وفي هذة الحالة يخرج ايضاً ولكن مع ارجاع الموقع الذي عثر على العنصر فيه .. واما الشرط الثالث وهو في حالة ان العنصر غير موجود في الموقع الحالي ويوجد مواقع لم تتم زيارتها بعد فأن الدالة تستدعي نفسها ولكن مع زيادة الموقع Pos الى الموقع الذي يليه Pos+1 وبالتالي فأن الدالة قد تبدو بالشكل التالي :

Function Find(A() As Integer, Item As Integer, Pos As Integer) As Integer
    Dim Max As Integer
    Dim I As Integer
    
    Max = UBound(A)
    
    If Pos > Max Then
       Find = 0
    ElseIf A(Pos) = Item Then
       Find = Pos
    Else
       Find = Find(A(), Item, Pos + 1)
    End If
End Function

لنفترض الان اننا نبحث عن الرقم 60 من الموقع 1 فان الخطوات سوف تكون كالتالي :

    1) Find(A(), 60, 1) = Find(A(), 60, 2)
    2) Find(A(), 60, 2) = Find(A(), 60, 3)
    3) Find(A(), 60, 3) = Find(A(), 60, 4)
    4) Find(A(), 60, 4) = Find(A(), 60, 5)
    5) Find(A(), 60, 5) = Find(A(), 60, 6)
    6) Find(A(), 60, 6) = 6

في المرة الاولى سوف يكون الموقع اصغر من الـ Max وكذلك لن يعتر على العنصر 60 في الموقع الاول لهذا سوف يتنقل الى الموقع التالي وهكذا الى ان يصل الى الموقع السادس وفيه سوف يجد العنصر 60 لهذا سوف يكون الراجع هي قيمة الـ Pos اي 6 في هذة الحالة ثم سوف يتم التعويض بالقيمة 6 في الخطوة الخامسة ثم الرابعة ثم الثالثة وهكذا حتى الاولى وبهذا يكون الناتج هو 6 ... اي ان الحل سوف يكون كالتالي :

    1) Find(A(), 60, 1) = Find(A(), 60, 2) = 6
    2) Find(A(), 60, 2) = Find(A(), 60, 3) = 6
    3) Find(A(), 60, 3) = Find(A(), 60, 4) = 6
    4) Find(A(), 60, 4) = Find(A(), 60, 5) = 6
    5) Find(A(), 60, 5) = Find(A(), 60, 6) = 6
    6) Find(A(), 60, 6) = 6

وبنفس الطريقة يمكن التجربة عندما يكون العدد غير موجود في المصفوفة فانه سوف يصل الى الحالة الاخيرة التي يكون فيها Pos اكبر من Max وبهذا يتم ارجاع القيمة صفر ويتم التعويض بالقيمة صفر في الخطوات الى الخطوة الاولى ويكون عندها الراجع هو صفر ويعني ان العنصر غير موجود ...

لاحظ ان استخدام الدالة Find السابقة بدون الاستدعاء الذاتي كان اكثر كفائة من الحالة التي استخدمنا فيها الاستدعاء الذاتي لعدة اسباب منها اننا في حالة الاستدعاء الذاتي قمنا بعمليات فحص اكثر ... اي شرطان بدل الشرط الواحد بدون الاستدعاء الذاتي :

1-هل Pos اكبر من Max

2-هل A(Pos)=Item

كما ان العبارة Max=Ubound(A) يتم تنفيذها مرة واحدة (بدون الاستدعاء الذاتي) واما في حالة الاستدعاء الذاتي فانه يتم تنفيذها بعدد المرات التي يتم استدعاء الدالة فيها ... (طبعاً يمكن تلافي ذلك بتمرير القيمة العظمى للمصفوفة بدلاً من حسابها يدوياً ) ....

على الرغم العيوب السابقة فانه يمكن تحسين البحث ولكني هنا اعتمد التبسيط لهذا فأن تركيزنا هو كيف يعمل الاستدعاء الذاتي وكيف تتمكن من تتبع الدوال التي كتبت بصيغة الاستدعاء الذاتي ....

--------------------

حالات اكثر تعقيداً :

--------------------

لاحظنا في الامثلة السابقة ان جميع الحالات تم فيها استدعاء الدالة مرة واحدة فقط اي انه في كل استدعاء للدالة فانها تستدعي نفسها مرة واحدة ولكن يوجد حالات تقوم الدالة فيها باستدعاء نفسها اكثر من مرة وهنا تكمن قوة الاستدعاء الذاتي وسوف اضرب اكثر الامثلة شهرة في هذا المجال وهي حساب سلسلة Fibonacci و هذة السلسلة لها معادلة

خاصة حيث ان كل عنصر فيها هو عبارة عن مجموع العنصرين اللذان يسبقانه في هذة السلسلة وهي كالتالي :

0 = 1
1 = 1
2 = 2
3 = 3
4 = 5
5 = 8
6 = 13
7 = 21
8 = 34
9 = 55
  : وهكذا
  :

فاذا نظرت الى العنصر رقم 6 مثلاً فانه عبارة عن جمع العنصر رقم 5 (الذي يساوي 8) والعنصر رقم 4 (الذي يساوي 5) وبالتالي فان العنصر رقم 6 يساوي 13 ....

Fib(6)=Fib(5)+Fib(4)

وينطبق هذا الوضع على جميع العناصر ماعدا العنصرين رقم صفر ورقم واحد فكلاهما يحملان القيمة 1 ...

يتضح مما سبق انه اذا كانت اسم الدالة مثلاً Fib فان Fib(N) تساوي 1 اذا كان N =0 او N=1 وغير ذلك فان Fib(N)= Fib(N-1)+Fib(N-2) .... بالشكل التالي :

         |= 1           	 N<=1
Fib(N) |= Fib(N-1)+Fib(N-2)    N>1

لهذا فأن كتابة دالة الاستدعاء الذاتي سيكون بسيطاً جداً بينما اذا فكرت في بنائه بدون استدعاء ذاتي فان هذا سيكون مرهقاً ....

والدالة هي كالتالي :

Function Fib(N As Integer) As Integer
    If N <= 1 Then
       Fib = 1
    Else
       Fib = Fib(N - 1) + Fib(N - 2)
    End If
End Function

---------

خاتمة :

---------

مما سبق يمكن رؤية ان استخدام الاستدعاء الذاتي بالشكل الصحيح سوف يمكنك من تبسيط المسائل البرمجية المعقدة خاصة وان معظم الخوارزميات او اللوغارتيمات القوية تستخدم تقنية الاستدعاء الذاتي بشكل اساسي فيها مثل لوغارتيمات الفرز السريع ومن اشهرها QuickSort الذي يستخدم تقنية الاستدعاء الذاتي لتنفيذ مبدأ فرق تسد (Divide And Conquer) وهو من المبادئ المهمة في تصميم اللوغارتيمات كما ان معظم لوغارتيمات ضغط الملفات و البحث في القرص الصلب تستفيد كثيراً من هذة التقنية في عملها ...

اتمنى للجميع حظاً موفقاً و السلام عليكم ورحمة الله وبركاته ...

1

مواضيع برمجية تقنية جديدة

----------------------------------------------------------------------------------------------------------------------------------------------

• إستغلال خدمة الـ RSS في برامجنا ومواقعنا ! (VB.NET)

----------------------------------------------------------------------------------------------------------------------------------------------

• برنامج للبحث في الملفات ومحتواياتها (VB.NET)

----------------------------------------------------------------------------------------------------------------------------------------------

• فئة للتراسل بين برنامجين في جهاز واحد ! (VB.NET)

----------------------------------------------------------------------------------------------------------------------------------------------

مواضيع برمجية تقنية قديمة

----------------------------------------------------------------------------------------------------------------------------------------------

• إستغلال رسائل البالونات Balloons في الوينذوز أكس بي(VB.NET)

----------------------------------------------------------------------------------------------------------------------------------------------

• مقالة : لماذا يجب ان نعتني بشفرات برامجنا !؟- (VB6.0)

----------------------------------------------------------------------------------------------------------------------------------------------

• لمطوري البرامج و مواقع الويب - برنامج تركيب ألوان - (VB6.0)

----------------------------------------------------------------------------------------------------------------------------------------------

• برنامج : بسيط للتحكم بالنظام, عن طريق الريجستري - (VB6.0)

----------------------------------------------------------------------------------------------------------------------------------------------

• برنامج : لتقييد و منع تشغيل البرامج الاخرى - (VB6.0)

----------------------------------------------------------------------------------------------------------------------------------------------

• النوافذ المنبثقة و طريقة لإستغلالها في برامجنا -(VB.NET)

----------------------------------------------------------------------------------------------------------------------------------------------

• تقنية الإستدعاء الذاتي - (VB.NET , VB6.0)

----------------------------------------------------------------------------------------------------------------------------------------------

• مصفوفات الأدوات Control Arrays افكار وتلميحات ! - (VB.NET)

----------------------------------------------------------------------------------------------------------------------------------------------

• أفكار و تلميحات[ المعنى الحقيقي للـ True و False ] - أمثلة (VB.NETVB6.0)

----------------------------------------------------------------------------------------------------------------------------------------------

• دوال الـ APIs و مايقابلها في الدوت نت ! - (VB.NET)

----------------------------------------------------------------------------------------------------------------------------------------------

#2

ما شاء الله اخي رغيد موضوع فعلا قمة في الروعة

عاجز عن شكرك لكن لك دعوة في السر ولجميع الاخوة الاعضاء

#3

الله أكبر .. مشاء الله لا قوة إلا بالله .. بصراحة جهد جبار

فلسطين الزاهية بألوان الحب والسلام

ذلك الطعم الزيتوني الذي يتدفق عسلاً من أفواه بساتينها وحدائقها

تشتاق إليها نفوس المتقين وترنوا للسجود فيها جباه المسلمين

#5

جزاك الله خيرا

فعلا موضوع مهم ومعلومة جديدة ومفيدة لي

ونحن دائما بحاجة اليها الى مثل هذه الموضيع المهمة للمبرمج والمحلل

#6

مشكور اخي رغيد على المواضيع الجميله ...

#7

تسلم على الموضوع الرائع

من ساعة ماجيت المنتدى نشط معاك اكثر واكثر...

يعطيك الف عافيه

وجزاك الله الف خير

#8

موضوع رائع وجهد جبار، الله يعطيك ألف عافية...

أود التعقيب على هذا الشرح الجيمل والكافي والوافي بفكرة بسيطة.

عندما يريد المبرمج استخدام تقنية الاستدعاء الذاتي يجب أن يكون صاحب بعد نظر، أي ليعرف الوقت المناسب لاستخدامه يجب أن يعرف ماذا سوف تكون النتيجة، ويحسب النتيجة بشكل عكسي. أي بدل أن يعد خطوات العمل بالترتيب التصاعدي يجب دائماً أن يعد بشكل تنازلي (... - 4 - 3 - 2 - 1). وذلك لأنه، بهذه التقنية، يجمع نتيجة لم تحصل بعد مع قيمة ما مثلاً.

طبعاً هذا كان واضحاً في شرحك وأمثلتك البسيطة والقوية، ولكني وددت التنبيه إليه كتابياً ليستطيع إخوتنا الذين لا يعرفون هذه التقنية كيف يستطيعون استيعابها.

جزاك الله خيراً، ودمت ذخراً للأمة الإسلامية والعربية..

#9

السلام عليكم

لو كنت عندك كان قبلت رأسك والله

شرح جبار ومبسط جدا وممتاز

شكرًا لك

#10

رائع فعلا كثير نحتاج الى الاستدعاء الذاتي..موضوع ممتاز

تسلم ..بحاجة الى مواضيع ودروس ..بسبب الركود اصبح المنتدى

طرح مشكلة وحل ...شكرا للجميع

#11

السلام عليكم

جزاك الله خيرا

والى الأمام دوما.... وفي انتظار المزيد من الإبداعات

اكتشافات علمية في قيام الليل

من فوائد الحمـــى

هل تريد اكثر من مليار حسنه في دقيقه واحده ......

فقط قل هذه الجمله وستأخذ عن كل مسلم حسنه من غير أن ينقص من أجرهم شيئا

اللهم أغفر للمؤمنين و المؤمنات و المسلمين و المسلمات الأحياء منهم و الأموات

وتأخذ عن كل واحد حسنه فانظر كم عدد المسلمين

كلمتان خفيفتان على اللسان ثقيلتان في الميزان حبيبتان على الرحمن

" سبحان الله وبحمده سبحان الله العظيم"

* دقيقة واحده : تستطيع ان تقول لاحول ولا قوة الا بالله اكثر من ( 40 ) مره وهي كنز من كنوز الجنه .

دقيقة واحدة : تستطيع ان تصلي على النبي صلى الله عليه وسلم ( 20 ) مره فيصلي عليك الله مقابلها

( 200 ) مره وحطة عنه ( 200 ) خطيئة من خطاياه ، ورفع له ( 200 ) درجة .

#12

تسلم على الموضوع الرائع

والى الأمام دوما.... وفي انتظار المزيد

#13

تسلم على الشرح الوافي

#14

اذا ممكن تكمل معانا الشرح لداله gcd 

تم تعديل هذه المشاركة بواسطة eleen في 21 فبراير 2013 في 22:52

مواضيع مشابهة