Zufällige Verzweigungen in Python generieren

Bild

Unter Hinweis auf Dawkins kann die Hauptidee wie folgt ausgedrückt werden: Wenn Sie den Tornado lange Zeit über dem Müll halten, kann sich eine Boeing 747 zusammenbauen. Das Entstehen einer Struktur aus dem Chaos durch einen Durik: Alles in einer Reihe sortieren und neu kombinieren, aus all den bedeutungslosen und ungeordneten Prozessen kann man ziemlich bedeutungsvolle und geordnete sehen. Wenn solche Prozesse irgendwie fixiert und wiederholt werden, dann sieht das System, das gestern eine Brownsche Bewegung war, heute so aus, als ob sein Verhalten von einer unsichtbaren Hand aufgebaut worden wäre und dass es einige Aktionen ausführt, die aus unserer Sicht sinnvoll sind. Gleichzeitig gibt es überhaupt keine Hand. Sie stellte sich auf.

Um dies noch einmal sicherzustellen, bemühe ich mich, eine Art digitales Leben zu schreiben, das aus Chaos und ohne unnötige Anweisungen einer Person in der Lage sein wird, zufällig Logik für sich selbst zu generieren und darauf in seinem natürlichen Lebensraum - dem Betriebssystem - zu existieren. Ja, hier besteht wahrscheinlich ein Unterschied zu vielen Programmen aus der Richtung „Künstliches Leben“, die in Ställen „leben“, „Raubtiere“ und „Pflanzenfresser“ produzieren und auf künstlichen Feldern neben „Nahrung“ und untereinander existieren. Keines dieser Programme interagiert mit Systemobjekten (Prozessen, Dateien usw.), was bedeutet, dass der Code nicht wirklich lebt. Darüber hinaus führt dieser Code auf die eine oder andere Weise immer noch eine Aufgabe aus, die eine Person benötigt, und ist daher in ihrem Umfang sehr begrenzt.

Um Code mit einem hohen Maß an Handlungsfreiheit im Betriebssystem zu implementieren, der gleichzeitig nicht nur ein chaotischer Satz von Ausführungsanweisungen wäre, erschien ein Modell, das aus 3 Modulen besteht.

  1. Das Modul zur zufälligen Generierung des ausführbaren Hauptcodes
  2. Modul für zufällige Bildung
  3. Das "Computer Vision" -Modul von Betriebssystemobjekten

In diesem Artikel werden wir über das erste Modul sprechen, bei dem es sich bisher nur um die Erzeugung einer zufälligen Verzweigung handelt, d. H. Konstruktionen wie "if-elif-else". Warum verzweigen? Denn im Großen und Ganzen besteht das Leben eines lebenden Organismus aus konditionierten Reaktionen: Alles, was wir tun, ist eine Reaktion auf wahrgenommene Informationen. Zellen teilen sich, wenn bestimmte Bedingungen eintreten, das Opfer versucht zu fliehen, wenn es ein stärkeres Raubtier sieht, und wenn es schwächer ist, kann es versuchen, ihn anzugreifen, Kakerlaken zerstreuen sich, wenn das Licht an ist, eine Person geht essen, wenn sie hungrig ist usw. usw. - Diese Reihe ist endlos. Es gibt keine unabhängigen, separaten Aktionen, die durch nichts bedingt sind. Folglich wird insbesondere das Verhalten lebender Organismen als Reaktion auf den Zustand beschrieben: WENN [etwas] DANN [etwas]. Wir versuchen, dieses Verhalten zu erzeugen.

Warum zufällig? Um dem Code die maximale Möglichkeit zu geben, unabhängig zu handeln und die Person (Programmierer) so weit wie möglich von diesem Prozess wegzubewegen (idealerweise vollständig auszuschließen). Letzteres ist für den Programmierer am schwierigsten, weil Die Standardprogrammierung, an die jeder gewöhnt ist, ähnelt einem harten Training von Tieren, das genau das ausführen muss, was der Programmierer angibt, genau so, wie er es angibt, wenn er angibt. Hier ist die Situation umgekehrt: Der endgültig generierte Code muss so handeln, dass er für den Ersteller seines Generators ebenso unvorhersehbar ist.

