Python中的数学方程操作
我想开发一个图形界面应用程序,用来显示一个数学方程。当你点击方程中的某个变量,表示这个变量是未知数,也就是需要计算的内容时,方程会自动调整,来计算这个未知数。
举个例子:
a = (b+c*d)/e
假设我点击了“d”,表示它是未知数。那么方程应该重新调整为:
d = (a*e - b)/c
目前,我只想知道如何根据用户的输入来重新排列这个方程。我哥哥给我的一个建议是,在后台使用前缀/后缀表示法来进行计算。
这是不是唯一的方法,还是有其他更简单的建议呢?另外,我不仅会使用基本的数学函数,还会用到三角函数和微积分(我觉得是基础的,不涉及偏微分之类的)。我觉得前缀/后缀表示法可能在处理更复杂的数学函数时不太有用。
不过这只是我的看法,如果我错了请指出来。另外,我会使用SymPy来进行数学计算,所以计算一个给定的数学方程不是问题,我主要的问题是如何从一个通用的方程创建一个特定的方程。
5 个回答
自从2009年以来,事情确实发生了很大变化。我不知道你的图形用户界面(GUI)应用程序进展得怎么样,但现在在IPython qtconsole中可以直接做到这一点(你可以把它嵌入到自定义的PyQt/PySide应用程序中,并跟踪所有定义的符号,以便在一个单独的列表框中进行GUI交互等等)。
(使用了IPython的 sympyprt
扩展)
如果你想要不依赖任何库,直接实现这个功能,我觉得你遇到的问题可能和Python本身没有关系。如果你想找到这样的方程,你需要描述解决这些方程所需的经验法则。
首先,你需要表示你的方程。可以考虑把它分开:
- 操作数:
- 符号操作数(比如a, b)
- 数字操作数(比如1, 2)
- 运算符:
- 一元运算符(比如负号-,三角函数)
- 二元运算符(比如加法+,减法-,乘法*,除法/)
一元运算符显然只需要一个操作数,而二元运算符则需要两个。
那类型呢?
我觉得所有这些组成部分应该都来自一个共同的expression
类型。这个类应该有一个getsymbols
方法,用来快速找到你表达式中的符号。
然后区分一元和二元运算符,添加一些基本的补充/重排序的功能……
大概是这样的:
class expression(object):
def symbols(self):
if not hasattr(self, '_symbols'):
self._symbols = self._getsymbols()
return self._symbols
def _getsymbols(self):
"""
return type: list of strings
"""
raise NotImplementedError
class operand(expression): pass
class symbolicoperand(operand):
def __init__(self, name):
self.name = name
def _getsymbols(self):
return [self.name]
def __str__(self):
return self.name
class numericoperand(operand):
def __init__(self, value):
self.value = value
def _getsymbols(self):
return []
def __str__(self):
return str(self.value)
class operator(expression): pass
class binaryoperator(operator):
def __init__(self, lop, rop):
"""
@type lop, rop: expression
"""
self.lop = lop
self.rop = rop
def _getsymbols(self):
return self.lop._getsymbols() + self.rop._getsymbols()
@staticmethod
def complementop():
"""
Return complement operator:
op.complementop()(op(a,b), b) = a
"""
raise NotImplementedError
def reorder():
"""
for op1(a,b) return op2(f(b),g(a)) such as op1(a,b) = op2(f(a),g(b))
"""
raise NotImplementedError
def _getstr(self):
"""
string representing the operator alone
"""
raise NotImplementedError
def __str__(self):
lop = str(self.lop)
if isinstance(self.lop, operator):
lop = '(%s)' % lop
rop = str(self.rop)
if isinstance(self.rop, operator):
rop = '(%s)' % rop
return '%s%s%s' % (lop, self._getstr(), rop)
class symetricoperator(binaryoperator):
def reorder(self):
return self.__class__(self.rop, self.lop)
class asymetricoperator(binaryoperator):
@staticmethod
def _invert(operand):
"""
div._invert(a) -> 1/a
sub._invert(a) -> -a
"""
raise NotImplementedError
def reorder(self):
return self.complementop()(self._invert(self.rop), self.lop)
class div(asymetricoperator):
@staticmethod
def _invert(operand):
if isinstance(operand, div):
return div(self.rop, self.lop)
else:
return div(numericoperand(1), operand)
@staticmethod
def complementop():
return mul
def _getstr(self):
return '/'
class mul(symetricoperator):
@staticmethod
def complementop():
return div
def _getstr(self):
return '*'
class add(symetricoperator):
@staticmethod
def complementop():
return sub
def _getstr(self):
return '+'
class sub(asymetricoperator):
@staticmethod
def _invert(operand):
if isinstance(operand, min):
return operand.op
else:
return min(operand)
@staticmethod
def complementop():
return add
def _getstr(self):
return '-'
class unaryoperator(operator):
def __init__(self, op):
"""
@type op: expression
"""
self.op = op
@staticmethod
def complement(expression):
raise NotImplementedError
def _getsymbols(self):
return self.op._getsymbols()
class min(unaryoperator):
@staticmethod
def complement(expression):
if isinstance(expression, min):
return expression.op
else:
return min(expression)
def __str__(self):
return '-' + str(self.op)
有了这个基本结构后,你应该能够描述一个简单的经验法则来解决非常简单的方程。想想你学过的解方程的简单规则,把它们写下来。这样应该就能行 :)
然后是一个非常简单的求解器:
def solve(left, right, symbol):
"""
@type left, right: expression
@type symbol: string
"""
if symbol not in left.symbols():
if symbol not in right.symbols():
raise ValueError('%s not in expressions' % symbol)
left, right = right, left
solved = False
while not solved:
if isinstance(left, operator):
if isinstance(left, unaryoperator):
complementor = left.complement
right = complementor(right)
left = complementor(left)
elif isinstance(left, binaryoperator):
if symbol in left.rop.symbols():
left = left.reorder()
else:
right = left.complementop()(right, left.rop)
left = left.lop
elif isinstance(left, operand):
assert isinstance(left, symbolicoperand)
assert symbol==left.name
solved = True
print symbol,'=',right
a,b,c,d,e = map(symbolicoperand, 'abcde')
solve(a, div(add(b,mul(c,d)),e), 'd') # d = ((a*e)-b)/c
solve(numericoperand(1), min(min(a)), 'a') # a = 1
使用SymPy,你的例子可以这样写:
>>> import sympy
>>> a,b,c,d,e = sympy.symbols('abcde')
>>> r = (b+c*d)/e
>>> l = a
>>> r = sympy.solve(l-r,d)
>>> l = d
>>> r
[(-b + a*e)/c]
>>>
这似乎也适用于三角函数:
>>> l = a
>>> r = b*sympy.sin(c)
>>> sympy.solve(l-r,c)
[asin(a/b)]
>>>
而且因为你在使用图形用户界面(GUI),你可能想要在字符串和表达式之间来回转换:
>>> r = '(b+c*d)/e'
>>> sympy.sympify(r)
(b + c*d)/e
>>> sympy.sstr(_)
'(b + c*d)/e'
>>>
或者你可能更喜欢将它们以渲染后的LaTeX或MathML格式显示。