Generating Random Branches in Python

image

Recalling Dawkins, the main idea can be expressed as follows: if you keep the tornado over the trash for a long time , then a Boeing 747 can assemble. The emergence of a structure from chaos by a durik: sorting and recombining everything in a row, from all the meaningless and disordered processes, one can see quite meaningful and ordered ones. If such processes are somehow fixed and repeated, then the system, which yesterday was a Brownian motion, today begins to look as if its behavior was set up by an invisible hand, and that it is making some actions that are meaningful from our point of view. At the same time, there is no hand at all. She set herself up.

To make sure of this again, I strive to write some kind of digital life, which, out of chaos and without unnecessary instructions from a person, will be able to randomly generate logic for itself and exist on it in its natural habitat - the operating system. Yes, in this, probably, there is a difference from many programs from the “Artificial Life” direction, which “live” in corrals, produce “predators” and “herbivores,” and co-exist in artificial fields with “food” and each other. None of these programs interact with system objects (processes, files, etc.), which means that the code does not really live. In addition, this code one way or another still performs some kind of task that a person needs and is very limited in scope because of this.

To implement code with a large degree of freedom of action in the operating system, which at the same time would not be just a chaotic set of executing instructions, a model appeared that consists of 3 modules.

  1. The module of random generation of the main executable code
  2. Random Education Module
  3. The "computer vision" module of OS objects

In this article, we will talk about the first module, which so far is only the generation of random branching, i.e. constructions like "if-elif-else". Why branching? Because, by and large, the life of any living organism consists of conditioned reactions: everything we do is a response to perceived information. Cells divide if certain conditions occur, the victim tries to escape if he sees a stronger predator, and if he is weaker, he can try to attack him, cockroaches scatter if the light turns on, a person goes to eat, if he is hungry, etc. etc. - this row is endless. There are no independent, separate actions that are not conditioned by anything. Consequently, the behavior of living organisms in particular is described as a reaction to the condition: IF [something] THEN [something]. We are trying to generate this behavior.

Why randomly? In order to leave the code the maximum opportunity to act independently and move the person (programmer) away from this process as far as possible (ideally completely exclude). The latter is the most difficult for the programmer, because standard programming, to which everyone is accustomed, resembles a hard training of animals, which must perform exactly what the programmer indicates, exactly as he indicates when he indicates. Here the situation is the opposite: the final generated code must act so that it is as unpredictable for the creator of its generator.

Before we move on to the diagrams and code of the generator, it is necessary to dwell on the decision-making function, which is used as a conductor, allowing one or the other part of the code to be executed. I wrote about her earlierhere . I was then prompted that I described the idea of ​​Reinforcement Learning and the game of John Conway, entitled "Life." It may well be that I have nothing against using what has already been developed or openly. In the end, everything new is a synthesis of the already known, and I myself admitted that I adopted the idea of ​​prioritizing flows, which is used in Windows. Here she is very suitable.

Currently, the mentioned function has been slightly transformed:

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)

At the input, it takes 2 probabilities (by default at the start they are both equal to 0.5), after which it checks their operation one by one. The triggered probability decreases itself by 1% and at the same time increases the other by 1%. Therefore, each time the probability works, it decreases, and the other increases. As a result, no probability gets too much advantage over another, and they self-balance, forming a normal distribution centered at 0.5 and with a working neighborhood of no more than + -10%, which distinguishes this function from the standard random, where the probability in our case It would always be equal to 0.5 and would not depend on previous calculations.

Figuratively speaking, it is a probability pendulum with a small amplitude. If the first probability worked and the second didn’t work, it returns 1, otherwise -1 is returned, and if both worked or didn’t work, 0. Thus, the make_solution function for 2 incoming probabilities returns one of 3 possible actions, giving a balanced forked solution with 3 possible continuation options. In the future, this function is likely to be universal, and will be able to take an indefinite number of probabilities, because the variation at the forks can be more than 3, but in the case of the if-elif-else generator, three options for continuation are quite enough.