Bevor wir zu den Diagrammen und dem Code des Generators übergehen, müssen wir uns mit der Entscheidungsfunktion befassen, die als Leiter verwendet wird, damit der eine oder andere Teil des Codes ausgeführt werden kann. Ich habe früher über sie geschriebenhier . Ich wurde dann aufgefordert, die Idee des Reinforcement Learning und das Spiel von John Conway mit dem Titel "Life" zu beschreiben. Es kann gut sein, dass ich nichts dagegen habe, das zu verwenden, was bereits entwickelt wurde oder offen. Am Ende ist alles Neue eine Synthese des bereits Bekannten, und ich selbst gab zu, dass ich die Idee der Priorisierung von Flows übernommen habe, die in Windows verwendet wird. Hier ist sie sehr gut geeignet.

Derzeit wurde die erwähnte Funktion leicht transformiert:

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)

Am Eingang werden 2 Wahrscheinlichkeiten benötigt (standardmäßig sind sie zu Beginn beide gleich 0,5), wonach die Operation nacheinander überprüft wird. Die ausgelöste Wahrscheinlichkeit verringert sich um 1% und erhöht gleichzeitig die andere um 1%. Daher nimmt die Wahrscheinlichkeit jedes Mal ab, wenn sie funktioniert, und die andere nimmt zu. Infolgedessen erhält keine Wahrscheinlichkeit einen zu großen Vorteil gegenüber einer anderen, und sie gleichen sich selbst aus und bilden eine Normalverteilung, die bei 0,5 zentriert ist und eine Arbeitsumgebung von nicht mehr als + -10% aufweist, was diese Funktion von dem Standard-Zufall unterscheidet, bei dem die Wahrscheinlichkeit in unserem Fall Sie wäre immer gleich 0,5 und würde nicht von früheren Berechnungen abhängen.

Im übertragenen Sinne handelt es sich um ein Wahrscheinlichkeitspendel mit einer kleinen Amplitude. Wenn die erste Wahrscheinlichkeit funktioniert hat und die zweite nicht funktioniert hat, wird 1 zurückgegeben, andernfalls wird -1 zurückgegeben, und wenn beide funktioniert haben oder nicht funktioniert haben, 0, gibt die Funktion make_solution für 2 eingehende Wahrscheinlichkeiten eine von 3 möglichen Aktionen zurück, was einen Ausgleich ergibt Gabellösung mit 3 möglichen Fortsetzungsoptionen. In Zukunft ist diese Funktion wahrscheinlich universell und kann eine unbestimmte Anzahl von Wahrscheinlichkeiten annehmen, weil Die Variation an den Gabeln kann mehr als 3 betragen, aber im Fall des if-elif-else-Generators sind drei Optionen für die Fortsetzung völlig ausreichend.

Hierbei ist auch zu beachten, dass es im Code sozusagen verschiedene typische Gabeln gibt. Wie weiter unten zu sehen sein wird, gibt es in der Hauptfunktion des Generators eine Gabelung, in der ein Schema zum Erstellen eines Zweigs ausgewählt werden kann, von dem es nur 3 gibt, aber auch andere Fälle im Code vorhanden sind: Einfügen eines Aktionsblocks oder Starten einer Rekursion, Anzahl der zu generierenden Aktionszeilen, wie komplex sie sein sollte Linie mit der Bedingung, setzen oder oder und und, elif oder sonst.

Ich glaube, dass das probabilistische Pendel, über das wir oben gesprochen haben, für jede Art von Aktion festgelegt werden sollte: Dann wird die Gabel nur auf der Grundlage dessen ausgeglichen, was zuvor auf dieser Gabel passiert ist, und nicht in einigen anderen Teilen des Codes. Jene. Bei der Auswahl der allgemeinen Verzweigungsstruktur haben wir unser eigenes Paar von Wahrscheinlichkeiten und innerhalb, wenn ihre Elemente aufgebaut sind, ein anderes.

