In [22]:
%matplotlib inline

import matplotlib
import numpy as np
import matplotlib.pyplot as plt
import json



Genetic approach to artificial intelligence in gaming

Introduction

In this training, I want to focus on the algorithm and not on its performance, this will be for a later study-case.

Artificial intelligence has always been present in games, so the player can play even alone. Finding some complex problems to solve shouldn't be a hard task.

Let's take a real game and try to make an artificial intelligence which solves the game. I choosed a french TV game "Le compte est bon". Let's explains the rules:

  • a goal number is randomly choosen between 100 and 999
  • 6 numbers are randomly choosen from the set : [1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 25, 50, 75, 100]
  • with only the basic integer operations + - * / and the 6 numbers, you have to find the goal number
  • each of the 6 numbers can only be used once

Example

  • goal number: 247
  • set : [3, 5, 7, 100, 25, 1]
  • solution :
3 * 7 = 21
5 * 25 = 125
125 + 21 = 146
146 + 100 = 246
246 + 1 = 247

We can represent the whole calcul in one line : (3 * 7) + (5 * 25) + 100 + 1

Which can also be represented as a tree :

  +
 / \
1   +
   / \
 100  +
     / \
    /   \
   *     *
  / \   / \
 3  7   5  25

Where genetics can help us

About genetic programming

Genetic programming consists on applying the rules of genetic evolution to our algorithm.

On a random population, we determine which candidate solves the problem the best. This step is called evaluation.

The next step is to select the candidates that will be the parents of the next generation. This step is called selection.

We select random parts of both parents DNA to build a new one, and then apply a random mutation so the new candidate is unique.

We repeat this process until we have a new population to evaluate.

The most important part is in the selection step. Once we scored each candidate in the evaluation step we obviously must select the bests to make sure the next generation is closer to the answer.

But building an elite will prevents DNA diversity and the population could never find the solution.

It is important also to include less good candidate, as they can carry intersting genes to pass.

Example

Lets say we have randomly generated a set of tree :

  *              *           
 / \            / \          
1   -          7   +         
   / \            / \        
 100  +         100  +       
     / \            / \      
    /   \          /   \     
   +     /        -     /    
  / \   / \      / \   / \   
 3  7  25  5    3  25 5   1

Those tree could represent the DNA of our population. Genes would be each operators and links to the leafs.

We can easily select part of each tree to insert into a new one as in DNA combination. Then randomly change an operator or switch leafs as in random mutation.

Evaluation would consist on parsing the tree, and get the distance to the goal number : abs(goal - tree_result).

The first generation

First, we need to generate the game :


In [23]:
import random

elements = list(range(1, 11)) * 2 + [25, 50, 75, 100]
game = random.sample(elements, 6)
goal = random.randint(100, 999)

print goal, ':', game


160 : [2, 4, 9, 75, 10, 100]

Now, we need to generate our population, this means for each candidate we will generate random DNA :


In [26]:
# the DNA is just the calcul in a string
def random_dna(game):
    # we want random links to node, so we need to shuffle the game
    game_shuffled = list(game)
    random.shuffle(game_shuffled)
    
    # let start with an empty dna
    dna = ''

    i = 0
    while i < len(game_shuffled):
        try_dna = dna

        if i > 0:
            try_dna += random.choice([' * ', ' / ', ' + ', ' - '])
        
        try_dna += str(float(game_shuffled[i]))
        
        # we check that the result is still an int before recording the random gene
        check_result = eval(try_dna)

        if check_result == int(check_result):
            dna = try_dna
            i += 1
    
    return dna
    
test_dna = random_dna(game)
test_res = eval(test_dna)

assert test_res == int(test_res)

population_size = 1000

def first_generation(population_size=1000):
    return [random_dna(game) for _ in range(population_size)]

population = first_generation(population_size)




In [27]:
print 'Test:', test_dna, '=', test_res
print 'Population Size:', population_size
print 'Population:'

for dna in population[:5]:
    print '->', dna, ' = ', eval(dna)

print '-> ...'


for dna in population[-5:]:
    print '->', dna, ' = ', eval(dna)


Test: 4.0 - 100.0 + 75.0 + 2.0 + 9.0 + 10.0 = 0.0
Population Size: 1000
Population:
-> 75.0 - 2.0 * 4.0 + 10.0 * 100.0 * 9.0  =  9067.0
-> 4.0 * 100.0 - 75.0 + 9.0 + 2.0 - 10.0  =  326.0
-> 10.0 + 2.0 * 100.0 + 4.0 * 75.0 + 9.0  =  519.0
-> 10.0 * 75.0 - 4.0 / 2.0 + 100.0 - 9.0  =  839.0
-> 75.0 * 100.0 * 2.0 - 9.0 * 4.0 - 10.0  =  14954.0
-> ...
-> 100.0 - 75.0 - 10.0 * 9.0 + 2.0 * 4.0  =  -57.0
-> 100.0 - 4.0 * 2.0 + 10.0 * 75.0 - 9.0  =  833.0
-> 2.0 * 4.0 + 100.0 + 10.0 - 9.0 - 75.0  =  34.0
-> 75.0 + 2.0 - 100.0 * 9.0 * 4.0 - 10.0  =  -3533.0
-> 100.0 - 4.0 - 2.0 * 75.0 - 9.0 * 10.0  =  -144.0

Now, we would like to score our population :


In [28]:
def score(dna):
    return abs(goal - eval(dna))

scored_population = sorted([(dna, score(dna)) for dna in population], key=lambda item: item[1])

In [29]:
def show_scored_population(scored_population):
    for dna, score in scored_population[:5]:
        print '->', dna, ' = ', eval(dna), '\t|', score

    print '-> ...'

    for dna, score in scored_population[-5:]:
        print '->', dna, ' = ', eval(dna), '\t|', score

show_scored_population(scored_population)


-> 4.0 * 100.0 - 75.0 * 2.0 - 9.0 * 10.0  =  160.0 	| 0.0
-> 9.0 * 2.0 * 10.0 + 4.0 + 75.0 - 100.0  =  159.0 	| 1.0
-> 4.0 + 100.0 - 10.0 - 9.0 + 2.0 + 75.0  =  162.0 	| 2.0
-> 2.0 - 9.0 - 10.0 + 4.0 + 75.0 + 100.0  =  162.0 	| 2.0
-> 75.0 + 100.0 - 9.0 - 10.0 + 2.0 + 4.0  =  162.0 	| 2.0
-> ...
-> 100.0 * 9.0 / 2.0 * 75.0 * 10.0 * 4.0  =  1350000.0 	| 1349840.0
-> 4.0 - 9.0 * 75.0 * 100.0 * 10.0 * 2.0  =  -1349996.0 	| 1350156.0
-> 4.0 - 100.0 * 10.0 * 2.0 * 75.0 * 9.0  =  -1349996.0 	| 1350156.0
-> 4.0 - 75.0 * 2.0 * 100.0 * 10.0 * 9.0  =  -1349996.0 	| 1350156.0
-> 2.0 * 75.0 * 10.0 * 4.0 * 100.0 * 9.0  =  5400000.0 	| 5399840.0