It should also be noted here that in the code there are different, so to say, typical forks. For example, as will be seen below, in the main function of the generator there is a fork in which there is a choice of a scheme for constructing a branch, of which there are only 3, but other cases are also present in the code: insert an action block or start a recursion, how many action lines to generate, how complex it should be line with the condition, put or or and, elif or else.

I believe that the probabilistic pendulum, which we talked about above, should be set for each type of action: then the fork is balanced only on the basis of what happened earlier on this fork, and not in some other parts of the code. Those. when choosing the general branching structure, we have our own pair of probabilities, and inside, when its elements are built, another.

Of course, you can balance all actions with one pair, but then the probability at each fork will be very difficult and will depend on all previous actions at other junctions. The randomness of such a design will be even higher, but for now I personally am inclined to the first scheme, because I like the design where other small ones swing within the framework of one large swinging pendulum, i.e. smaller balances are born in one big balance. Plus, in the second scheme, randomness is also more than sufficient.

When writing the branch generator, it was necessary to make not only workable code that produces error-free generations, but also such code that couldgenerate the maximum possible constructs of if-elif-else, but there are not 2 or 3 of such possible options. Consider, for example, the following possible schemes.

image

By the icon [..] in the schemes I mean a set of expressions for a condition or a block of random actions. The most elementary scheme is 1, where the condition simply goes, and after it the action block. 2a and 2b are if variations with one elif or one else. In option 2c, if already comes in combination with several elif without else. And finally, in option 2d, the most general scheme is presented, where if contains several elif and 1 else.

Everything would be simple if it were not for the need to build unlimited branches. After each if, elif or else, recursion can be called, which in turn can also recurse further and produce new elif-else blocks to the “right”. Let's look at the scheme of possible options.

image

Embodiments 2e and 2f show simple special cases of such recursive branching when recursion is called either after a single elif or after a single else. Option 2g describes the most complex and general case of such recursion, when after each elif there can be an action block + recursion (or immediately recursion), and the same thing can happen after else.

There are still variations when recursion occurs immediately after if or after if and an action block.

image

This is seen in options 3a and 3b. Option 3c shows such a scheme in the most general form.

This is not to say that the above schemes cover all possible options for constructing branches, however, even in this form, the final code easily creates branches in 150 lines, going “to the right” for 10-15 steps. In any case, complicating the scheme if necessary is not difficult.

You can look at an example of one such generation to make sure that the branches can be very diverse.

image

You do not need to pay attention to the composition of conditional expressions and action blocks - for visual simplicity, they are generated from only combinations of two variables, 3 expressions and a small number of arithmetic and logical signs. A discussion of the real “meat” for recombination is beyond the scope of this article (this will be discussed in the discussion of 3 modules).

Before proceeding to a direct examination of the generator code, it is necessary to remember that the generated blocks must be shifted horizontally to the right, if it is elif, else, if recursion or action blocks, and also “go back” to the left after the branch completes. Moreover, given that Python is very picky about horizontal indents, it is desirable to make the step the same (in our case, the step is 3).

The following diagram illustrates how displacements are shifted.

image

The most important thing here is that the displacements with the deepening of the branches always shift to the right. However, if we have, for example, an elif-else block in which there are several elif or a single elif-else pair, then there is a need to “return” the carriage that floated to the right, so that the next elif (or else) begins with same offsets as the previous one in the block. To do this, you must save the original offset ( wall_offset) and after the end of branch generation (for example, full branching of one elif), restore it. This ensures that the elif, else elements in the block are “on top of each other” evenly. Moreover, each new block has its own displacement. The same trick provides harmony in the overall if-elif-else construct (including recursions).

Now let's move on to the code. The code with a total volume of about 200 lines consists of 8 functions, one of which we examined above. Due to recursiveness and a large number of parameters passed to functions, it can be poorly readable in places. To begin with, I will cite the very “meat” that is used to generate conditional expressions and action blocks.

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

