توليد الفروع العشوائية في بايثون

صورة

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

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

لتطبيق رمز بدرجة كبيرة من حرية العمل في نظام التشغيل ، والذي لن يكون في الوقت نفسه مجرد مجموعة فوضوية من التعليمات القابلة للتنفيذ ، ظهر نموذج يتكون من 3 وحدات.

  1. وحدة التوليد العشوائي للشفرة التنفيذية الرئيسية
  2. وحدة التعليم العشوائي
  3. وحدة "رؤية الكمبيوتر" لكائنات نظام التشغيل

ستناقش هذه المقالة الوحدة الأولى ، والتي هي حتى الآن مجرد جيل من التفرع العشوائي ، أي الإنشاءات مثل "if-elif-else". لماذا تتفرع؟ لأن حياة أي كائن حي بشكل عام تتكون من ردود فعل مشروطة: كل ما نقوم به هو استجابة للمعلومات المدركة. تنقسم الخلايا في حالة حدوث ظروف معينة ، ويحاول الضحية الهروب إذا رأى مفترسًا أقوى ، وإذا كان أضعف ، يمكنه محاولة مهاجمته ، وتناثر الصراصير إذا كان الضوء يضيء ، يذهب الشخص لتناول الطعام ، إذا كان جائعًا ، إلخ. إلخ - هذا الصف لا نهاية له. لا توجد أفعال مستقلة منفصلة ليست مشروطة بأي شيء. وبالتالي ، فإن سلوك الكائنات الحية على وجه الخصوص يوصف بأنه رد فعل على الحالة: إذا [شيء] ثم [شيء]. نحن نحاول توليد هذا السلوك.

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

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

حاليا ، تم تحويل الوظيفة المذكورة بشكل طفيف:

def make_solution(p_random, p_deter):                       
    deter_flag = 0
    random_flag = 0
    if p_random >= random.random():
            p_random-=0.01                                  #  
            p_deter+=0.01
            random_flag = 1
    if p_deter >= random.random():
            p_deter-=0.01                                   #  
            p_random+=0.01
            deter_flag = 1
    if random_flag == 1 and deter_flag == 0:
        return(p_random, p_deter, 1)
    elif deter_flag == 1 and random_flag == 0:
        return(p_random, p_deter, -1)
    else:
        return (p_random, p_deter,0)

عند الإدخال ، يستغرق الأمر احتمالين (افتراضيًا في البداية كلاهما يساوي 0.5) ، وبعد ذلك يتحقق من تشغيلهما واحدًا تلو الآخر. ينخفض ​​الاحتمال المحفز نفسه بنسبة 1٪ وفي نفس الوقت يزيد الآخر بنسبة 1٪. لذلك ، في كل مرة يعمل الاحتمال ، ينخفض ​​، ويزداد الآخر. ونتيجة لذلك ، لا يوجد احتمال كبير للحصول على ميزة أكبر من الآخر ، ويقومون بالتوازن الذاتي ، ويشكلون توزيعًا طبيعيًا يتمركز عند 0.5 ومع حي عامل لا يزيد عن + -10٪ ، مما يميز هذه الوظيفة عن عشوائي عشوائي ، حيث الاحتمال في حالتنا وستكون دائمًا مساوية لـ 0.5 ولن تعتمد على الحسابات السابقة.

بالمعنى المجازي ، هو بندول احتمالي بسعة صغيرة. إذا نجح الاحتمال الأول ولم ينجح الثاني ، فسيُرجع 1 ، وإلا يتم إرجاع -1 ، وإذا كان كلاهما يعمل أو لم يعمل ، 0. وبالتالي ، فإن وظيفة make_solution للاحتمالين التاليين تُرجع أحد 3 إجراءات محتملة ، مما يعطي توازنًا حل متشعب مع 3 خيارات متابعة ممكنة. في المستقبل ، من المرجح أن تكون هذه الوظيفة عالمية ، وستكون قادرة على تحمل عدد غير محدود من الاحتمالات ، لأن يمكن أن يكون الاختلاف في الشوكات أكثر من 3 ، ولكن في حالة مولد if-elif-else ، فإن ثلاثة خيارات للاستمرار كافية تمامًا.

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

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

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