If we are lucky, we have found the solution on the first generation. But it is unlikely on more complex problems.

For the fun lets make some stats of the generation:


In [30]:
from collections import OrderedDict
import math


def generation_stats(generation):
    scores = [c[1] for c in generation]
    
    stats = OrderedDict((
        ('avg', float(sum(scores)) / len(scores)),
        ('min', min(scores)),
        ('max', max(scores)),
        ('stdev', None),
        ('q1', None),
        ('med', None),
        ('q3', None)
    ))
    
    variance = float(sum([(s - stats['avg'])**2 for s in scores])) / len(scores)
    stats['stdev'] = math.sqrt(variance)
    
    q1idx = len(scores) / 4
    stats['q1'] = scores[q1idx]
    
    q3idx = 3 * len(scores) / 4
    stats['q3'] = scores[q3idx]
    
    if len(scores) % 2 == 0:
        i1idx = len(scores) / 2
        i2idx = i1idx + 1
        
        i1, i2 = scores[i1idx], scores[i2idx]
        stats['med'] = (i1 + i2) / 2
    
    else:
        medidx = len(scores) / 2 + 1
        stats['med'] = scores[medidx]

    return stats, scores

In [31]:
def plot_stats(stats, scores, gen=0):
    rows = zip(*stats.items())
    dim = [0.05, 0.80, 0.9, 0.15]

    # Figure 1: min avg/q3 max color graph
    fig1 = plt.figure(figsize=(18, 3))
    a1x = fig1.add_axes(dim)

    cmap1 = matplotlib.colors.ListedColormap(['g', 'b', 'r'])
    bounds1 = [
        stats['min'],
        min(stats['q3'], stats['avg']),
        max(stats['q3'], stats['avg']),
        stats['max']
    ]
    norm1 = matplotlib.colors.BoundaryNorm(bounds1, cmap1.N)
    cbl1 = matplotlib.colorbar.ColorbarBase(
        a1x,
        cmap=cmap1, norm=norm1,
        spacing='proportional',
        orientation='horizontal'
    )

    # Figure 2: min q1 med q3 color graph
    fig2 = plt.figure(figsize=(18, 3))
    a2x = fig2.add_axes(dim)

    cmap2 = matplotlib.colors.ListedColormap(['g', 'b', 'y'])
    bounds2 = [stats['min'], stats['q1'], stats['med'], stats['q3']]
    norm2 = matplotlib.colors.BoundaryNorm(bounds2, cmap2.N)
    cbl2 = matplotlib.colorbar.ColorbarBase(
        a2x,
        cmap=cmap2, norm=norm2,
        spacing='proportional',
        orientation='horizontal'
    )

    a1x.set_xticklabels([
        'min',
        'avg' if stats['avg'] <= stats['q3'] else 'q3',
        'avg' if stats['avg'] > stats['q3'] else 'q3',
        'max'
    ])
    a2x.set_xticklabels(['min', 'q1', 'med', 'q3', 'max'])

    # Figure 3: scores line chart
    fig3, a3x = plt.subplots()
    a3x.plot(scores)
    a3x.grid(True)
    a3x.set_ylabel('Score')
    a3x.set_xlabel('Candidate')

    a1x.set_title('Generation: {0}'.format(gen))
    plt.show()

In [32]:
stats1, scores1 = generation_stats(scored_population)
print json.dumps(stats1, indent=2)
plot_stats(stats1, scores1)


{
  "avg": 24536.851, 
  "min": 0.0, 
  "max": 5399840.0, 
  "stdev": 201601.91834952563, 
  "q1": 144.0, 
  "med": 411.5, 
  "q3": 1701.0
}

Now that we have our first generation and we know what it looks like, we can proceed to selection.

Selection implementation

If we just generate a new random population, our algorithm would be nothing else than a bruteforce.

The selection step makes everything. So how do we proceed ?

After a few runs, it looks like the distance from the goal grows slowly except a very few number of with an enormous distance due to random.

This is good! This means we have a lot of interesting solutions. Let's try the following approach for selection : the 50% best candidates of each quartile in this generation.


In [33]:
def selection(generation, stats):
    parents = []
    
    q1 = filter(lambda c: c[1] < stats['q1'], generation)
    q2 = filter(lambda c: stats['q1'] <= c[1] < stats['med'], generation)
    q3 = filter(lambda c: stats['med'] <= c[1] < stats['q3'], generation)
    q4 = filter(lambda c: stats['q3'] <= c[1], generation)
    
    for q in [q1, q2, q3, q4]:
        parents += q[:len(q) / 2]
    
    return parents

In [34]:
s1 = selection(scored_population, stats1)
show_scored_population(s1)


-> 4.0 * 100.0 - 75.0 * 2.0 - 9.0 * 10.0  =  160.0 	| 0.0
-> 9.0 * 2.0 * 10.0 + 4.0 + 75.0 - 100.0  =  159.0 	| 1.0
-> 4.0 + 100.0 - 10.0 - 9.0 + 2.0 + 75.0  =  162.0 	| 2.0
-> 2.0 - 9.0 - 10.0 + 4.0 + 75.0 + 100.0  =  162.0 	| 2.0
-> 75.0 + 100.0 - 9.0 - 10.0 + 2.0 + 4.0  =  162.0 	| 2.0
-> ...
-> 9.0 - 10.0 / 2.0 - 4.0 - 100.0 * 75.0  =  -7500.0 	| 7660.0
-> 10.0 - 9.0 - 75.0 * 100.0 + 2.0 - 4.0  =  -7501.0 	| 7661.0
-> 4.0 - 100.0 * 75.0 - 2.0 - 10.0 - 9.0  =  -7517.0 	| 7677.0
-> 10.0 - 75.0 * 100.0 - 9.0 * 4.0 - 2.0  =  -7528.0 	| 7688.0
-> 2.0 - 10.0 * 4.0 + 9.0 - 100.0 * 75.0  =  -7529.0 	| 7689.0

In [35]:
stats_s1, scores_s1 = generation_stats(s1)
print json.dumps(stats_s1, indent=2)
plot_stats(stats_s1, scores_s1)


{
  "avg": 1473.6993987975952, 
  "min": 0.0, 
  "max": 7689.0, 
  "stdev": 2381.1516575497963, 
  "q1": 68.0, 
  "med": 412.0, 
  "q3": 1701.0
}

Combination and mutation, creating the next generation

In this part, we will parse the DNA code of each candidates of s1 in order to build the tree.

Because it is a simple calcul, it is also a valid Python code. We could use the library RedBaron, lets import it:


In [36]:
from redbaron import RedBaron, BinaryOperatorNode, FloatNode

red = RedBaron(s1[0][0])

print 'Example:', s1[0][0]
red.help()


Example: 4.0 * 100.0 - 75.0 * 2.0 - 9.0 * 10.0
0 -----------------------------------------------------
BinaryOperatorNode()
  # identifiers: binary_operator, binary_operator_, binaryoperator, binaryoperatornode
  value='-'
  first ->
    BinaryOperatorNode()
      # identifiers: binary_operator, binary_operator_, binaryoperator, binaryoperatornode
      value='*'
      first ->
        FloatNode() ...
      second ->
        FloatNode() ...
  second ->
    BinaryOperatorNode()
      # identifiers: binary_operator, binary_operator_, binaryoperator, binaryoperatornode
      value='-'
      first ->
        BinaryOperatorNode() ...
      second ->
        BinaryOperatorNode() ...

