Skip to content Skip to sidebar Skip to footer

Generating Evalable Python Code: All Combinations Of Functions In Disjunctive Normal Form

(A,B,C) = (100, 200, 300) def f1(p): return p+50 def f2(p): return p*1.5 def f3(p): return p*p vars_ = (A,B,C) funcs_ = [f1, f2, f3] logic_ = ['and','or'] vol_lmt_ = [200, 300

Solution 1:

This is equivalent to taking the outer/cartesian product, "summing" across the "var" dimension, and interspersing those with the outer product of logic operators. You can use itertools.product or just the normal list comprehensions. The following will work for any number of variables, functions, comparators, logic operators, and numeric thresholds. It is also easily extensible if you choose to make more complicated expressions:

#!/usr/bin/python3

from pprint import pprint as pp
from itertools import *

VARS = 'XYZ'
FUNCS = range(2)
COMPARE = '><='
LOGIC = ['and', 'or']
NUMS = [200, 300]

def listJoin(iter):
    return sum(map(list,iter), [])

terms = [
    [
         'func[{func}]({var}){compare}{num}'.format(func=func, var=var, compare=compare, num=num)
         for var in VARS
    ]
    for func in FUNCS
    for compare in COMPARE
    for num in NUMS
]

def intersperse(iter, joiners):
    iter = list(iter)
    for tokens in product(*(joiners for _ in iter[:-1])):
        yield ' '.join(listJoin(zip(iter,tokens))+[iter[-1]])

formulas = listJoin(intersperse(t, LOGIC) for t in terms)

pp(formulas)

Result:

['func[0](X)>200 and func[0](Y)>200 and func[0](Z)>200',                                                                                               
 'func[0](X)>200 and func[0](Y)>200 or func[0](Z)>200',
 'func[0](X)>200 or func[0](Y)>200 and func[0](Z)>200',
 'func[0](X)>200 or func[0](Y)>200 or func[0](Z)>200',
 'func[0](X)>300 and func[0](Y)>300 and func[0](Z)>300',
 'func[0](X)>300 and func[0](Y)>300 or func[0](Z)>300',
 'func[0](X)>300 or func[0](Y)>300 and func[0](Z)>300',
 'func[0](X)>300 or func[0](Y)>300 or func[0](Z)>300',
 'func[0](X)<200 and func[0](Y)<200 and func[0](Z)<200',
 'func[0](X)<200 and func[0](Y)<200 or func[0](Z)<200',
 'func[0](X)<200 or func[0](Y)<200 and func[0](Z)<200',
 'func[0](X)<200 or func[0](Y)<200 or func[0](Z)<200',
 'func[0](X)<300 and func[0](Y)<300 and func[0](Z)<300',
 'func[0](X)<300 and func[0](Y)<300 or func[0](Z)<300',
 'func[0](X)<300 or func[0](Y)<300 and func[0](Z)<300',
 'func[0](X)<300 or func[0](Y)<300 or func[0](Z)<300',
 'func[0](X)=200 and func[0](Y)=200 and func[0](Z)=200',
 'func[0](X)=200 and func[0](Y)=200 or func[0](Z)=200',
 'func[0](X)=200 or func[0](Y)=200 and func[0](Z)=200',
 'func[0](X)=200 or func[0](Y)=200 or func[0](Z)=200',
 'func[0](X)=300 and func[0](Y)=300 and func[0](Z)=300',
 'func[0](X)=300 and func[0](Y)=300 or func[0](Z)=300',
 'func[0](X)=300 or func[0](Y)=300 and func[0](Z)=300',
 'func[0](X)=300 or func[0](Y)=300 or func[0](Z)=300',
 'func[1](X)>200 and func[1](Y)>200 and func[1](Z)>200',
 'func[1](X)>200 and func[1](Y)>200 or func[1](Z)>200',
 'func[1](X)>200 or func[1](Y)>200 and func[1](Z)>200',
 'func[1](X)>200 or func[1](Y)>200 or func[1](Z)>200',
 'func[1](X)>300 and func[1](Y)>300 and func[1](Z)>300',
 'func[1](X)>300 and func[1](Y)>300 or func[1](Z)>300',
 'func[1](X)>300 or func[1](Y)>300 and func[1](Z)>300',
 'func[1](X)>300 or func[1](Y)>300 or func[1](Z)>300',
 'func[1](X)<200 and func[1](Y)<200 and func[1](Z)<200',
 'func[1](X)<200 and func[1](Y)<200 or func[1](Z)<200',
 'func[1](X)<200 or func[1](Y)<200 and func[1](Z)<200',
 'func[1](X)<200 or func[1](Y)<200 or func[1](Z)<200',
 'func[1](X)<300 and func[1](Y)<300 and func[1](Z)<300',
 'func[1](X)<300 and func[1](Y)<300 or func[1](Z)<300',
 'func[1](X)<300 or func[1](Y)<300 and func[1](Z)<300',
 'func[1](X)<300 or func[1](Y)<300 or func[1](Z)<300',
 'func[1](X)=200 and func[1](Y)=200 and func[1](Z)=200',
 'func[1](X)=200 and func[1](Y)=200 or func[1](Z)=200',
 'func[1](X)=200 or func[1](Y)=200 and func[1](Z)=200',
 'func[1](X)=200 or func[1](Y)=200 or func[1](Z)=200',
 'func[1](X)=300 and func[1](Y)=300 and func[1](Z)=300',
 'func[1](X)=300 and func[1](Y)=300 or func[1](Z)=300',
 'func[1](X)=300 or func[1](Y)=300 and func[1](Z)=300',
 'func[1](X)=300 or func[1](Y)=300 or func[1](Z)=300']

Solution 2:

Maybe this can summaries what you are trying to do (using python2 syntax):

import itertools

arguments = ('A', 'B', 'C', 'D')
funcs_ = [f1, f2, f3, f4]
logic_ = ["and","or"]
op_ = [">","<","="]
vol_lmt_ = [200, 300]

num_func = len(funcs_)

assert num_func == len(arguments), ("The number of argument should be the same as "
                                    "the number of function.")

operands = itertools.product(["funcs_[%d]" % i for i in range(num_func)],
                             arguments,
                             op_,
                             vol_lmt_)

def comp(operands):
    templ = "{func}({arg}){op}{val}"
    for operand in operands:
        yield templ.format(func=operand[0], arg=operand[1],
                           op=operand[2], val=operand[3])

new_operands = map(comp, itertools.tee(operands, num_func))

# construct the argument to pass to itertools.product.
args = []
for operand in new_operands:
    args.append(operand)
    args.append(logic_)

args.pop() # Remove the last logic operator.

res = itertools.product(*args)

print " ".join(res.next())
# funcs_[0](A)>200 and funcs_[0](A)>200 and funcs_[0](A)>200 and funcs_[0](A)>200

...

In this method i just cheated by replacing vars_ with ('A', 'B', 'C'). beside that i think it should work.


If you don't like my way of cheating by hard coding the the vars_ list and the funcs_ name you can get the name of your variable from the globals dictionary something like this:

def get_name(obj):
    """Get the name of an object (variable) from the globals dict.

    Argument:
       - obj : The variable that we want to get the name of. 

    Return:
       - A string representing the name of the object if it was found else return None.

    """

    for name, value in globals().items():
         if value is obj:
             return name

Solution 3:

First of all, I would start by saying eval is a bad idea.There is always another way to do it.

Answer to your question:

Question 1:

Function name,

You can get using f.func_name.

Variable name,

Not so simple,but this should work,

import gc, sys

def find_names(obj):
    frame = sys._getframe()
    for frame in iter(lambda: frame.f_back, None):
        frame.f_locals
    result = []
    for referrer in gc.get_referrers(obj):
        if isinstance(referrer, dict):
            for k, v in referrer.iteritems():
                if v is obj:
                    result.append(k)
    return result

a = 97
print find_names(a)[0]

It doesn't matter if it returns the wrong name since their value will be equal.

The operator and vol_limit is easily generated.

2nd Question.

No obvious solution out of mind. Iterate through all of them?

3rd Question.

Yes it is possible. Check out Decorators.


Post a Comment for "Generating Evalable Python Code: All Combinations Of Functions In Disjunctive Normal Form"