عند كتابة مولد الفرع ، كان من الضروري ليس فقط إنشاء رمز عملي ينتج أجيال خالية من الأخطاء ، ولكن أيضًا مثل هذا الرمز الذي يمكن أنإنشاء أكبر عدد ممكن من التراكيب الممكنة لـ if-elif-else ، ولكن لا يوجد خياران أو ثلاثة من هذه الخيارات المحتملة. فكر على سبيل المثال في المخططات المحتملة التالية.

صورة

بواسطة الرمز [..] في المخططات أعني مجموعة من التعبيرات لحالة أو كتلة من الإجراءات العشوائية. المخطط الأكثر بدائية هو 1 ، حيث تذهب الحالة ببساطة ، وبعدها كتلة العمل. 2 أ و 2 ب إذا كانت الاختلافات مع أليف واحد أو آخر. في الخيار 2 ج ، إذا كان قد تم دمجه بالفعل مع عدة جنيهات دون أي شيء آخر. وأخيرًا ، في الخيار 2 د ، يتم تقديم المخطط الأكثر عمومية ، حيث يحتوي على عدة أليف و 1 آخر.

كل شيء سيكون بسيطًا لولا الحاجة إلى بناء فروع غير محدودة. بعد كل حالة ، إذا كان elif أو آخر ، يمكن استدعاء العودية ، والتي بدورها يمكن أن تتكرر أكثر وتنتج كتل elif-else جديدة إلى "اليمين". دعونا نلقي نظرة على مخطط الخيارات الممكنة.

صورة

يظهر التجسيد 2e و 2f حالات خاصة بسيطة لمثل هذا التفرع العودي عندما يتم استدعاء العودية إما بعد elif منفرد أو بعد منفرد آخر. يصف الخيار 2g الحالة الأكثر تعقيدًا وعامة لمثل هذا التكرار ، عندما يمكن أن يكون هناك كتلة عمل + تكرار (أو التكرار الفوري) بعد كل حالة من العفاريت ، ويمكن أن يحدث نفس الشيء بعد ذلك.

لا تزال هناك اختلافات عند حدوث العودية مباشرة بعد إذا أو بعد إذا وكتلة إجراء.

صورة

ويظهر ذلك في الخيارين 3 أ و 3 ب. يظهر الخيار 3 ج مثل هذا المخطط في الشكل العام.

هذا لا يعني أن المخططات أعلاه تغطي جميع الخيارات الممكنة لإنشاء الفروع ، ولكن حتى في هذا النموذج ، يؤدي الرمز النهائي بسهولة إلى ظهور فروع من 150 سطرًا ، والانتقال "إلى اليمين" لـ 10-15 خطوة. على أي حال ، ليس من الصعب تعقيد المخطط إذا لزم الأمر.

يمكنك أن تبحث في مثال واحد هذا الجيل للتأكد من أن فروع يمكن أن تكون متنوعة جدا.

صورة

لا تحتاج إلى الانتباه إلى تكوين التعبيرات الشرطية وكتل الحركة - من أجل البساطة البصرية ، يتم إنشاؤها من مجموعات من متغيرين فقط ، 3 تعبيرات وعدد صغير من العلامات الحسابية والعلامات المنطقية. إن مناقشة "اللحم" الحقيقي لإعادة التركيب تتجاوز نطاق هذه المقالة (سيتم مناقشة هذا في مناقشة 3 وحدات).

قبل الشروع في الفحص المباشر لرمز المولد ، من الضروري أن تتذكر أنه يجب تحويل الكتل المولدة أفقياً إلى اليمين ، إذا كانت elif ، وإلا ، إذا كانت كتل العودية أو الحركة ، وكذلك "العودة" إلى اليسار بعد اكتمال الفرع. علاوة على ذلك ، نظرًا لأن Python صعب الإرضاء بشأن المسافات البادئة الأفقية ، فمن المستحسن أن تجعل الخطوة هي نفسها (في حالتنا ، فإن الخطوة هي 3).