As you can see, two variables are used: a and b ( var_list ), which are initialized, 3 arithmetic expressions ( exp_list ), and also two sheets with signs ( sign, sign2 ). As mentioned earlier, the composition of the resulting expressions does not matter now and is not considered in this article - they are needed mainly to illustrate the code. One more peculiarity should be noted: in the generation of the elif-else block, you need to track the appearance of the else and stop the generation, otherwise else may appear before elif, which naturally will cause an error. The fin_else_flag flag is used for this purpose .

We begin our consideration with the main generation function.

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)

In addition to lists with “meat” for generation (exp_list, var_list), the function also accepts if_str - this is the line where the generated code is collected in turn. It is accepted here because the if_gen function itself can be called recursively, and it would be advisable not to lose the piece of code generated earlier.

The offset_koeff parameter is the offset coefficient, which is a factor for a line with one space ( base_offset ) and, accordingly, it is responsible for the horizontal displacements of the code blocks. We talked

about fin_else_flag above, here it is simply passed to a function that is responsible for generating if + elif / else (see below).

Well, there is another parameter -prob_list , which is a sheet with 10 probabilities (5 pairs of probabilities)
prob_list = [0.5 for y in range(0,10)] 
and is used by the make_solution function as we discussed above: this or that pair of probabilities from it corresponding to the type of fork is passed to it (for example, the main structural fork uses the first 2 probabilities in the sheet: prob_list [0] and prob_list [1] ). The results of the probability changes in this sheet, as an example, can be seen in the following figure.

image

The probabilities in this list change from generation to generation, if during the next generation the corresponding piece of code gets executed.

In the function itself, the nested choice_list is initialized at the beginning - it is needed for convenient random generation of expressions from “meat”, and the base offset base_offset = '' in one space.

After that comes the main fork, which, through the make_solution function, gets the solution into the sol variable. Sol takes one of three values ​​(0, -1.1) and determines, therefore, according to what scheme the structure will be built.

The first option implements the simplest option if + [..]. The answer is formed as a string with the current offset (it is not necessarily equal to 0!), An “if” string, a random condition that is generated by the if_sub function (which will be discussed later), carriage return, and generation of an action block using the action_str function (see below) . As a result, we get something like:

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

The second option is responsible for generating this type: if [..] + elif / else-block (option 2 in the schemes). First, an if + [..] line is formed there, then an elif / else fork occurs, which decides whether the elif-else block will be generated, just if-elif or if-else (e lif_else_block function - see below). Results may vary. For instance:

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

The third option implements recursion from the very beginning (option 3 in the schemes), i.e. gives rise to a branch of the form:

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

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

First, the if line is formed (similarly), then a fork appears, which decides whether to insert the action block further or not, after which the offset is saved and recursion is called. The offset must be saved so that after the recursion is completed and the piece of code is returned, it is possible to add another elif-else block at the same offset as the original line with if. Here you can see how elif and else in the branch stand at the same offset with their "native" if.

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

Next comes a fork in the elif-else-block / action block, which decides whether to add an action block or an elif-else block after recursion. If you decide to add an elif-else block, then there, similarly to the case described above, in scheme 2, elif or else is selected.

Here it is necessary to pay attention to the fact that recursion is called with an offset of + 3 to shift the generated code to the right by a step, and the elif-else block is called with an offset of wall_offset so that this block does not go to the right after the recursion, but remains with the “native” the offset of the original if.

The results can be quite different: from simple to complex, but the appearance of recursion immediately produces the most ornate branches.

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

Now let's look at the elif_else_block function , which is responsible for forming the elif-else block and is called from the main if_gen function .

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)

This function decides whether to add an elif or elif / else block to the code. She does not decide whether to simply put else, but depends on the input value e e_string , which she receives from the main function if_gen . First, the elif block is generated in the while loop , where 2 conditions are checked: probabilistic - the number of elif in the block and the fin_else_flag flag depend on it , which, if it suddenly turns on, it means that else was connected before that, and therefore you need to exit the loop .