Now we need to parse a pair of candidates, in order to return a new one. How do we proceed ?

I suggest that :

  • we copy parent b's DNA into child c
  • then we choose randomly a BinaryOperatorNode in parent a's DNA and find all FloatNode() it contains
  • each of thoose nodes are deleted from c's DNA, as well as the useless operators
  • we insert the choosen BinaryOperatorNode into c's DNA with a new randomly choosen BinaryOperatorNode

    Example :

Step 1:

  *              *           
 / \            / \          
1   -          7   +         
   / \            / \        
 100 (+)        100  +       
     / \            / \      
    /   \          /   \     
   +     /        -     /    
  / \   / \      / \   / \   
 3  7  25  5    3  25 5   1

Step 2:

  *              *           
 / \            / \          
1   -          x   +         
   / \            / \        
 100 (+)        100  +       
     / \            / \      
    /   \          /   \     
   +     /        -     /    
  / \   / \      / \   / \   
 3  7  25  5    x   x x   1  

  *              +           
 / \            / \          
1   -         100  1         
   / \                       
 100 (+)                     
     / \                     
    /   \                    
   +     /                   
  / \   / \                  
 3  7  25  5

Step 3:

     *    
    / \   
   +   \  
  / \   \ 
100  1   +                     
        / \                     
       /   \                    
      +     /                   
     / \   / \                  
    3   7 25  5

In [ ]:
def gen_child(parents_dna):
    # reimport things because it will be used in a IPython parallel engine
    from redbaron import RedBaron
    import random

    tmpred = RedBaron(parents_dna[0])
    child = RedBaron(parents_dna[1])
    
    # Choose random operator from parent a
    operators = tmpred.find_all('binary_operator')
    op = random.choice(operators[1:])  # we don't want the root operator
    
    # Find and remove all leafs from child
    nbs = [float(nb.value) for nb in op.find_all('float')]
    
    # mark the nodes as empty
    for node in child.find_all('float'):
        if float(node.value) in nbs:
            if node.parent.first is node:
                node.parent.first.replace('None')
            
            elif node.parent.second is node:
                node.parent.second.replace('None')

    # keep going until nothing is done (which means there is no more empty nodes)
    reparented = True
    while reparented:
        reparented = False

        for node in child.find_all('binary_operator'):
            if node.first.value == 'None' and node.second.value == 'None':
                reparent = 'None'

            elif node.first.value == 'None':
                reparent = node.second.dumps()

            elif node.second.value == 'None':
                reparent = node.first.dumps()

            else:
                continue

            if node.parent.parent is None:
                node.replace(reparent)
                reparented = True

            elif node.parent.first is node:
                node.parent.first.replace(reparent)
                reparented = True

            elif node.parent.second is node:
                node.parent.second.replace(reparent)
                reparented = True
    
    # Combine parents DNA with a mutation: a random operator
    notint = True
    while notint:
        combine = '{0} {2} {1}'.format(
            op.dumps(),
            child[0].dumps(),
            random.choice(['+', '-', '*', '/'])
        )

        res = eval(combine)
        
        if res == int(res):
            notint = False

    child[0].replace(combine)

    print '.'
    return child.dumps()

In [38]:
child = gen_child((s1[0][0], s1[-1][0]))
test_child = eval(child)
assert test_child == int(test_child)

print child, '=', test_child


9.0 * 10.0 / 2.0 - 4.0 + 100.0 * 75.0 = 7541.0

Now that we are able to generate a child from two parents, let's make our couples:


In [39]:
def make_couples(selected):
    parents = []
    
    for _ in range(4):
        _set = list(selected)
        
        while len(_set) > 1:
            i = random.randrange(0, len(_set))
            a = _set.pop(i)

            i = random.randrange(0, len(_set))
            b = _set.pop(i)

            parents.append((a[0], b[0]))
    
    return parents

And generate our new population:


In [61]:
def new_population(selected, population_size):
    population = []
    couples = make_couples(selected)
    
    while len(population) < population_size:
        parents = random.choice(couples)
        population.append(gen_child(parents))
    
    return population

The whole process


In [ ]:
def next_generation(population, gen):
    scored_population = sorted([(dna, score(dna)) for dna in population], key=lambda item: item[1])
    show_scored_population(scored_population)

    for i in range(len(scored_population)):
        if scored_population[i][1] == 0:
            print 'WIN:', i, ':', scored_population[i][0]
        
        else:
            break

    stats, scores = generation_stats(scored_population)
    print json.dumps(stats, indent=2)
    plot_stats(stats, scores, gen)

    selected = selection(scored_population, stats)
    return new_population(selected, len(population)), gen + 1

In [ ]:
def main(popsize=1000, nsteps=100):
    pop, gen = first_generation(popsize), 0

    while nsteps > 0:
        pop, gen = next_generation(pop, gen)
        nsteps -= 1

In [ ]:
main(popsize=10, nsteps=30)