Natürlich können Sie alle Aktionen mit einem Paar ausgleichen, aber dann ist die Wahrscheinlichkeit an jeder Gabelung sehr schwierig und hängt von allen vorherigen Aktionen an anderen Kreuzungen ab. Die Zufälligkeit eines solchen Entwurfs wird noch höher sein, aber im Moment neige ich persönlich zum ersten Schema, weil ich den Entwurf mag, bei dem andere kleine im Rahmen eines großen schwingenden Pendels schwingen, d. H. kleinere Guthaben entstehen in einem großen Guthaben. Außerdem ist im zweiten Schema die Zufälligkeit mehr als ausreichend.

Beim Schreiben des Zweiggenerators musste nicht nur funktionsfähiger Code erstellt werden, der fehlerfreie Generationen erzeugt, sondern auch Code, der dies könnteGenerieren Sie die maximal möglichen Konstrukte von if-elif-else, aber es gibt nicht zwei oder drei solcher möglichen Optionen. Betrachten Sie beispielsweise die folgenden möglichen Schemata.

Bild

Mit dem Symbol [..] in den Schemata meine ich eine Reihe von Ausdrücken für eine Bedingung oder einen Block zufälliger Aktionen. Das elementarste Schema ist 1, wo die Bedingung einfach geht, und danach der Aktionsblock. 2a und 2b sind Variationen mit einem Elif oder einem anderen. In Option 2c, wenn bereits in Kombination mit mehreren elif ohne sonst kommt. Und schließlich wird in Option 2d das allgemeinste Schema vorgestellt, wobei if mehrere elif und 1 else enthält.

Alles wäre einfach, wenn nicht unbegrenzt viele Filialen gebaut werden müssten. Nach jedem if, elif oder else kann eine Rekursion aufgerufen werden, die wiederum weiter rekursiv sein und neue elif-else-Blöcke rechts erzeugen kann. Schauen wir uns das Schema möglicher Optionen an.

Bild

Die Ausführungsformen 2e und 2f zeigen einfache Sonderfälle einer solchen rekursiven Verzweigung, wenn die Rekursion entweder nach einem einzelnen Elif oder nach einem einzelnen anderen aufgerufen wird. Option 2g beschreibt den komplexesten und allgemeinsten Fall einer solchen Rekursion, wenn nach jedem elif ein Aktionsblock + eine Rekursion (oder eine sofortige Rekursion) auftreten kann und dasselbe nach anderen passieren kann.

Es gibt immer noch Variationen, wenn die Rekursion unmittelbar nach if oder after if und einem Aktionsblock erfolgt.

Bild

Dies ist in den Optionen 3a und 3b zu sehen. Option 3c zeigt ein solches Schema in der allgemeinsten Form.

Dies bedeutet nicht, dass die obigen Schemata alle möglichen Optionen zum Erstellen von Verzweigungen abdecken. Selbst in dieser Form erstellt der endgültige Code problemlos Verzweigungen in 150 Zeilen, wobei 10 bis 15 Schritte „nach rechts“ ausgeführt werden. In jedem Fall ist es nicht schwierig, das System bei Bedarf zu komplizieren.

Sie können sich ein Beispiel einer solchen Generation ansehen , um sicherzustellen, dass die Zweige sehr unterschiedlich sein können.

Bild

Sie müssen nicht auf die Zusammensetzung von bedingten Ausdrücken und Aktionsblöcken achten - der visuellen Einfachheit halber werden sie nur aus Kombinationen von zwei Variablen, 3 Ausdrücken und einer kleinen Anzahl von arithmetischen und logischen Vorzeichen generiert. Eine Diskussion des echten „Fleisches“ für die Rekombination würde den Rahmen dieses Artikels sprengen (dies wird in der Diskussion von 3 Modulen diskutiert).