يوضح الرسم التخطيطي التالي كيف يتم نقل النزوح.

صورة

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

الآن دعنا ننتقل إلى الرمز. يتكون الرمز الذي يبلغ حجمه الإجمالي حوالي 200 سطر من 8 وظائف ، واحدة درسناها أعلاه. نظرًا للتكرار وعدد كبير من المعلمات التي تم تمريرها إلى الدوال ، يمكن قراءتها بشكل سيء في الأماكن. بادئ ذي بدء ، سأستشهد بـ "اللحم" المستخدم في توليد التعبيرات الشرطية وكتل الحركة.

var_list = ['a','b']
exp_list = ['a+b','b-a', 'b//a']
sign = ['+','-','/','*','//']
sign2 = ['>','<','==','>=','<=','!=']
a = 3
b = 2

كما ترى ، يتم استخدام متغيرين: a و b ( var_list ) ، والتي تمت تهيئتها ، و 3 تعبيرات حسابية ( exp_list ) ، وكذلك ورقتين مع علامات ( علامة ، علامة 2 ). كما ذكرنا سابقًا ، لا يهم تكوين التعبيرات الناتجة الآن ولا يتم تناوله في هذه المقالة - فهي ضرورية بشكل أساسي لتوضيح الرمز. يجب ملاحظة خصوصية أخرى: في إنشاء كتلة elif-else ، تحتاج إلى تتبع مظهر الآخر وإيقاف الجيل ، وإلا قد يظهر قبل elif ، والذي سيؤدي بطبيعة الحال إلى حدوث خطأ. يتم استخدام علامة fin_else_flag لهذا الغرض .

نبدأ النظر في وظيفة الجيل الرئيسي.