-> 100.0 / 4.0 - 2.0 - 10.0 - 9.0 + 75.0  =  79.0 	| 81.0
-> 4.0 * 100.0 + 2.0 + 75.0 - 10.0 + 9.0  =  476.0 	| 316.0
-> 10.0 - 4.0 - 100.0 - 2.0 * 75.0 + 9.0  =  -235.0 	| 395.0
-> 2.0 - 100.0 / 10.0 * 4.0 + 9.0 * 75.0  =  637.0 	| 477.0
-> 100.0 + 75.0 * 10.0 + 4.0 * 2.0 - 9.0  =  849.0 	| 689.0
-> ...
-> 100.0 + 2.0 + 4.0 - 9.0 * 75.0 + 10.0  =  -559.0 	| 719.0
-> 9.0 * 10.0 + 75.0 + 100.0 * 4.0 * 2.0  =  965.0 	| 805.0
-> 2.0 * 10.0 + 75.0 - 9.0 * 4.0 * 100.0  =  -3505.0 	| 3665.0
-> 75.0 * 9.0 * 10.0 + 4.0 + 2.0 * 100.0  =  6954.0 	| 6794.0
-> 9.0 - 4.0 / 2.0 * 100.0 * 75.0 * 10.0  =  -149991.0 	| 150151.0
{
  "avg": 16409.2, 
  "min": 81.0, 
  "max": 150151.0, 
  "stdev": 44625.58088540697, 
  "q1": 395.0, 
  "med": 762.0, 
  "q3": 3665.0
}
-> 2.0 * 75.0 + 100.0 / 10.0 * 4.0 + 9.0  =  199.0 	| 39.0
-> 2.0 * 75.0 + 9.0 - 100.0 / 10.0 * 4.0  =  119.0 	| 41.0
-> 9.0 + 75.0 * 2.0 - 100.0 / 10.0 * 4.0  =  119.0 	| 41.0
-> 2.0 * 10.0 * 4.0 - 100.0 - 75.0 + 9.0  =  -86.0 	| 246.0
-> 4.0 * 100.0 / 10.0 - 2.0 * 75.0 + 9.0  =  -101.0 	| 261.0
-> ...
-> 2.0 * 10.0 / 4.0 - 100.0 - 75.0 + 9.0  =  -161.0 	| 321.0
-> 10.0 * 4.0 - 100.0 - 2.0 * 75.0 + 9.0  =  -201.0 	| 361.0
-> 75.0 - 9.0 * 4.0 * 100.0 + 2.0 - 10.0  =  -3533.0 	| 3693.0
-> 9.0 * 75.0 * 10.0 - 4.0 - 100.0 - 2.0  =  6644.0 	| 6484.0
-> 75.0 - 9.0 * 4.0 * 100.0 * 10.0 - 2.0  =  -35927.0 	| 36087.0
{
  "avg": 4757.4, 
  "min": 39.0, 
  "max": 36087.0, 
  "stdev": 10639.50378730136, 
  "q1": 41.0, 
  "med": 341.0, 
  "q3": 3693.0
}
-> 75.0 * 2.0 - 100.0 / 10.0 * 4.0 + 9.0  =  119.0 	| 41.0
-> 100.0 / 10.0 * 4.0 + 75.0 - 9.0 + 2.0  =  108.0 	| 52.0
-> 75.0 * 2.0 - 9.0 - 100.0 / 10.0 * 4.0  =  101.0 	| 59.0
-> 2.0 - 10.0 - 75.0 + 9.0 - 100.0 / 4.0  =  -99.0 	| 259.0
-> 10.0 * 4.0 - 2.0 * 75.0 + 9.0 - 100.0  =  -201.0 	| 361.0
-> ...
-> 2.0 - 10.0 * 75.0 + 9.0 - 100.0 / 4.0  =  -764.0 	| 924.0
-> 75.0 * 2.0 * 9.0 - 100.0 / 10.0 * 4.0  =  1310.0 	| 1150.0
-> 75.0 * 2.0 * 9.0 - 100.0 / 10.0 * 4.0  =  1310.0 	| 1150.0
-> 100.0 / 10.0 * 4.0 / 2.0 * 75.0 + 9.0  =  1509.0 	| 1349.0
-> 9.0 * 4.0 * 100.0 + 2.0 * 75.0 + 10.0  =  3760.0 	| 3600.0
{
  "avg": 894.5, 
  "min": 41.0, 
  "max": 3600.0, 
  "stdev": 1023.8428834542925, 
  "q1": 59.0, 
  "med": 1037.0, 
  "q3": 1150.0
}
-> 75.0 + 9.0 - 100.0 / 4.0 - 2.0 - 10.0  =  47.0 	| 113.0
-> 75.0 + 9.0 - 100.0 / 4.0 * 2.0 - 10.0  =  24.0 	| 136.0
-> 2.0 * 9.0 - 75.0 - 100.0 / 10.0 * 4.0  =  -97.0 	| 257.0
-> 2.0 * 9.0 - 75.0 - 100.0 / 10.0 * 4.0  =  -97.0 	| 257.0
-> 2.0 * 9.0 - 75.0 - 100.0 / 10.0 * 4.0  =  -97.0 	| 257.0
-> ...
-> 2.0 * 9.0 - 75.0 - 100.0 / 10.0 * 4.0  =  -97.0 	| 257.0
-> 100.0 / 10.0 * 4.0 + 9.0 * 75.0 * 2.0  =  1390.0 	| 1230.0
-> 100.0 / 10.0 * 4.0 + 75.0 * 2.0 * 9.0  =  1390.0 	| 1230.0
-> 10.0 * 4.0 * 75.0 * 2.0 - 9.0 - 100.0  =  5891.0 	| 5731.0
-> 100.0 / 10.0 * 4.0 * 75.0 * 2.0 - 9.0  =  5991.0 	| 5831.0
{
  "avg": 1529.9, 
  "min": 113.0, 
  "max": 5831.0, 
  "stdev": 2162.4116837457204, 
  "q1": 257.0, 
  "med": 743.5, 
  "q3": 1230.0
}
-> 10.0 * 4.0 / 2.0 * 9.0 - 75.0 - 100.0  =  5.0 	| 155.0
-> 100.0 / 4.0 - 2.0 - 10.0 - 9.0 - 75.0  =  -71.0 	| 231.0
-> 2.0 - 10.0 + 9.0 - 75.0 - 100.0 / 4.0  =  -99.0 	| 259.0
-> 10.0 * 4.0 - 2.0 * 9.0 - 75.0 - 100.0  =  -153.0 	| 313.0
-> 2.0 * 9.0 * 100.0 / 10.0 * 4.0 + 75.0  =  795.0 	| 635.0
-> ...
-> 10.0 * 4.0 - 100.0 + 75.0 * 2.0 * 9.0  =  1290.0 	| 1130.0
-> 9.0 * 75.0 * 2.0 - 100.0 / 4.0 - 10.0  =  1315.0 	| 1155.0
-> 100.0 / 10.0 * 4.0 * 75.0 + 9.0 - 2.0  =  3007.0 	| 2847.0
-> 100.0 / 10.0 * 4.0 * 75.0 + 9.0 - 2.0  =  3007.0 	| 2847.0
-> 100.0 / 10.0 * 4.0 * 75.0 * 2.0 * 9.0  =  54000.0 	| 53840.0
{
  "avg": 6341.2, 
  "min": 155.0, 
  "max": 53840.0, 
  "stdev": 15862.23448824282, 
  "q1": 259.0, 
  "med": 1142.5, 
  "q3": 2847.0
}
-> 4.0 * 75.0 + 2.0 - 10.0 + 9.0 - 100.0  =  201.0 	| 41.0
-> 2.0 * 9.0 * 10.0 + 75.0 - 100.0 / 4.0  =  230.0 	| 70.0
-> 9.0 - 2.0 + 10.0 + 75.0 - 100.0 / 4.0  =  67.0 	| 93.0
-> 75.0 - 100.0 / 10.0 * 4.0 - 2.0 * 9.0  =  17.0 	| 143.0
-> 10.0 * 4.0 / 2.0 * 9.0 - 75.0 - 100.0  =  5.0 	| 155.0
-> ...
-> 2.0 * 9.0 - 75.0 - 100.0 + 10.0 * 4.0  =  -117.0 	| 277.0
-> 10.0 * 4.0 * 75.0 / 2.0 - 9.0 - 100.0  =  1391.0 	| 1231.0
-> 10.0 * 4.0 * 75.0 - 2.0 - 9.0 - 100.0  =  2889.0 	| 2729.0
-> 100.0 / 10.0 * 4.0 * 75.0 + 2.0 - 9.0  =  2993.0 	| 2833.0
-> 2.0 * 9.0 - 100.0 / 10.0 * 4.0 * 75.0  =  -2982.0 	| 3142.0
{
  "avg": 1071.4, 
  "min": 41.0, 
  "max": 3142.0, 
  "stdev": 1245.725025838367, 
  "q1": 93.0, 
  "med": 754.0, 
  "q3": 2729.0
}
-> 100.0 / 4.0 + 75.0 + 2.0 - 10.0 + 9.0  =  101.0 	| 59.0
-> 100.0 / 10.0 * 4.0 - 2.0 * 9.0 + 75.0  =  97.0 	| 63.0
-> 2.0 + 10.0 + 75.0 - 100.0 / 4.0 + 9.0  =  71.0 	| 89.0
-> 10.0 + 75.0 - 100.0 / 4.0 + 2.0 - 9.0  =  53.0 	| 107.0
-> 75.0 - 100.0 / 4.0 + 2.0 - 10.0 + 9.0  =  51.0 	| 109.0
-> ...
-> 10.0 * 4.0 + 75.0 + 2.0 - 9.0 - 100.0  =  8.0 	| 152.0
-> 4.0 * 75.0 + 9.0 - 2.0 + 10.0 + 100.0  =  417.0 	| 257.0
-> 100.0 / 4.0 * 75.0 - 10.0 - 2.0 * 9.0  =  1847.0 	| 1687.0
-> 100.0 / 4.0 * 75.0 + 2.0 - 10.0 + 9.0  =  1876.0 	| 1716.0
-> 100.0 / 10.0 * 4.0 * 75.0 + 2.0 - 9.0  =  2993.0 	| 2833.0
{
  "avg": 707.2, 
  "min": 59.0, 
  "max": 2833.0, 
  "stdev": 945.6769850218415, 
  "q1": 89.0, 
  "med": 204.5, 
  "q3": 1687.0
}
-> 75.0 + 2.0 - 10.0 + 9.0 + 100.0 / 4.0  =  101.0 	| 59.0
-> 100.0 / 4.0 + 10.0 + 75.0 - 2.0 - 9.0  =  99.0 	| 61.0
-> 100.0 / 4.0 + 75.0 - 10.0 - 2.0 * 9.0  =  72.0 	| 88.0
-> 10.0 + 75.0 - 100.0 / 4.0 + 9.0 - 2.0  =  67.0 	| 93.0
-> 2.0 - 9.0 + 10.0 + 75.0 - 100.0 / 4.0  =  53.0 	| 107.0
-> ...
-> 75.0 + 2.0 - 10.0 + 9.0 * 100.0 / 4.0  =  292.0 	| 132.0
-> 10.0 - 2.0 * 9.0 - 75.0 - 100.0 / 4.0  =  -108.0 	| 268.0
-> 2.0 - 10.0 + 9.0 * 75.0 - 100.0 / 4.0  =  642.0 	| 482.0
-> 100.0 / 4.0 + 9.0 * 75.0 + 2.0 - 10.0  =  692.0 	| 532.0
-> 10.0 - 2.0 * 9.0 * 75.0 - 100.0 / 4.0  =  -1365.0 	| 1525.0
{
  "avg": 334.7, 
  "min": 59.0, 
  "max": 1525.0, 
  "stdev": 429.5374372508175, 
  "q1": 88.0, 
  "med": 200.0, 
  "q3": 482.0
}
-> 9.0 + 100.0 / 4.0 * 2.0 - 10.0 + 75.0  =  124.0 	| 36.0
-> 2.0 - 10.0 + 9.0 + 100.0 / 4.0 + 75.0  =  101.0 	| 59.0
-> 100.0 / 4.0 + 9.0 - 2.0 + 75.0 - 10.0  =  97.0 	| 63.0
-> 2.0 * 9.0 + 10.0 + 75.0 - 100.0 / 4.0  =  78.0 	| 82.0
-> 75.0 - 10.0 - 2.0 * 9.0 - 100.0 / 4.0  =  22.0 	| 138.0
-> ...
-> 2.0 - 10.0 + 9.0 + 100.0 / 4.0 - 75.0  =  -49.0 	| 209.0
-> 100.0 / 4.0 * 2.0 - 10.0 + 9.0 * 75.0  =  715.0 	| 555.0
-> 100.0 / 4.0 * 75.0 - 10.0 - 2.0 * 9.0  =  1847.0 	| 1687.0
-> 100.0 / 4.0 * 75.0 - 10.0 - 2.0 * 9.0  =  1847.0 	| 1687.0
-> 100.0 / 4.0 * 75.0 - 10.0 - 2.0 * 9.0  =  1847.0 	| 1687.0
{
  "avg": 620.3, 
  "min": 36.0, 
  "max": 1687.0, 
  "stdev": 712.4890244768687, 
  "q1": 63.0, 
  "med": 382.0, 
  "q3": 1687.0
}
-> 100.0 / 4.0 * 2.0 - 10.0 + 75.0 + 9.0  =  124.0 	| 36.0
-> 100.0 / 4.0 + 9.0 + 2.0 - 10.0 + 75.0  =  101.0 	| 59.0
-> 100.0 / 4.0 + 9.0 + 2.0 - 10.0 + 75.0  =  101.0 	| 59.0
-> 10.0 + 75.0 - 100.0 / 4.0 - 9.0 + 2.0  =  53.0 	| 107.0
-> 75.0 - 100.0 / 4.0 + 9.0 + 2.0 - 10.0  =  51.0 	| 109.0
-> ...
-> 10.0 + 75.0 - 100.0 / 4.0 * 9.0 + 2.0  =  -138.0 	| 298.0
-> 10.0 + 75.0 - 100.0 / 4.0 * 2.0 * 9.0  =  -365.0 	| 525.0
-> 100.0 / 4.0 * 75.0 - 10.0 - 2.0 * 9.0  =  1847.0 	| 1687.0
-> 100.0 / 4.0 * 75.0 - 9.0 - 2.0 + 10.0  =  1874.0 	| 1714.0
-> 100.0 / 4.0 * 2.0 * 75.0 - 10.0 - 9.0  =  3731.0 	| 3571.0
{
  "avg": 816.5, 
  "min": 36.0, 
  "max": 3571.0, 
  "stdev": 1107.4791420157762, 
  "q1": 59.0, 
  "med": 411.5, 
  "q3": 1687.0
}
-> 100.0 / 4.0 + 9.0 + 2.0 - 10.0 + 75.0  =  101.0 	| 59.0
-> 9.0 + 2.0 - 10.0 + 75.0 + 100.0 / 4.0  =  101.0 	| 59.0
-> 10.0 + 75.0 + 100.0 / 4.0 - 2.0 * 9.0  =  92.0 	| 68.0
-> 10.0 + 75.0 + 100.0 / 4.0 - 2.0 * 9.0  =  92.0 	| 68.0
-> 2.0 - 10.0 + 75.0 + 100.0 / 4.0 - 9.0  =  83.0 	| 77.0
-> ...
-> 9.0 + 2.0 - 10.0 + 75.0 - 100.0 / 4.0  =  51.0 	| 109.0
-> 10.0 + 75.0 - 100.0 / 4.0 - 2.0 * 9.0  =  42.0 	| 118.0
-> 100.0 / 4.0 - 75.0 - 10.0 - 2.0 * 9.0  =  -78.0 	| 238.0
-> 10.0 + 75.0 * 100.0 / 4.0 - 2.0 * 9.0  =  1867.0 	| 1707.0
-> 10.0 + 75.0 * 100.0 / 4.0 - 2.0 * 9.0  =  1867.0 	| 1707.0
{
  "avg": 421.0, 
  "min": 59.0, 
  "max": 1707.0, 
  "stdev": 644.9710071003192, 
  "q1": 68.0, 
  "med": 113.5, 
  "q3": 238.0
}
-> 100.0 / 4.0 + 10.0 + 75.0 + 2.0 * 9.0  =  128.0 	| 32.0
-> 2.0 * 9.0 - 10.0 + 75.0 + 100.0 / 4.0  =  108.0 	| 52.0
-> 2.0 * 9.0 - 10.0 + 75.0 + 100.0 / 4.0  =  108.0 	| 52.0
-> 2.0 * 9.0 - 10.0 + 75.0 + 100.0 / 4.0  =  108.0 	| 52.0
-> 9.0 + 2.0 - 10.0 + 75.0 - 100.0 / 4.0  =  51.0 	| 109.0
-> ...
-> 2.0 * 9.0 * 10.0 + 75.0 + 100.0 / 4.0  =  280.0 	| 120.0
-> 10.0 - 2.0 * 9.0 * 75.0 + 100.0 / 4.0  =  -1315.0 	| 1475.0
-> 10.0 - 2.0 * 9.0 * 75.0 + 100.0 / 4.0  =  -1315.0 	| 1475.0
-> 2.0 - 10.0 + 75.0 * 100.0 / 4.0 - 9.0  =  1858.0 	| 1698.0
-> 10.0 + 75.0 * 100.0 / 4.0 - 2.0 * 9.0  =  1867.0 	| 1707.0
{
  "avg": 677.2, 
  "min": 32.0, 
  "max": 1707.0, 
  "stdev": 748.1792298640747, 
  "q1": 52.0, 
  "med": 797.5, 
  "q3": 1475.0
}
-> 100.0 / 4.0 + 2.0 * 9.0 - 10.0 + 75.0  =  108.0 	| 52.0
-> 75.0 + 100.0 / 4.0 + 2.0 * 9.0 - 10.0  =  108.0 	| 52.0
-> 75.0 + 100.0 / 4.0 + 10.0 - 2.0 * 9.0  =  92.0 	| 68.0
-> 10.0 + 75.0 + 100.0 / 4.0 - 2.0 * 9.0  =  92.0 	| 68.0
-> 10.0 + 75.0 + 100.0 / 4.0 - 2.0 * 9.0  =  92.0 	| 68.0
-> ...
-> 10.0 + 75.0 + 100.0 / 4.0 * 2.0 * 9.0  =  535.0 	| 375.0
-> 9.0 * 75.0 + 10.0 - 2.0 + 100.0 / 4.0  =  708.0 	| 548.0
-> 100.0 / 4.0 * 10.0 - 2.0 * 9.0 * 75.0  =  -1100.0 	| 1260.0
-> 100.0 / 4.0 - 10.0 - 2.0 * 9.0 * 75.0  =  -1335.0 	| 1495.0
-> 100.0 / 4.0 - 10.0 - 2.0 * 9.0 * 75.0  =  -1335.0 	| 1495.0
{
  "avg": 548.1, 
  "min": 52.0, 
  "max": 1495.0, 
  "stdev": 592.7666404243747, 
  "q1": 68.0, 
  "med": 461.5, 
  "q3": 1260.0
}
-> 2.0 * 9.0 + 100.0 / 4.0 + 10.0 + 75.0  =  128.0 	| 32.0
-> 2.0 * 9.0 + 10.0 + 75.0 + 100.0 / 4.0  =  128.0 	| 32.0
-> 2.0 * 9.0 + 100.0 / 4.0 + 10.0 + 75.0  =  128.0 	| 32.0
-> 2.0 * 9.0 + 100.0 / 4.0 + 10.0 + 75.0  =  128.0 	| 32.0
-> 100.0 / 4.0 * 10.0 - 75.0 + 2.0 * 9.0  =  193.0 	| 33.0
-> ...
-> 2.0 * 9.0 - 10.0 + 75.0 - 100.0 / 4.0  =  58.0 	| 102.0
-> 100.0 / 4.0 - 10.0 - 2.0 * 9.0 * 75.0  =  -1335.0 	| 1495.0
-> 100.0 / 4.0 * 75.0 + 10.0 - 2.0 * 9.0  =  1867.0 	| 1707.0
-> 9.0 * 75.0 * 100.0 / 4.0 + 10.0 - 2.0  =  16883.0 	| 16723.0
-> 100.0 / 4.0 * 10.0 * 75.0 + 2.0 * 9.0  =  18768.0 	| 18608.0
{
  "avg": 3879.6, 
  "min": 32.0, 
  "max": 18608.0, 
  "stdev": 6932.300443575711, 
  "q1": 32.0, 
  "med": 798.5, 
  "q3": 1707.0
}
-> 2.0 * 9.0 + 100.0 / 4.0 + 10.0 + 75.0  =  128.0 	| 32.0
-> 75.0 + 100.0 / 4.0 + 2.0 * 9.0 + 10.0  =  128.0 	| 32.0
-> 10.0 + 75.0 + 100.0 / 4.0 - 2.0 * 9.0  =  92.0 	| 68.0
-> 10.0 - 2.0 * 9.0 + 100.0 / 4.0 + 75.0  =  92.0 	| 68.0
-> 100.0 / 4.0 + 10.0 + 75.0 - 2.0 * 9.0  =  92.0 	| 68.0
-> ...
-> 10.0 - 2.0 * 9.0 + 100.0 / 4.0 + 75.0  =  92.0 	| 68.0
-> 100.0 / 4.0 + 10.0 + 75.0 - 2.0 * 9.0  =  92.0 	| 68.0
-> 2.0 * 9.0 * 100.0 / 4.0 + 10.0 + 75.0  =  535.0 	| 375.0
-> 100.0 / 4.0 * 75.0 - 2.0 * 9.0 + 10.0  =  1867.0 	| 1707.0
-> 2.0 * 9.0 - 100.0 / 4.0 * 75.0 + 10.0  =  -1847.0 	| 2007.0
{
  "avg": 449.3, 
  "min": 32.0, 
  "max": 2007.0, 
  "stdev": 713.3712988339242, 
  "q1": 68.0, 
  "med": 68.0, 
  "q3": 375.0
}
-> 100.0 / 4.0 + 10.0 + 75.0 + 2.0 * 9.0  =  128.0 	| 32.0
-> 100.0 / 4.0 + 2.0 * 9.0 + 10.0 + 75.0  =  128.0 	| 32.0
-> 100.0 / 4.0 + 10.0 + 75.0 + 2.0 * 9.0  =  128.0 	| 32.0
-> 2.0 * 9.0 + 100.0 / 4.0 + 75.0 - 10.0  =  108.0 	| 52.0
-> 75.0 + 100.0 / 4.0 - 2.0 * 9.0 - 10.0  =  72.0 	| 88.0
-> ...
-> 100.0 / 4.0 * 10.0 - 2.0 * 9.0 + 75.0  =  307.0 	| 147.0
-> 100.0 / 4.0 - 2.0 * 9.0 * 10.0 + 75.0  =  -80.0 	| 240.0
-> 75.0 + 100.0 / 4.0 - 2.0 * 9.0 * 10.0  =  -80.0 	| 240.0
-> 2.0 * 9.0 * 100.0 / 4.0 + 10.0 + 75.0  =  535.0 	| 375.0
-> 2.0 * 9.0 + 100.0 / 4.0 + 75.0 * 10.0  =  793.0 	| 633.0
{
  "avg": 187.1, 
  "min": 32.0, 
  "max": 633.0, 
  "stdev": 184.818532620514, 
  "q1": 32.0, 
  "med": 193.5, 
  "q3": 240.0
}
-> 100.0 / 4.0 - 75.0 + 2.0 * 9.0 * 10.0  =  130.0 	| 30.0
-> 100.0 / 4.0 + 10.0 + 75.0 + 2.0 * 9.0  =  128.0 	| 32.0
-> 100.0 / 4.0 + 10.0 + 75.0 + 2.0 * 9.0  =  128.0 	| 32.0
-> 100.0 / 4.0 - 2.0 * 9.0 + 10.0 + 75.0  =  92.0 	| 68.0
-> 10.0 + 75.0 + 2.0 * 9.0 - 100.0 / 4.0  =  78.0 	| 82.0
-> ...
-> 2.0 * 9.0 * 10.0 + 75.0 + 100.0 / 4.0  =  280.0 	| 120.0
-> 10.0 + 75.0 + 2.0 * 9.0 * 100.0 / 4.0  =  535.0 	| 375.0
-> 75.0 + 2.0 * 9.0 * 100.0 / 4.0 + 10.0  =  535.0 	| 375.0
-> 100.0 / 4.0 - 2.0 * 9.0 * 10.0 - 75.0  =  -230.0 	| 390.0
-> 9.0 * 10.0 * 75.0 + 100.0 / 4.0 - 2.0  =  6773.0 	| 6613.0
{
  "avg": 811.7, 
  "min": 30.0, 
  "max": 6613.0, 
  "stdev": 1939.207211723389, 
  "q1": 32.0, 
  "med": 247.5, 
  "q3": 375.0
}
-> 100.0 / 4.0 + 10.0 + 75.0 + 2.0 * 9.0  =  128.0 	| 32.0
-> 75.0 + 2.0 * 9.0 - 100.0 / 4.0 + 10.0  =  78.0 	| 82.0
-> 100.0 / 4.0 * 10.0 + 75.0 + 2.0 * 9.0  =  343.0 	| 183.0
-> 2.0 * 9.0 * 100.0 / 4.0 - 10.0 + 75.0  =  515.0 	| 355.0
-> 2.0 * 9.0 * 100.0 / 4.0 - 10.0 + 75.0  =  515.0 	| 355.0
-> ...
-> 10.0 + 75.0 + 2.0 * 9.0 * 100.0 / 4.0  =  535.0 	| 375.0
-> 2.0 * 9.0 * 100.0 / 4.0 + 10.0 * 75.0  =  1200.0 	| 1040.0
-> 2.0 * 9.0 * 75.0 + 100.0 / 4.0 + 10.0  =  1385.0 	| 1225.0
-> 9.0 * 100.0 / 4.0 * 10.0 + 75.0 + 2.0  =  2327.0 	| 2167.0
-> 2.0 * 9.0 * 100.0 / 4.0 * 10.0 + 75.0  =  4575.0 	| 4415.0
{
  "avg": 1022.9, 
  "min": 32.0, 
  "max": 4415.0, 
  "stdev": 1293.870430143606, 
  "q1": 183.0, 
  "med": 707.5, 
  "q3": 1225.0
}
-> 100.0 / 4.0 - 10.0 + 75.0 + 2.0 * 9.0  =  108.0 	| 52.0
-> 10.0 + 75.0 + 2.0 * 9.0 - 100.0 / 4.0  =  78.0 	| 82.0
-> 2.0 * 9.0 - 100.0 / 4.0 - 10.0 + 75.0  =  58.0 	| 102.0
-> 100.0 / 4.0 + 10.0 / 2.0 * 9.0 - 75.0  =  -5.0 	| 165.0
-> 2.0 * 9.0 * 100.0 / 4.0 - 10.0 + 75.0  =  515.0 	| 355.0
-> ...
-> 9.0 * 75.0 + 100.0 / 4.0 + 10.0 + 2.0  =  712.0 	| 552.0
-> 2.0 * 9.0 * 75.0 - 100.0 / 4.0 + 10.0  =  1335.0 	| 1175.0
-> 9.0 * 100.0 / 4.0 * 10.0 + 75.0 + 2.0  =  2327.0 	| 2167.0
-> 9.0 * 75.0 * 100.0 / 4.0 + 10.0 + 2.0  =  16887.0 	| 16727.0
-> 2.0 * 9.0 * 75.0 * 100.0 / 4.0 - 10.0  =  33740.0 	| 33580.0
{
  "avg": 5495.7, 
  "min": 52.0, 
  "max": 33580.0, 
  "stdev": 10544.837808615172, 
  "q1": 102.0, 
  "med": 863.5, 
  "q3": 2167.0
}
-> 100.0 / 4.0 * 10.0 - 2.0 * 9.0 - 75.0  =  157.0 	| 3.0
-> 100.0 / 4.0 * 10.0 - 2.0 * 9.0 - 75.0  =  157.0 	| 3.0
-> 10.0 + 75.0 + 2.0 * 9.0 + 100.0 / 4.0  =  128.0 	| 32.0
-> 10.0 + 75.0 + 2.0 * 9.0 - 100.0 / 4.0  =  78.0 	| 82.0
-> 2.0 * 9.0 + 100.0 / 4.0 * 10.0 + 75.0  =  343.0 	| 183.0
-> ...
-> 10.0 / 2.0 * 9.0 - 75.0 - 100.0 / 4.0  =  -55.0 	| 215.0
-> 10.0 / 2.0 * 9.0 - 75.0 - 100.0 / 4.0  =  -55.0 	| 215.0
-> 2.0 * 9.0 - 100.0 / 4.0 * 10.0 + 75.0  =  -157.0 	| 317.0
-> 2.0 * 9.0 * 100.0 / 4.0 - 10.0 + 75.0  =  515.0 	| 355.0
-> 4.0 * 10.0 * 2.0 * 9.0 - 100.0 - 75.0  =  545.0 	| 385.0
{
  "avg": 179.0, 
  "min": 3.0, 
  "max": 385.0, 
  "stdev": 137.1254899717773, 
  "q1": 32.0, 
  "med": 215.0, 
  "q3": 317.0
}
-> 2.0 * 9.0 - 10.0 + 75.0 + 100.0 / 4.0  =  108.0 	| 52.0
-> 4.0 * 10.0 + 100.0 - 2.0 * 9.0 - 75.0  =  47.0 	| 113.0
-> 4.0 * 10.0 + 2.0 * 9.0 - 100.0 + 75.0  =  33.0 	| 127.0
-> 4.0 * 10.0 + 2.0 * 9.0 - 100.0 + 75.0  =  33.0 	| 127.0
-> 2.0 * 9.0 + 100.0 / 4.0 + 10.0 - 75.0  =  -22.0 	| 182.0
-> ...
-> 100.0 / 4.0 * 10.0 + 75.0 + 2.0 * 9.0  =  343.0 	| 183.0
-> 2.0 * 9.0 + 100.0 / 4.0 * 10.0 + 75.0  =  343.0 	| 183.0
-> 100.0 / 4.0 - 10.0 - 2.0 * 9.0 - 75.0  =  -78.0 	| 238.0
-> 100.0 / 4.0 * 10.0 / 2.0 * 9.0 - 75.0  =  1050.0 	| 890.0
-> 100.0 / 4.0 * 10.0 / 2.0 * 9.0 - 75.0  =  1050.0 	| 890.0
{
  "avg": 298.5, 
  "min": 52.0, 
  "max": 890.0, 
  "stdev": 299.60882830784544, 
  "q1": 127.0, 
  "med": 183.0, 
  "q3": 238.0
}
-> 4.0 * 10.0 / 2.0 * 9.0 - 75.0 + 100.0  =  205.0 	| 45.0
-> 4.0 * 10.0 / 2.0 * 9.0 - 75.0 + 100.0  =  205.0 	| 45.0
-> 2.0 * 9.0 - 10.0 + 75.0 + 100.0 / 4.0  =  108.0 	| 52.0
-> 2.0 * 9.0 * 10.0 + 75.0 + 100.0 / 4.0  =  280.0 	| 120.0
-> 2.0 * 9.0 * 10.0 + 75.0 + 100.0 / 4.0  =  280.0 	| 120.0
-> ...
-> 10.0 - 2.0 * 9.0 - 75.0 - 4.0 + 100.0  =  13.0 	| 147.0
-> 2.0 * 9.0 * 100.0 / 4.0 - 10.0 - 75.0  =  365.0 	| 205.0
-> 2.0 * 9.0 - 100.0 / 4.0 - 10.0 - 75.0  =  -92.0 	| 252.0
-> 10.0 - 2.0 * 9.0 - 75.0 * 4.0 + 100.0  =  -208.0 	| 368.0
-> 10.0 + 75.0 + 100.0 / 4.0 * 2.0 * 9.0  =  535.0 	| 375.0
{
  "avg": 172.9, 
  "min": 45.0, 
  "max": 375.0, 
  "stdev": 118.23573909778717, 
  "q1": 52.0, 
  "med": 176.0, 
  "q3": 252.0
}
-> 2.0 * 9.0 * 10.0 - 4.0 - 75.0 + 100.0  =  201.0 	| 41.0
-> 75.0 + 100.0 / 4.0 - 10.0 / 2.0 * 9.0  =  55.0 	| 105.0
-> 2.0 * 9.0 * 10.0 + 75.0 + 100.0 / 4.0  =  280.0 	| 120.0
-> 9.0 * 10.0 - 2.0 - 100.0 / 4.0 - 75.0  =  -12.0 	| 172.0
-> 100.0 / 4.0 - 10.0 - 75.0 - 2.0 * 9.0  =  -78.0 	| 238.0
-> ...
-> 100.0 / 4.0 - 2.0 * 9.0 * 10.0 + 75.0  =  -80.0 	| 240.0
-> 2.0 * 9.0 - 100.0 / 4.0 - 10.0 - 75.0  =  -92.0 	| 252.0
-> 100.0 / 4.0 - 10.0 / 2.0 * 9.0 - 75.0  =  -95.0 	| 255.0
-> 100.0 / 4.0 / 2.0 * 9.0 * 10.0 + 75.0  =  1200.0 	| 1040.0
-> 4.0 * 10.0 / 2.0 * 9.0 * 100.0 - 75.0  =  17925.0 	| 17765.0
{
  "avg": 2022.8, 
  "min": 41.0, 
  "max": 17765.0, 
  "stdev": 5254.132560185363, 
  "q1": 120.0, 
  "med": 246.0, 
  "q3": 255.0
}
-> 9.0 * 10.0 + 2.0 + 75.0 + 100.0 / 4.0  =  192.0 	| 32.0
-> 2.0 * 9.0 - 10.0 + 75.0 + 100.0 / 4.0  =  108.0 	| 52.0
-> 2.0 * 9.0 * 10.0 + 75.0 + 100.0 / 4.0  =  280.0 	| 120.0
-> 100.0 / 4.0 + 2.0 * 9.0 * 10.0 + 75.0  =  280.0 	| 120.0
-> 100.0 / 4.0 + 9.0 * 10.0 - 2.0 - 75.0  =  38.0 	| 122.0
-> ...
-> 10.0 / 2.0 * 9.0 - 75.0 + 100.0 / 4.0  =  -5.0 	| 165.0
-> 9.0 * 10.0 - 2.0 - 100.0 / 4.0 - 75.0  =  -12.0 	| 172.0
-> 100.0 / 4.0 - 10.0 / 2.0 * 9.0 - 75.0  =  -95.0 	| 255.0
-> 100.0 / 4.0 * 2.0 * 9.0 * 10.0 - 75.0  =  4425.0 	| 4265.0
-> 4.0 - 75.0 + 100.0 * 9.0 * 10.0 - 2.0  =  8927.0 	| 8767.0
{
  "avg": 1407.0, 
  "min": 32.0, 
  "max": 8767.0, 
  "stdev": 2746.329732570363, 
  "q1": 120.0, 
  "med": 168.5, 
  "q3": 255.0
}
-> 10.0 / 2.0 * 9.0 + 100.0 / 4.0 + 75.0  =  145.0 	| 15.0
-> 9.0 * 10.0 + 2.0 + 75.0 + 100.0 / 4.0  =  192.0 	| 32.0
-> 100.0 / 4.0 + 2.0 * 9.0 * 10.0 + 75.0  =  280.0 	| 120.0
-> 100.0 / 4.0 - 9.0 * 10.0 + 2.0 + 75.0  =  12.0 	| 148.0
-> 10.0 / 2.0 * 9.0 - 75.0 + 100.0 / 4.0  =  -5.0 	| 165.0
-> ...
-> 2.0 * 9.0 * 10.0 + 75.0 * 100.0 / 4.0  =  2055.0 	| 1895.0
-> 10.0 / 2.0 * 9.0 - 75.0 * 100.0 / 4.0  =  -1830.0 	| 1990.0
-> 100.0 / 4.0 * 9.0 * 10.0 + 2.0 + 75.0  =  2327.0 	| 2167.0
-> 100.0 / 4.0 * 9.0 * 10.0 + 2.0 + 75.0  =  2327.0 	| 2167.0
-> 100.0 / 4.0 * 2.0 * 9.0 * 10.0 + 75.0  =  4575.0 	| 4415.0
{
  "avg": 1311.4, 
  "min": 15.0, 
  "max": 4415.0, 
  "stdev": 1389.3238067491682, 
  "q1": 120.0, 
  "med": 1942.5, 
  "q3": 2167.0
}

In [ ]:


In [ ]: