Generating evalable python code: all combinations of functions in disjunctive normal form

|▌冷眼眸甩不掉的悲伤 提交于 2020-01-24 09:39:25

问题


(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]
op_ = [">","<","="] 

I want generate the assert code string for eval() to test the validity, take below one for example:

"f1(A)>200 and f1(B)>200 and f1(C)>200"                             # True
 -^-------------^-------------^------------: funcs_
 ----^-------------^-------------^---------: vars_
 ------^-------------^-------------^-------: op_
 --------^-------------^-------------^-----: vol_lmt_        
 ------------^-------------^---------------: logic_

My questions are:

  1. how to generate the code string I wanted based on those vars above?

  2. how to enumerate all test logic possibility for above (A,B,C)? For example:

    "f1(A)>200 and f1(B)>200 and f1(C)>200"
    "f1(A)<300 and f2(B)=200 or f3(C)>200"

  3. is it possible to replace the the name of function to the list entry when generate the code?

    "f(A)>200 and f1(B)>200 and f1(C)>200"

    To

    "funcs_[0](A)>200 and funcs_[0](B)>200 and funcs_[0](C)>200"


回答1:


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



回答2:


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']



回答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.



来源:https://stackoverflow.com/questions/6224474/generating-evalable-python-code-all-combinations-of-functions-in-disjunctive-no

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!