def if_gen(exp_list, var_list, if_str, offset_koeff, fin_else_flag, prob_list):             
    choice_list = [exp_list, var_list]
    base_offset = ' '
    #   
    prob_list[0],prob_list[1],sol = make_solution(prob_list[0],prob_list[1])       
    # if +   (1   )        
    if sol == 0: 
        #     +3                                                                   
        action_str = action_str_gen(choice_list, offset_koeff+3, prob_list)                 
        return(base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' + action_str, offset_koeff, fin_else_flag, prob_list) 
    # if + elif/else (2   )           
    elif sol == -1:                                                                         
        if_str= base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' + action_str_gen(choice_list, offset_koeff+3, prob_list) # if [..]:
        #  elif/else
        prob_list[2],prob_list[3],sol2=make_solution(prob_list[2],prob_list[3])             
        if sol2!=0:
            ee_string='elif'
        else:
             ee_string='else'
        #   elif/else
        if_str, offset_koeff, fin_else_flag, prob_list = elif_else_block(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)
        return(if_str, offset_koeff, fin_else_flag, prob_list)
    # if + if() (3   )
    else:                                                                                   
            if_str= base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' # if [..]:
            #  if/if+ 
            prob_list[4],prob_list[5],sol = make_solution(prob_list[4],prob_list[5])        
            if sol==0:
                #     +3
                if_str+=action_str_gen(choice_list, offset_koeff+3, prob_list)      
            #          
            wall_offset = offset_koeff                                                      
            if_rek, offset_koeff, fin_else_flag, prob_list = if_gen(exp_list, var_list, if_str, offset_koeff+3, fin_else_flag, prob_list) #  if+if
            #    
            if_str+=if_rek   
            #   elif-else/                                                                
            prob_list[4],prob_list[5],sol2=make_solution(prob_list[4],prob_list[5])         
            if sol2!=0:
                prob_list[2],prob_list[3],sol3=make_solution(prob_list[2],prob_list[3])
                if sol3!=0:
                    ee_string='elif'
                else:
                    ee_string='else'
                if_str, offset_koeff, fin_else_flag, prob_list = elif_else_block(ee_string, wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)  
            else:
                #     +3
                if_str+=action_str_gen(choice_list, offset_koeff+3, prob_list)              
            return(if_str, offset_koeff,fin_else_flag, prob_list)

بالإضافة إلى القوائم التي تحتوي على "لحم" للتوليد (exp_list ، var_list) ، تقبل الوظيفة أيضًا if_str - هذا هو الخط الذي يتم من خلاله تجميع الشفرة التي تم إنشاؤها. يتم قبولها هنا لأنه يمكن استدعاء وظيفة if_gen نفسها بشكل متكرر ، ومن المستحسن عدم فقدان قطعة التعليمات البرمجية التي تم إنشاؤها سابقًا.

إن معلمة offset_koeff هي معامل الإزاحة ، وهو عامل لسطر بمسافة واحدة ( base_offset ) ، وبالتالي فهو مسؤول عن الإزاحة الأفقية لكتل ​​الشفرة. تحدثنا

عن fin_else_flag أعلاه ، هنا يتم تمريرها ببساطة إلى وظيفة مسؤولة عن إنشاء if + elif / else (انظر أدناه).

حسنًا ، هناك معلمة أخرى -prob_list ، وهي ورقة تحتوي على 10 احتمالات (5 أزواج من الاحتمالات)
prob_list = [0.5 for y in range(0,10)] 
ويتم استخدامه بواسطة وظيفة make_solution كما ناقشنا أعلاه: يتم تمرير زوج واحد أو آخر من الاحتمالات منه يتوافق مع نوع الشوكة (على سبيل المثال ، يستخدم الشوكة الهيكلية الرئيسية الاحتمالين الأولين في الورقة: prob_list [0] و prob_list [1] ). يمكن رؤية نتائج التغييرات الاحتمالية في هذه الورقة ، كمثال ، في الشكل التالي.

صورة

تتغير الاحتمالات في هذه القائمة من جيل إلى جيل ، إذا تم تنفيذ الجزء المقابل من التعليمات البرمجية خلال الجيل التالي.

في الوظيفة نفسها ، تتم تهيئة قائمة الاختيار_الأساسية المتداخلة في البداية - وهي ضرورية لتوليد عبارات عشوائية مريحة من "اللحم" ، وإزاحة القاعدة base_offset = '' في مسافة واحدة.

بعد ذلك تأتي الشوكة الرئيسية ، والتي ، من خلال وظيفة make_solution ، تحصل على الحل في المتغير sol. يأخذ Sol واحدة من ثلاث قيم (0 ، -1.1) ويحدد ، بناءً على ذلك ، وفقًا للمخطط الذي سيتم بناء الهيكل.

يطبق الخيار الأول الخيار الأبسط إذا + [..]. يتم تكوين الإجابة كسلسلة مع الإزاحة الحالية (لا تساوي بالضرورة 0!) ، وسلسلة "if" ، وهي حالة عشوائية يتم إنشاؤها بواسطة دالة if_sub (التي سيتم مناقشتها لاحقًا) ، وإرجاع حرف النقل ، وإنشاء كتلة إجراء باستخدام وظيفة action_str (انظر أدناه) . ونتيجة لذلك ، نحصل على شيء مثل:

if ((a+b)==(b)):
   b=b
   a=b-a
   a=a

الخيار الثاني مسؤول عن توليد هذا النوع: if [..] + elif / else-block (الخيار 2 في المخططات). أولاً ، يتم تشكيل سطر if + [..] هناك ، ثم يحدث تفرع elif / else ، والذي يقرر ما إذا كان سيتم إنشاء كتلة elif-else ، فقط إذا كان elif أو if-else (e lif_else_block - انظر أدناه). قد تختلف النتائج. على سبيل المثال:

if ((a+b)==(a)):
   b=a+b
elif ((b//a)==(a)):
   None
elif ((a+b)<=(a)):
   a=b//a
else:
   if ((b)<=(a)):
      a=b-a
      b=a

if ((a)==(b-a)):
   b=b-a
   b=b
   a=b
   a=b-a
elif ((b)>(b-a))and((a)<(b-a)):
   if ((b//a)<(a)):
      b=b-a
   elif ((a+b)<(b-a))and((b)<(a+b))or((a+b)==(a+b)):
      b=b
      a=b-a
   elif ((a)>(b-a)):
      None

if ((b)<=(b-a))or((a+b)>=(b)):
   a=a
   b=b
elif ((b)<=(b)):
   if ((a)>=(b)):
      a=a+b
      a=b
elif ((b)>=(a)):
   a=b-a
   a=a
   if ((a)>=(b))and((b//a)==(a))and((b//a)!=(b)):
      b=b-a
else:
   a=b//a
   if ((b//a)<(b-a)):
      a=b
      a=b-a
   else:
      if ((a)==(b)):
         a=a
         a=b//a
         b=b
         b=a+b
         b=a
      else:
         None

الخيار الثالث يطبق العودية منذ البداية (الخيار 3 في المخططات) ، أي يؤدي إلى فرع من النموذج:

if ((a)==(a)):
   if ((a+b)<(b)):

أو
if ((b-a)<=(a)):
   a=a
   if ((b-a)==(b)):
      a=a
      a=a

أولاً ، يتم تشكيل الخط if (بالمثل) ، ثم تظهر شوكة ، والتي تقرر ما إذا كان سيتم إدراج كتلة الإجراء أم لا ، وبعد ذلك يتم حفظ الإزاحة ويتم استدعاء العودية. يجب حفظ الإزاحة بحيث أنه بعد اكتمال العودية وإرجاع جزء من الرمز ، من الممكن إضافة كتلة elif-other أخرى في نفس الإزاحة مثل السطر الأصلي مع if. هنا يمكنك أن ترى كيف تقف elif وغيرها في الفرع في نفس الإزاحة مع "مواطن" إذا.

if ((b-a)==(b)):

   if ((a)>(a+b)):
      if ((b)==(b-a)):
         b=b
         a=a
      elif ((b)>(b)):
         None
      else:
         None
         b=a
         b=b

يأتي بعد ذلك شوكة في كتلة elif-else-block / action ، والتي تقرر ما إذا كان سيتم إضافة كتلة عمل أو كتلة elif-else بعد التكرار. إذا قررت إضافة كتلة elif-else ، فسيتم تحديد هناك ، على غرار الحالة الموضحة أعلاه ، في المخطط 2 ، elif أو آخر.

هنا من الضروري الانتباه إلى حقيقة أن العودية يتم استدعاؤها بإزاحة + 3 لتحويل الشفرة التي تم إنشاؤها إلى اليمين بخطوة ، ويتم استدعاء كتلة elif-else بإزاحة wall_offset بحيث لا تذهب هذه الكتلة إلى اليمين بعد العودية ، ولكن تبقى مع "الأصلي" إزاحة الأصل إذا.

يمكن أن تكون النتائج مختلفة تمامًا: من البسيط إلى المعقد ، ولكن ظهور العودية ينتج على الفور أكثر الفروع المزخرفة.

if ((b-a)>(a+b))and((b)<(a+b)):
   if ((b-a)<=(a+b)):
      b=b//a
   elif ((b)!=(a)):
      a=b-a
else:
   if ((a+b)!=(b-a)):
      a=a

if ((b)<(b-a)):
   if ((a+b)==(b-a))and((b-a)<(a+b))and((b-a)==(a))and((a)>(b//a))or((a+b)>(b//a)):
      if ((b)>=(b-a)):
         a=b
         b=b
         if ((b)>(b)):
            a=a+b
            b=a+b
            a=a
            b=a+b
            b=b//a
            b=a
      else:
         b=a+b
         a=b
         a=b
   elif ((a)<(b-a)):
      a=b//a
      a=b-a

if ((a)>=(b-a))or((a)>=(a))or((b)<=(b)):
   a=a
   a=a
elif ((a)==(a))and((b)>(b-a)):
   a=b//a
   if ((a)<(b)):
      if ((a+b)==(b-a)):
         a=a
         if ((a)!=(b//a)):
            if ((b//a)!=(a))and((b-a)>=(b)):
               a=b
            else:
               None
               a=b//a
      else:
         b=b
         b=a+b
         if ((b-a)<=(b//a)):
            a=b
            a=b
            a=a+b
else:
   a=a+b
   if ((b-a)>=(a)):
      a=b
      if ((b-a)==(a))or((b)!=(b//a)):
         a=b-a
         a=a
         a=a
         a=b//a
         a=a+b
         b=a

الآن دعونا نلقي نظرة على دالة elif_else_block ، وهي المسؤولة عن تشكيل كتلة elif-else ويتم استدعاؤها من وظيفة if_gen الرئيسية .

def elif_else_block(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list,  fin_else_flag, prob_list):
    if ee_string=='elif':
        sol3 = 9
        #  
        wall_offset = offset_koeff
        #  elif  
        while sol3!=0 and fin_else_flag!=1:
            temp_str, offset_koeff, fin_else_flag, prob_list=elif_else('elif', wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)
            if_str+=temp_str
            prob_list[6],prob_list[7],sol3 = make_solution(prob_list[6],prob_list[7])
        #  -   else   elif?
        prob_list[2],prob_list[3],sol = make_solution(prob_list[2],prob_list[3])
        if sol!=0:
            #  else,   
            fin_else_flag=1
            temp_str,offset_koeff, fin_else_flag, prob_list=elif_else('else', wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)
            if_str+=temp_str
        return(if_str,offset_koeff, fin_else_flag, prob_list)
    #  else
    else: 
          temp_str,offset_koeff, fin_else_flag, prob_list=elif_else('else', offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)
          if_str+=temp_str
          return(if_str, offset_koeff, fin_else_flag, prob_list)

تحدد هذه الوظيفة ما إذا كان سيتم إضافة كتلة elif أو elif / else إلى الشفرة. لم تقرر ما إذا كانت ستضع ببساطة شيء آخر ، ولكنها تعتمد على قيمة الإدخال e_string ، التي تتلقاها من الوظيفة الرئيسية if_gen . أولاً ، يتم إنشاء كتلة elif في حلقة while ، حيث يتم التحقق من شرطين: الاحتمالية - يعتمد عدد elif في الكتلة وعلامة fin_else_flag عليه ، وإذا تم تشغيله فجأة ، فهذا يعني أنه تم توصيل آخر قبل ذلك ، وبالتالي تحتاج إلى الخروج من الحلقة .

يتم أيضًا تحديد قرار ما إذا كان سيتم إرفاق آخر إلى كتلة elif بواسطة شوكة باستخدام نفس وظيفة make_solution ، وإذا تم إرفاقه ، يتم تشغيل علامة fin_else_flag على الفورالذي يوقف توليد الكتلة.

يتم تنفيذ الربط المباشر ل elif وغيرها بواسطة وظيفة elif_else (انظر أدناه). هنا من الضروري الانتباه إلى أنه عند إنشاء كتلة elif (وأيضًا عند إرفاقها بآخر ) ، يتم استخدام الإزاحة wall_offset لبناء الكتلة بشكل سلس.

الآن فكر في دالة elif_else .

<b>def elif_else(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list):
    ee_str = ''
    #   else:  elif [..]:
    if ee_string=='else':
        ee_str += ' '*offset_koeff+ee_string + ':\n'
    elif ee_string=='elif':
        ee_str += ' '*offset_koeff+ee_string+' '+if_sub(exp_list, var_list, sign, prob_list) + ':\n'
    #   -None /  +
    prob_list[2],prob_list[3],sol = make_solution(prob_list[2],prob_list[3])
    if sol!=0:
        prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7])
        if sol2!=0:
            #  
            ee_str+=action_str_gen(choice_list,offset_koeff+3, prob_list)
        else:
            # None
            ee_str+=' '*(offset_koeff+3)+'None\n'
        return(ee_str, offset_koeff, fin_else_flag, prob_list)
    else:
        #   
        prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7])
        if sol2==0:
            #  
            ee_str+=action_str_gen(choice_list,offset_koeff+3, prob_list)
        #  if_gen
        if_str, offset_koeff,  fin_else_flag, prob_list = if_gen(exp_list, var_list, if_str, offset_koeff+3, fin_else_flag, prob_list)                 
        ee_str+=if_str
        return(ee_str, offset_koeff, fin_else_flag, prob_list)

الوظيفة مسؤولة عن تكوين خط elif أو خط آخر نفسه ، وكذلك عن الجيل اللاحق من كتل العمل أو العودية بعد هذه الخطوط. كما أنه يأخذ متغير ee_string ، والذي يحتوي إما على elif أو آخر ، ويشكل السلسلة المقابلة. ثم هناك شوكة ، حيث يتم تحديد ما سيذهب بعد ذلك: (كتلة العمل أو لا شيء) ، أو (كتلة العمل أو كتلة العمل + العودية). داخل هذا الشوكة ، يوجد تقسيم ، على التوالي ، إلى شوكة فرعية ، وفي كل حالة يتم استدعاء وظيفة make_solution مع المعلمات المناسبة لاتخاذ قرار.

وتجدر الإشارة إلى أنه عندما يحدث في التعليمات البرمجيةif sol!=0، هذا يعني أننا نعطي ميزة عن قصد لجزء من الشفرة على جزء آخر ، لأنه إذا كان sol! = 0 ، فإنه يساوي إما -1 ، أو 1 ، وبالتالي سيتم تنفيذ جزء آخر من التعليمات البرمجية بشكل أقل (فقط عندما sol == 0). يتم استخدام هذا ، على وجه الخصوص ، في دالة elif_else_block ، حيث يكون أكثر ربحية بالنسبة لنا أن نترك المزيد من الأليفات في الكتلة ، بدلاً من إعطاء احتمالية متساوية لـ elif وغيرها. أو ، على سبيل المثال ، في دالة elif_else ، نعطي ميزة للخيار عندما يتم تشكيل كتلة عمل أو لا شيء بدلاً من ما يحدث في العودية - وإلا يمكن أن تنمو الفروع إلى أحجام غير لائقة تمامًا.

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

الوظيفة المسئولة عن توليد كتلة الإجراء action_str .

def action_str_gen(choice_list, offset_koeff, prob_list):
    sol = 9
    curr_offset = ' '*offset_koeff
    act_str = ''
    while sol!=0:
        act_str+= curr_offset+rand(rand(choice_list[1]))+'='+rand(rand(choice_list))+'\n'
        prob_list[6],prob_list[7],sol = make_solution(prob_list[6],prob_list[7])
    return(act_str)

كل شيء بسيط للغاية هنا: من القائمة المتداخلة choise_list ، والتي ، كما نتذكر ، تتكون من v ar_list (قائمة المتغيرات) و exp_list (قائمة التعبيرات) ، تتكون هذه الوظيفة من سطر واحد أو أكثر من هذا النموذج: a = a + b أو b = b . أولئك. إما أن يتم تخصيص تعبير للمتغير ، أو متغير آخر (بما في ذلك نفسه). تحدد وظيفة rand بشكل عشوائي عنصرًا من القائمة وهي مطلوبة هنا فقط حتى لا يتم إنتاج سلاسل وحشية.

def rand(t_list):
    return(t_list[random.randint(0,len(t_list)-1)])

تبدو دالة إنشاء التعبير if_sub للظروف أكبر.

def if_sub(exp_list, var_list, sign, prob_list):
    sub_str = ''
    sol = 9
    choice_list = [exp_list, var_list]
    flag = 0
    while sol!=0:
        prob_list[6],prob_list[7],sol = make_solution(prob_list[6],prob_list[7])
        sub_str+='(('+rand(rand(choice_list))+')'+rand(sign2)+'('+rand(rand(choice_list))+'))'
        if flag == 1 and sol==1:
            sub_str+=')'
            flag=0
        or_and_exp = or_and(prob_list)
        if len(or_and_exp):
            sub_str+=or_and_exp
        else:
            break
        prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7])
        if sol2 == 1 and (sub_str[-1]=='D' or sub_str[-1]=='R') and flag == 0:
            sub_str+='('
            flag = 1
    
    if sub_str[-1] == '(':
        if sub_str[-2]=='d':
           sub_str=sub_str[0:-4]
        elif sub_str[-2]=='r':
             sub_str=sub_str[0:-3]
        else:
            sub_str=sub_str[0:-1]
    elif sub_str[-1]=='d':
         sub_str=sub_str[0:-3]
    elif sub_str[-1]=='r':
         sub_str=sub_str[0:-2]
    else:
         None
    if flag == 1:
        sub_str+=')'
        return(sub_str)
    else:
        return(sub_str)

يولد التعبيرات حسب النوع: ((أ)> = (با)) أو ((أ)> = (أ)) أو ((ب) <= (ب)) . في نفس الوقت ، يمكن أن يكون لكل من الجانبين الأيسر والأيمن خيارات مختلفة والوقوف كمتغيرات منفصلة ، وكذلك التعبيرات أو مجموعاتها. العوامل المنطقية أو و و تستخدم أيضا هنا ، التي يتم اختيارها للراحة باستخدام or_and_exp وظيفة .

def or_and(prob_list):
    prob_list[8],prob_list[9],sol = make_solution(prob_list[8],prob_list[9])
    if sol==-1:
        return('and')
    elif sol==1:
        return('or')
    else:
        return('')

بقية الدالة if_sub تقطع ذيول إضافية من التعبيرات وتضيف ، عند الضرورة ، أقواس إغلاق ، للنظر في هذه الرقصات مع الدف هنا ، على ما أعتقد ، غير مجدية.

حسنا هذا كل شيء. يمكنك بدء المولد ، على سبيل المثال ، مثل:

var_list = ['a','b']
exp_list = ['a+b','b-a', 'b//a']
sign = ['+','-','/','*','//']
sign2 = ['>','<','==','>=','<=','!=']
a = 3
b = 2       
prob_list = [0.5 for y in range(0,10)]      
while True:
     if_str = ''
     if_str, offset_koeff, fin_else_flag, prob_list = if_gen(exp_list, var_list, if_str, 0,0, prob_list)
     try:
         exec(compile(if_str,'gen','exec'))
         print(if_str)
         input()
         
     except ZeroDivisionError:
         None
     except:
         print('error')
         print(if_str)
         input()

أولاً ، الإدخال ، بما في ذلك prob_list مع الاحتمالات ، ثم في حلقة لا نهائية ، استدعاء الوظيفة الرئيسية if_gen وبدء السلسلة المولدة للتنفيذ. يجدر معالجة ZeroDivisionError بشكل منفصل ، لأنه القسمة على صفر مع هذا البناء العشوائي للتعبيرات شائع جدًا. بعد الإطلاق ، ما عليك سوى الضغط على Enter لتظهر للجيل التالي. غالبًا ما تكون بسيطة جدًا ، ولكنها غالبًا ما تكون متفرعة وحتى متفرعة جدًا. حسنًا ، سيكون من السهل أيضًا إدراج عشوائي في البداية ؛) بالنسبة لأولئك الذين لا يريدون رؤية جمع كل شيء يدويًا ، يمكنك تنزيل الملف من Github (file if_gen.py).

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

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

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

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

All Articles