Bevor Sie mit der direkten Prüfung des Generatorcodes fortfahren, müssen Sie berücksichtigen, dass die generierten Blöcke horizontal nach rechts verschoben werden müssen, wenn es sich um elif handelt, andernfalls wenn es sich um Rekursions- oder Aktionsblöcke handelt, und dass Sie nach Abschluss des Zweigs auch nach links „zurückgehen“ müssen. Da Python in Bezug auf horizontale Einrückungen sehr wählerisch ist, ist es außerdem wünschenswert, den Schritt gleich zu machen (in unserem Fall ist der Schritt 3).

Das folgende Diagramm zeigt, wie Verschiebungen verschoben werden.

Bild

Das Wichtigste dabei ist, dass die Verschiebungen mit der Vertiefung des Astes immer nach rechts verschoben werden. Wenn wir jedoch zum Beispiel einen elif-else-Block haben, in dem sich mehrere elif oder ein einzelnes elif-else-Paar befinden, muss der nach rechts schwebende Wagen „zurückgegeben“ werden, damit der nächste elif (oder sonst) mit beginnt gleiche Offsets wie der vorherige im Block. Dazu müssen Sie den ursprünglichen Offset ( wall_offset) speichern) und nach dem Ende der Verzweigungsgenerierung (z. B. vollständige Verzweigung eines Elifs) wiederherstellen. Dies stellt sicher, dass die elif, else-Elemente im Block gleichmäßig „übereinander“ liegen. Darüber hinaus hat jeder neue Block seine eigene Verschiebung. Der gleiche Trick sorgt für Harmonie im gesamten if-elif-else-Konstrukt (einschließlich Rekursionen).

Kommen wir nun zum Code. Der Code mit einem Gesamtvolumen von ca. 200 Zeilen besteht aus 8 Funktionen, von denen eine oben untersucht wurde. Aufgrund der Rekursivität und einer großen Anzahl von Parametern, die an Funktionen übergeben werden, kann es stellenweise schlecht lesbar sein. Zunächst werde ich genau das „Fleisch“ zitieren, mit dem bedingte Ausdrücke und Aktionsblöcke erzeugt werden.

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

Wie Sie sehen können, werden zwei Variablen verwendet: a und b ( var_list ), die initialisiert werden, 3 arithmetische Ausdrücke ( exp_list ) sowie zwei Blätter mit Vorzeichen ( sign, sign2 ). Wie bereits erwähnt, spielt die Zusammensetzung der resultierenden Ausdrücke jetzt keine Rolle und wird in diesem Artikel nicht berücksichtigt. Sie werden hauptsächlich zur Veranschaulichung des Codes benötigt. Eine weitere Besonderheit sollte beachtet werden: Bei der Generierung des elif-else-Blocks müssen Sie das Erscheinungsbild des else verfolgen und die Generierung stoppen. Andernfalls kann else vor elif erscheinen, was natürlich zu einem Fehler führt. Zu diesem Zweck wird das Flag fin_else_flag verwendet .

Wir beginnen unsere Betrachtung mit der Hauptgenerationsfunktion.

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)

Zusätzlich zu Listen mit "Fleisch" zur Generierung (exp_list, var_list) akzeptiert die Funktion auch if_str - dies ist die Zeile, in der der generierte Code der Reihe nach gesammelt wird. Dies wird hier akzeptiert, da die Funktion if_gen selbst rekursiv aufgerufen werden kann und es ratsam ist, den zuvor generierten Code nicht zu verlieren.

Der Parameter offset_koeff ist der Versatzkoeffizient, der ein Faktor für eine Linie mit einem Leerzeichen ist ( base_offset ) und dementsprechend für die horizontalen Verschiebungen der Codeblöcke verantwortlich ist. Wir haben oben

über fin_else_flag gesprochen , hier wird es einfach an eine Funktion übergeben, die für die Erzeugung von if + elif / else verantwortlich ist (siehe unten).

