我要构建一个通过构造函数方法接受一系列输入的类,然后使用这些参数使用calculate()
执行计算。这里的技巧是这些参数有时可能可用,而其他时候可能不可用。但是,变量之间存在一个给定的方程式,因此可以从方程式中计算出缺失的方程式。这是一个示例:
我知道:
a = b * c - d
c = e/f
我要始终计算a+b+c+d+e+f
这是我到目前为止所拥有的:
class Calculation:
def __init__(self, **kwargs):
for parameter, value in kwargs.items():
setattr(self, '_'.format(parameter), value)
@property
def a(self):
try:
return self._a
except AttributeError:
return self._b * self._c - self._d
@property
def b(self):
try:
return self._b
except AttributeError:
return (self._a + self._d) / self._c
... // same for all a,b,c,d,e,f
def calculate(self):
return sum(self.a+self.b+self.c+self.d+self.e+self.f)
然后用作:
c = Calculation(e=4,f=6,b=7,d=2)
c.calculate()
但是,其他时间可能还有其他变量,例如: c =计算(b = 5,c = 6,d = 7,e = 3,f = 6) c.calculate()
我的问题是:在我的案例中使用哪种好的设计模式?到目前为止,为所有变量制作一个@property
似乎有点多余。它必须解决的问题是接受任何变量(可以进行最小计算),并根据我拥有的方程式,找出计算所需的其余部分。
答案 0 :(得分:1)
只需预先计算__init__
中的缺失值(并且由于您知道5个值是什么,所以要明确,而不要尝试使用kwargs
压缩代码):
# Note: Make all 6 keyword-only arguments
def __init__(self, *, a=None, b=None, c=None, d=None, e=None, f=None):
if a is None:
a = b * c - d
if c is None:
c = e / f
self.sum = a + b + c + d + e + f
def calculate(self):
return self.sum
答案 1 :(得分:1)
这似乎是 getattr 函数的不错选择。您可以将关键字参数直接存储在该类中,并使用该字典返回已知参数作为属性,或者根据您知道的其他公式“即时”推断出未指定的值:
class Calculation:
def __init__(self, **kwargs):
self.params = kwargs
self.inferred = {
"a" : lambda: self.b * self.c - self.d,
"c" : lambda: self.e / self.f,
"result": lambda: self.a+self.b+self.c+self.d+self.e+self.f
}
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred:
value = self.inferred[name]()
self.params[name] = value
return value
r = Calculation(b=1,d=3,e=45,f=9).result
print(r) # 65.0 (c->45/9->5, a->1*5-3->2)
请注意,如果您对某些参数进行了非常复杂的计算,则可以使用该类的函数作为self.inferred词典中lambda的实现。
如果要在许多公式中使用此模式,则可能需要将样板代码集中在基类中。这将使新的计算类所需的工作减少为仅需实现inferred()函数。
class SmartCalc:
def __init__(self, **kwargs):
self.params = kwargs
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred():
value = self.inferred()[name]()
self.params[name] = value
return value
class Calculation(SmartCalc):
def inferred(self):
return {
"a" : lambda: self.b * self.c - self.d,
"b" : lambda: (self.a+self.d)/self.c,
"c" : lambda: self.e / self.f,
"d" : lambda: self.c * self.b - self.a,
"e" : lambda: self.f * self.c,
"f" : lambda: self.e / self.c,
"result": lambda: self.a+self.b+self.c+self.d+self.e+self.f
}
inferred()中有足够的内容,您甚至可以使用此方法从其他方法的组合中获取任何值:
valueF = Calculation(a=2,b=1,c=5,d=3,e=45,result=65).f
print(valueF) # 9.0
编辑
如果要使其更加复杂,可以改进 getattr 以允许在inferred()词典中指定依赖项。
例如:
class SmartCalc:
def __init__(self, **kwargs):
self.params = kwargs
def __getattr__(self, name):
if name in self.params:
return self.params[name]
if name in self.inferred():
calc = self.inferred()[name]
if isinstance(calc,dict):
for names,subCalc in calc.items():
if isinstance(names,str): names = [names]
if all(name in self.params for name in names):
calc = subCalc; break
value = calc()
self.params[name] = value
return value
import math
class BodyMassIndex(SmartCalc):
def inferred(self):
return {
"heightM" : { "heightInches": lambda: self.heightInches * 0.0254,
("bmi","weightKg"): lambda: math.sqrt(self.weightKg/self.bmi),
("bmi","weightLb"): lambda: math.sqrt(self.weightKg/self.bmi)
},
"heightInches" : lambda: self.heightM / 0.0254,
"weightKg" : { "weightLb": lambda: self.weightLb / 2.20462,
("bmi","heightM"): lambda: self.heightM**2*self.bmi,
("bmi","heightInches"): lambda: self.heightM**2*self.bmi
},
"weightLb" : lambda: self.weightKg * 2.20462,
"bmi" : lambda: self.weightKg / (self.heightM**2)
}
bmi = BodyMassIndex(heightM=1.75,weightKg=130).bmi
print(bmi) # 42.44897959183673
height = BodyMassIndex(bmi=42.45,weightKg=130).heightInches
print(height) # 68.8968097135968 (1.75 Meters)
EDIT2
可以设计一个类似的类来处理表示为文本的公式。这将允许使用牛顿-拉普森迭代逼近(至少对于1次多项式方程式)的基本形式的项求解器:
class SmartFormula:
def __init__(self, **kwargs):
self.params = kwargs
self.moreToSolve = True
self.precision = 0.000001
self.maxIterations = 10000
def __getattr__(self, name):
self.resolve()
if name in self.params: return self.params[name]
def resolve(self):
while self.moreToSolve:
self.moreToSolve = False
for formula in self.formulas():
param = formula.split("=",1)[0].strip()
if param in self.params: continue
if "?" in formula:
self.useNewtonRaphson(param)
continue
try:
exec(formula,globals(),self.params)
self.moreToSolve = True
except: pass
def useNewtonRaphson(self,name):
for formula in self.formulas():
source,calc = [s.strip() for s in formula.split("=",1)]
if name not in calc: continue
if source not in self.params: continue
simDict = self.params.copy()
target = self.params[source]
value = target
try:
for _ in range(self.maxIterations):
simDict[name] = value
exec(formula,globals(),simDict)
result = simDict[source]
resultDelta = target-result
value += value*resultDelta/result/2
if abs(resultDelta) < self.precision/2 :
self.params[name] = round(simDict[name]/self.precision)*self.precision
self.moreToSolve = True
return
except: continue
通过这种方法,BodyMassIndex计算器将更易于阅读:
import math
class BodyMassIndex(SmartFormula):
def formulas(self):
return [
"heightM = heightInches * 0.0254",
"heightM = ?", # use Newton-Raphson solver.
"heightInches = ?",
"weightKg = weightLb / 2.20462",
"weightKg = heightM**2*bmi",
"weightLb = ?",
"bmi = weightKg / (heightM**2)"
]
这可以让您获取/使用列表中未明确说明其计算公式的术语(例如,根据heightM来计算的heightInches是根据bmi和weightKg来计算的):
height = BodyMassIndex(bmi=42.45,weightKg=130).heightInches
print(height) # 68.8968097135968 (1.75 Meters)
注:公式以文本形式表示,并使用eval()执行,该方法可能比其他解决方案慢得多。此外,Newton-Raphson算法适用于线性方程,但对于正斜率和负斜率混合的曲线也有其局限性。例如,我必须包括weightKg = heightM**2*bmi
公式,因为基于weightKg
获得bmi = weightKg/(heightM**2)
需要解决一个Newton-Raphson似乎无法处理的y = 1/x^2
方程。 / em>
以下是使用您原始问题的示例:
class OP(SmartFormula):
def formulas(self):
return [
"a = b * c - d",
"b = ?",
"c = e/f",
"d = ?",
"e = ?",
"f = ?",
"result = a+b+c+d+e+f"
]
r = OP(b=1,d=3,e=45,f=9).result
print(r) # 65.0
f = OP(a=2,c=5,d=3,e=45,result=65).f
print(f) # 9.0
class ABCD(SmartFormula):
def formulas(self) : return ["a=b+c*d","b=?","c=?","d=?"]
@property
def someProperty(self): return "Found it!"
abcd = ABCD(a=5,b=2,c=3)
print(abcd.d) # 1.0
print(abcd.someProperty) # Found it!
print(abcd.moreToSolve) # False
答案 2 :(得分:1)
[补充上一个答案的新答案]
我觉得我的答案太大了,所以我将这一改进的解决方案添加到单独的解决方案中。
这是简单方程的基本代数求解器,它将为输入方程的不同项输出赋值语句:
例如:
solveFor("d","a=b+c/d") # --> 'd=c/(a-b)'
使用此功能,可以在尝试转换为Newton-Raphson之前尝试使用代数来进一步改进SmartFormula类。当方程对于resolveFor()函数足够简单时,这将提供更可靠的结果。
solveFor()函数可以对公式中仅出现一次的任何项求解方程式。只要要求解的组件仅与基本运算(+,-,*,/,**)相关,它将“理解”计算。括号中不包含目标词的任何基团都将按“原样”处理,而无需进一步解释。这样,您可以将复杂的函数/运算符放在括号中,以便即使存在这些特殊计算也可以解决其他术语。
import re
from itertools import accumulate
def findGroups(expression):
levels = list(accumulate(int(c=="(")-int(c==")") for c in expression))
groups = "".join([c,"\n"][lv==0] for c,lv in zip(expression,levels)).split("\n")
groups = [ g+")" for g in groups if g ]
return sorted(groups,key=len,reverse=True)
functionMap = [("sin","asin"),("cos","acos"),("tan","atan"),("log10","10**"),("exp","log")]
functionMap += [ (b,a) for a,b in functionMap ]
def solveFor(term,equation):
equation = equation.replace(" ","").replace("**","†")
termIn = re.compile(f"(^|\\W){term}($|\\W)")
if len(termIn.findall(equation)) != 1: return None
left,right = equation.split("=",1)
if termIn.search(right): left,right = right,left
groups = { f"#{i}#":group for i,group in enumerate(findGroups(left)) }
for gid,group in groups.items(): left = left.replace(group,gid)
termGroup = next((gid for gid,group in groups.items() if termIn.search(group)),"##" )
def moveTerms(leftSide,rightSide,oper,invOper):
keepLeft = None
for i,x in enumerate(leftSide.split(oper)):
if termGroup in x or termIn.search(x):
keepLeft = x; continue
x = x or "0"
if any(op in x for op in "+-*/"): x = "("+x+")"
rightSide = invOper[i>0].replace("{r}",rightSide).replace("{x}",x)
return keepLeft, rightSide
def moveFunction(leftSide,rightSide,func,invFunc):
fn = leftSide.split("#",1)[0]
if fn.split(".")[-1] == func:
return leftSide[len(fn):],fn.replace(func,invFunc)
return leftSide,rightSide
left,right = moveTerms(left,right,"+",["{r}-{x}"]*2)
left,right = moveTerms(left,right,"-",["{x}-{r}","{r}+{x}"])
left,right = moveTerms(left,right,"*",["({r})/{x}"]*2)
left,right = moveTerms(left,right,"/",["{x}/({r})","({r})*{x}"])
left,right = moveTerms(left,right,"†",["log({r})/log({x})","({r})†(1/{x})"])
for func,invFunc in functionMap:
left,right = moveFunction(left,right,func,f"{invFunc}({right})")
for sqrFunc in ["math.sqrt","sqrt"]:
left,right = moveFunction(left,right,sqrFunc,f"({right})**2")
for gid,group in groups.items(): right = right.replace(gid,group)
if left == termGroup:
subEquation = groups[termGroup][1:-1]+"="+right
return solveFor(term,subEquation)
if left != term: return None
solution = f"{left}={right}".replace("†","**")
# expression clen-up
solution = re.sub(r"(?<!\w)(0\-)","-",solution)
solution = re.sub(r"1/\(1/(\w)\)",r"\g<1>",solution)
solution = re.sub(r"\(\(([^\(]*)\)\)",r"(\g<1>)",solution)
solution = re.sub(r"(?<!\w)\((\w*)\)",r"\g<1>",solution)
return solution
示例用法:
solveFor("x","y=(a+b)*x-(math.sin(1.5)/322)") # 'x=(y+(math.sin(1.5)/322))/(a+b)'
solveFor("a","q=(a**2+b**2)*(c-d)**2") # 'a=(q/(c-d)**2-b**2)**(1/2)'
solveFor("a","c=(a**2+b**2)**(1/2)") # 'a=(c**2-b**2)**(1/2)'
solveFor("a","x=((a+b)*c-d)*(23+y)") # 'a=(x/(23+y)+d)/c-b'
sa = solveFor("a","y=-sin((x)-sqrt(a))") # 'a=(x-asin(-y))**2'
sx = solveFor("x",sa) # 'x=a**(1/2)+asin(-y)'
sy = solveFor("y",sx) # 'y=-sin(x-a**(1/2))'
请注意,您可能在那里可以找到更好的代数“求解器”,这只是一个简单/幼稚的解决方案。
这是SmartFormula类的改进版本,该类使用solveFor()在尝试转换为牛顿-拉夫森近似值之前尝试代数求解:
class SmartFormula:
def __init__(self, **kwargs):
self.params = kwargs
self.precision = 0.000001
self.maxIterations = 10000
self._formulas = [(f.split("=",1)[0].strip(),f) for f in self.formulas()]
terms = set(term for _,f in self._formulas for term in re.findall(r"\w+\(?",f) )
terms = [ term for term in terms if "(" not in term and not term.isdigit() ]
self._formulas += [ (term,f"{term}=solve('{term}')") for term in terms]
self(**kwargs)
def __getattr__(self, name):
if name in self.params: return self.params[name]
def __call__(self, **kwargs):
self.params = kwargs
self.moreToSolve = True
self.params["solve"] = lambda n: self.autoSolve(n)
self.resolve()
return self.params.get(self._formulas[0][0],None)
def resolve(self):
while self.moreToSolve:
self.moreToSolve = False
for param,formula in self._formulas:
if self.params.get(param,None) is not None: continue
try:
exec(formula,globals(),self.params)
if self.params.get(param,None) is not None:
self.moreToSolve = True
except: pass
def autoSolve(self, name):
for resolver in [self.algebra, self.newtonRaphson]:
for source,formula in self._formulas:
if self.params.get(source,None) is None:
continue
if not re.search(f"(^|\\W){name}($|\\W)",formula):
continue
resolver(name,source,formula)
if self.params.get(name,None) is not None:
return self.params[name]
def algebra(self, name, source, formula):
try: exec(solveFor(name,formula),globals(),self.params)
except: pass
def newtonRaphson(self, name, source,formula):
simDict = self.params.copy()
target = self.params[source]
value = target
for _ in range(self.maxIterations):
simDict[name] = value
try: exec(formula,globals(),simDict)
except: break
result = simDict[source]
resultDelta = target-result
if abs(resultDelta) < self.precision :
self.params[name] = round(value/self.precision/2)*self.precision*2
return
value += value*resultDelta/result/2
这使示例类(BodyMassIndex)可以避免指定"weightKg = heightM**2*bmi"
计算,因为代数求解器可以解决该问题。改进的类还消除了指示自动求解术语名称(“ term =?”)的需要。
import math
class BodyMassIndex(SmartFormula):
def formulas(self):
return [
"bmi = weightKg / (heightM**2)",
"heightM = heightInches * 0.0254",
"weightKg = weightLb / 2.20462"
]
bmi = BodyMassIndex()
print("bmi",bmi(heightM=1.75,weightKg=130)) # 42.44897959183673
print("weight",bmi.weightLb) # 286.6006 (130 Kg)
bmi(bmi=42.45,weightKg=130)
print("height",bmi.heightInches) # 68.8968097135968 (1.75 Meters)
对于原始问题,这很简单:
class OP(SmartFormula):
def formulas(self):
return [
"result = a+b+c+d+e+f",
"a = b * c - d",
"c = e/f"
]
r = OP(b=1,d=3,e=45,f=9).result
print(r) # 65.0
f = OP(a=2,c=5,d=3,e=45,result=65).f
print(f) # 9.0
在这些计算中均未使用Newton-Raphson,因为代数在尝试逼近之前优先解决它们