The decision whether to attach else to the elif block is also decided by a fork using the same make_solution function , and if else is attached, the fin_else_flag flag is turned on immediatelywhich stops block generation.

The direct joining of elif and else is carried out by the elif_else function (see below). It should be noted here that when generating an elif block (and also when attaching an else block ), the offset wall_offset is used to smoothly build the block as a whole.

Now consider the elif_else function .

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

The function is responsible for the formation of the elif or else line itself, as well as for the subsequent generation of action or recursion blocks after these lines. It also takes an ee_string variable , which contains either elif or else, and forms the corresponding string. Then there is a fork, where it is determined what will go next: (action block or None), or (action block or action block + recursion). Inside this fork, there is a division, respectively, into two sub-fork, and in each case the make_solution function is called with the appropriate parameters for making a decision.

It should be noted that when it occurs in codeif sol!=0, this means that we intentionally give an advantage to one part of the code over another, because if sol! = 0, then it equals either -1, or 1, and therefore another piece of code will be executed less often (only when sol == 0). This is used, in particular, in the elif_else_block function , where it is more profitable for us to let more elifs form in the block, rather than giving equal probability to elif and else. Or, for example, in the elif_else function , we give an advantage to the option when an action block or None is formed rather than what the recursion is going for - otherwise the branches can grow to completely indecent sizes.

We only need to consider the functions responsible for the random generation of expressions in conditions and blocks of actions. As I said above, at this stage they do not play a decisive role and are introduced here to generally show what the final generated code will look like. But since they are used in the generator, we will look at them briefly.

The function responsible for generating the action_str action block .

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)

Everything is quite simple here: from the nested list choise_list, which, as we recall, consists of v ar_list (list of variables) and exp_list (list of expressions), this function consists of one or more lines of this form: a = a + b or b = b . Those. either an expression is assigned to the variable, or another variable (including itself). The rand function randomly selects an element from the list and is needed here solely in order not to produce monstrous strings.

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

The if_sub expression generation function for conditions looks bigger.

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)

It generates expressions by type: ((a)> = (ba)) or ((a)> = (a)) or ((b) <= (b)) . At the same time, both the left and right sides can have various options and stand as separate variables, as well as expressions or their groups. The logical operators or and and are also used here , which are selected for convenience using the or_and_exp function .

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

The rest of the if_sub function cuts off the extra tails from the expressions and adds, when necessary, closing brackets, to consider these dances with tambourines here, I think, is inexpedient.

Well, that's all. You can start the generator, for example, like this:

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

First, the input, including a prob_list with probabilities , then in an infinite loop, calling the main function if_gen and starting the generated generated string for execution. It is worth processing separately ZeroDivisionError, because division by zero with such a random construction of expressions is very common. After the launch, just press Enter for the next generation to appear. Most often they will be quite simple, but often branched and even very branched. Well, import random at the beginning would also be nice to insert;) For those who do not want to see collecting everything by hand, you can download the file from Github (file if_gen.py).

In conclusion, I want to say that the code I presented was tested on hundreds of thousands of generations without errors, while it demonstrated the whole palette of if-elif-else schemes that I wanted to finally see. Once, by mistake, I gave in one part of the code a too high probability of recursion and I got 52,000 (!) Lines of generation and at the same time it was working (although the comp was suspended for 30 seconds). This also indicates the reliability of the algorithm.

Probably, it was possible to write more concisely somewhere, optimize somewhere, arrange the main function in another way, but the main thing is that this code works and generates about 250 generations per second, which, in my opinion, is quite acceptable.

I never considered this code as self-sufficient - it is just a module of the future digital organism and was written for research purposes, so it hardly has any practical applications. At the same time, I am not responsible for any consequences for anyone using the above code, and I urge everyone to cut bread with a knife for cutting bread, and not something else.

In the next article, we will consider the second module, which will be responsible for the random formation of experience. This topic promises to be much more interesting than the if generator, and I will definitely post the results as soon as I have them.

All Articles