Nun, es gibt noch einen anderen Parameter -prob_list , ein Blatt mit 10 Wahrscheinlichkeiten (5 Wahrscheinlichkeitspaare)
prob_list = [0.5 for y in range(0,10)] 
und es wird von der Funktion make_solution verwendet, wie wir oben besprochen haben: Das eine oder andere Paar von Wahrscheinlichkeiten daraus, die dem Typ der Gabel entsprechen, wird an sie übergeben (zum Beispiel verwendet die Hauptstrukturgabel die ersten 2 Wahrscheinlichkeiten im Blatt: prob_list [0] und prob_list [1] ). Die Ergebnisse der Wahrscheinlichkeitsänderungen in diesem Blatt sind als Beispiel in der folgenden Abbildung dargestellt.

Bild

Die Wahrscheinlichkeiten in dieser Liste ändern sich von Generation zu Generation, wenn während der nächsten Generation der entsprechende Code ausgeführt wird.

In der Funktion selbst wird die verschachtelte Auswahlliste zu Beginn initialisiert - sie wird für die bequeme zufällige Generierung von Ausdrücken aus "Fleisch" benötigt, und der Basisoffset base_offset = '' in einem Leerzeichen.

Danach kommt die Hauptgabel, die über die Funktion make_solution die Lösung in die Variable sol bringt. Sol nimmt einen von drei Werten (0, -1,1) an und bestimmt daher, nach welchem ​​Schema die Struktur aufgebaut wird.

Die erste Option implementiert die einfachste Option, wenn + [..]. Die Antwort besteht aus einer Zeichenfolge mit dem aktuellen Offset (muss nicht unbedingt gleich 0 sein!), Einer "if" -String , einer zufälligen Bedingung, die von der if_sub- Funktion (die später erläutert wird), dem Wagenrücklauf und der Generierung eines Aktionsblocks mithilfe der action_str- Funktion generiert wird (siehe unten). . Als Ergebnis erhalten wir so etwas wie:

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

Die zweite Option ist für die Generierung dieses Typs verantwortlich: if [..] + elif / else-block (Option 2 in den Schemata). Zuerst wird dort die if + [..] -Linie gebildet, dann tritt die elif / else-Gabel auf, die entscheidet, ob der elif-else-Block generiert wird, nur wenn-elif oder if-else (e lif_else_block- Funktion - siehe unten). Ergebnisse können variieren. Zum Beispiel:

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

Die dritte Option implementiert die Rekursion von Anfang an (Option 3 in den Schemata), d. H. ergibt einen Zweig der Form:

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

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

Zuerst wird die if-Linie gebildet (ähnlich), dann erscheint eine Gabelung, die entscheidet, ob der Aktionsblock weiter eingefügt werden soll oder nicht, wonach der Versatz gespeichert und die Rekursion aufgerufen wird. Der Versatz muss gespeichert werden, damit nach Abschluss der Rekursion und Rückgabe des Codeteils ein weiterer elif-else-Block mit demselben Versatz wie in der ursprünglichen Zeile mit if hinzugefügt werden kann. Hier können Sie sehen, wie elif und sonst in der Branche im gleichen Versatz wie ihre "native" if stehen.

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

Als nächstes kommt eine Verzweigung im elif-else-Block / Aktionsblock, die entscheidet, ob nach der Rekursion ein Aktionsblock oder ein elif-else-Block hinzugefügt werden soll. Wenn Sie sich entscheiden, einen elif-else-Block hinzuzufügen, wird dort, ähnlich wie oben in Schema 2, elif oder else ausgewählt.

Hierbei ist zu beachten, dass die Rekursion mit einem Offset von + 3 aufgerufen wird, um den generierten Code schrittweise nach rechts zu verschieben, und der elif-else-Block mit einem Offset von wall_offset aufgerufen wird, damit dieser Block nach der Rekursion nicht nach rechts geht, sondern beim „nativen“ Block bleibt. der Versatz des Originals wenn.

Die Ergebnisse können sehr unterschiedlich sein: von einfach bis komplex, aber das Auftreten von Rekursion erzeugt sofort die reich verziertesten Zweige.

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

Schauen wir uns nun die Funktion elif_else_block an , die für die Bildung des Blocks elif-else verantwortlich ist und von der Hauptfunktion if_gen aufgerufen wird .

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)

Diese Funktion entscheidet, ob dem Code ein elif- oder ein elif / else-Block hinzugefügt werden soll. Sie entscheidet nicht, ob sie einfach etwas anderes setzen soll, sondern hängt vom Eingabewert e e_string ab , den sie von der Hauptfunktion if_gen erhält . Zuerst wird der elif-Block in der while-Schleife generiert, in der zwei Bedingungen überprüft werden: probabilistisch - Die Anzahl der elif im Block und das fin_else_flag- Flag hängen davon ab . Wenn es plötzlich eingeschaltet wird, bedeutet dies, dass zuvor eine Verbindung hergestellt wurde, und daher müssen Sie die Schleife verlassen .

Die Entscheidung, ob else und else an den elif-Block angehängt werden soll, wird von einer Verzweigung mit derselben Funktion make_solution getroffen. Wenn else angehängt wird, wird das Flag fin_else_flag sofort aktiviertDies stoppt die Blockerzeugung.

Das direkte Zusammenfügen von elif und else erfolgt über die Funktion elif_else (siehe unten). Hierbei ist zu beachten, dass beim Generieren des elif-Blocks (und auch beim Anhängen eines anderen) der Offset wall_offset verwendet wird, um den Block als Ganzes reibungslos zu erstellen.

Betrachten Sie nun die Funktion 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)

Die Funktion ist verantwortlich für die Bildung der elif- oder sonst-Linie selbst sowie für die nachfolgende Erzeugung von Aktions- oder Rekursionsblöcken nach diesen Linien. Es nimmt auch eine ee_string- Variable , die entweder elif oder else enthält, und bildet die entsprechende Zeichenfolge. Dann gibt es eine Gabelung, in der festgelegt wird, was als nächstes passieren soll: (Aktionsblock oder Keine) oder (Aktionsblock oder Aktionsblock + Rekursion). Innerhalb dieser Gabel gibt es jeweils eine Unterteilung in zwei Untergabeln , und in jedem Fall wird die Funktion make_solution mit den entsprechenden Parametern aufgerufen , um eine Entscheidung zu treffen.

Es sollte beachtet werden, dass, wenn es im Code auftrittif sol!=0Dies bedeutet, dass wir einem Teil des Codes absichtlich einen Vorteil gegenüber einem anderen geben, denn wenn sol! = 0 ist, ist er entweder -1 oder 1, und daher wird ein anderer Code weniger häufig ausgeführt (nur wenn sol == 0). Dies wird insbesondere in der Funktion elif_else_block verwendet , in der es für uns rentabler ist, mehr Elifen im Block bilden zu lassen, als elif und anderen die gleiche Wahrscheinlichkeit zu geben. Oder zum Beispiel geben wir in der Funktion elif_else der Option einen Vorteil, wenn ein Aktionsblock oder None gebildet wird, anstatt dem, wofür die Rekursion vorgesehen ist - andernfalls können die Zweige zu sehr unanständigen Größen wachsen.

Wir müssen nur die Funktionen berücksichtigen, die für die zufällige Erzeugung von Ausdrücken in Bedingungen und Aktionsblöcken verantwortlich sind. Wie ich oben sagte, spielen sie zu diesem Zeitpunkt keine entscheidende Rolle und werden hier vorgestellt, um allgemein zu zeigen, wie der endgültig generierte Code aussehen wird. Da sie jedoch im Generator verwendet werden, werden wir sie kurz betrachten.

Die Funktion, die für die Generierung des Aktionsblocks action_str verantwortlich ist .

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)

Hier ist alles ganz einfach: Aus der verschachtelten Liste choise_list, die, wie wir uns erinnern, aus v ar_list (Liste der Variablen) und exp_list (Liste der Ausdrücke) besteht, besteht diese Funktion aus einer oder mehreren Zeilen dieser Form: a = a + b oder b = b . Jene. Der Variablen wird entweder ein Ausdruck zugewiesen oder eine andere Variable (einschließlich sich selbst). Die Rand-Funktion wählt zufällig ein Element aus der Liste aus und wird hier nur benötigt, um keine monströsen Strings zu erzeugen.

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

Die Funktion zur Erzeugung von if_sub- Ausdrücken für Bedingungen sieht größer aus.

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)

Es werden Ausdrücke nach Typ generiert: ((a)> = (ba)) oder ((a)> = (a)) oder ((b) <= (b)) . Gleichzeitig können sowohl die linke als auch die rechte Seite verschiedene Optionen haben und als separate Variablen sowie als Ausdrücke oder deren Gruppen stehen. Hier werden auch die logischen Operatoren oder und und verwendet , die der Einfachheit halber mit der Funktion or_and_exp ausgewählt werden .

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('')

Der Rest der if_sub- Funktion schneidet die zusätzlichen Schwänze aus den Ausdrücken ab und fügt bei Bedarf schließende Klammern hinzu, um diese Tänze mit Tamburinen hier zu betrachten, was meiner Meinung nach nicht zweckmäßig ist.

Nun, das ist alles. Sie können den Generator beispielsweise folgendermaßen starten:

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()

Zuerst die Eingabe, einschließlich einer prob_list mit Wahrscheinlichkeiten , dann in einer Endlosschleife, wobei die Hauptfunktion if_gen aufgerufen und die generierte generierte Zeichenfolge zur Ausführung gestartet wird . Es lohnt sich, ZeroDivisionError separat zu verarbeiten, weil Eine Division durch Null mit einer solchen zufälligen Konstruktion von Ausdrücken ist sehr häufig. Drücken Sie nach dem Start einfach die Eingabetaste, damit die nächste Generation angezeigt wird. Meistens sind sie recht einfach, aber oft verzweigt und sogar sehr verzweigt. Nun, zufälliger Import am Anfang wäre auch schön einzufügen;) Für diejenigen, die nicht alles von Hand sammeln wollen, können Sie die Datei von Github herunterladen (Datei if_gen.py).

Abschließend möchte ich sagen, dass der von mir präsentierte Code an Hunderttausenden von Generationen ohne Fehler getestet wurde, während er die gesamte Palette von If-Elif-else-Schemata demonstrierte, die ich endlich sehen wollte. Einmal habe ich versehentlich in einem Teil des Codes eine zu hohe Rekursionswahrscheinlichkeit angegeben und 52.000 (!) Generierungszeilen erhalten, die gleichzeitig funktionierten (obwohl die Komposition 30 Sekunden lang angehalten wurde). Dies zeigt auch die Zuverlässigkeit des Algorithmus an.

Wahrscheinlich war es möglich, irgendwo prägnanter zu schreiben, irgendwo zu optimieren, die Hauptfunktion auf andere Weise anzuordnen, aber die Hauptsache ist, dass dieser Code funktioniert und ungefähr 250 Generationen pro Sekunde erzeugt, was meiner Meinung nach durchaus akzeptabel ist.

Ich habe diesen Code nie als autark angesehen - er ist nur ein Modul des zukünftigen digitalen Organismus und wurde zu Forschungszwecken geschrieben, sodass er kaum praktische Anwendungen hat. Gleichzeitig bin ich nicht verantwortlich für Konsequenzen für jemanden, der den obigen Code verwendet, und ich fordere jeden auf, Brot mit einem Messer zum Schneiden von Brot zu schneiden und nicht etwas anderes.

Im nächsten Artikel werden wir das zweite Modul betrachten, das für die zufällige Bildung von Erfahrungen verantwortlich sein wird. Dieses Thema verspricht viel interessanter zu sein als der if-Generator, und ich werde die Ergebnisse definitiv veröffentlichen, sobald ich sie habe.

All Articles