有没有方法自动生成有效的算术表达式?
我现在正在尝试创建一个Python脚本,自动生成有效的用空格分隔的算术表达式。不过,我得到的样本输出看起来像这样:( 32 - 42 / 95 + 24 ( ) ( 53 ) + ) 21
虽然空的括号对我来说没问题,但我不能用这个自动生成的表达式进行计算,因为在24和53之间没有运算符,而且在最后的21前面的加号也没有第二个操作数。
我想知道,是否有办法用Python的方式来处理或修复这些错误?(在有人指出之前,我会首先承认我下面发布的代码可能是我写过的最糟糕的代码,并且符合……嗯,Python的核心原则很少。)
import random
parentheses = ['(',')']
ops = ['+','-','*','/'] + parentheses
lines = 0
while lines < 1000:
fname = open('test.txt','a')
expr = []
numExpr = lines
if (numExpr % 2 == 0):
numExpr += 1
isDiv = False # Boolean var, makes sure there's no Div by 0
# isNumber, isParentheses, isOp determine whether next element is a number, parentheses, or operator, respectively
isNumber = random.randint(0,1) == 0 # determines whether to start sequence with number or parentheses
isParentheses = not isNumber
isOp = False
# Counts parentheses to ensure parentheses are matching
numParentheses = 0
while (numExpr > 0 or numParentheses > 0):
if (numExpr < 0 and numParentheses > 0):
isDiv = False
expr.append(')')
numParentheses -= 1
elif (isOp and numParentheses > 0):
rand = random.randint(0,5)
expr.append(ops[rand])
isDiv = (rand == 3) # True if div op was just appended
# Checks to see if ')' was appended
if (rand == 5):
isNumber = False
isOp = True
numParentheses -= 1
# Checks to see if '(' was appended
elif (rand == 4):
isNumber = True
isOp = False
numParentheses += 1
# All other operations go here
else:
isNumber = True
isOp = False
# Didn't add parentheses possibility here in case expression in parentheses somehow reaches 0
elif (isNumber and isDiv):
expr.append(str(random.randint(1,100)))
isDiv = False
isNumber = False
isOp = True
# If a number's up, decides whether to append parentheses or a number
elif (isNumber):
rand = random.randint(0,1)
if (rand == 0):
expr.append(str(random.randint(0,100)))
isNumber = False
isOp = True
elif (rand == 1):
if (numParentheses == 0):
expr.append('(')
numParentheses += 1
else:
rand = random.randint(0,1)
expr.append(parentheses[rand])
if rand == 0:
numParentheses += 1
else:
numParentheses -= 1
isDiv = False
numExpr -= 1
fname.write(' '.join(expr) + '\n')
fname.close()
lines += 1
6 个回答
其实,只要Ray Toal的回答在形式上是正确的,对于这么简单的问题,你不需要为每个操作符都创建一个子类。我想出了以下代码,效果还不错:
import random
import math
class Expression(object):
OPS = ['+', '-', '*', '/']
GROUP_PROB = 0.3
MIN_NUM, MAX_NUM = 0, 20
def __init__(self, maxNumbers, _maxdepth=None, _depth=0):
"""
maxNumbers has to be a power of 2
"""
if _maxdepth is None:
_maxdepth = math.log(maxNumbers, 2) - 1
if _depth < _maxdepth and random.randint(0, _maxdepth) > _depth:
self.left = Expression(maxNumbers, _maxdepth, _depth + 1)
else:
self.left = random.randint(Expression.MIN_NUM, Expression.MAX_NUM)
if _depth < _maxdepth and random.randint(0, _maxdepth) > _depth:
self.right = Expression(maxNumbers, _maxdepth, _depth + 1)
else:
self.right = random.randint(Expression.MIN_NUM, Expression.MAX_NUM)
self.grouped = random.random() < Expression.GROUP_PROB
self.operator = random.choice(Expression.OPS)
def __str__(self):
s = '{0!s} {1} {2!s}'.format(self.left, self.operator, self.right)
if self.grouped:
return '({0})'.format(s)
else:
return s
for i in range(10):
print Expression(4)
不过,这段代码还可以改进,比如要考虑到除以零的情况(目前没有处理),通过属性自定义所有参数,允许maxNumbers
参数接受任何值等等。
* 这里说的“简单问题”是指“生成有效的表达式”;如果你要添加其他功能(比如表达式求值),那么Ray的方法会更有用,因为你可以更清晰地定义每个子类的行为。
编辑(输出):
(5 * 12 / 16)
6 * 3 + 14 + 0
13 + 15 - 1
19 + (8 / 8)
(12 + 3 - 5)
(4 * 0 / 4)
1 - 18 / (3 * 15)
(3 * 16 + 3 * 1)
(6 + 16) / 16
(8 * 10)
我在网上找到一个类似的讨论,主要是为了生成随机表达式,用于符号计算的单元测试。在我的版本中,我加入了一些一元函数,并且允许符号是任意字符串,也就是说,你可以使用数字或者变量名。
from random import random, choice
UNARIES = ["sqrt(%s)", "exp(%s)", "log(%s)", "sin(%s)", "cos(%s)", "tan(%s)",
"sinh(%s)", "cosh(%s)", "tanh(%s)", "asin(%s)", "acos(%s)",
"atan(%s)", "-%s"]
BINARIES = ["%s + %s", "%s - %s", "%s * %s", "%s / %s", "%s ** %s"]
PROP_PARANTHESIS = 0.3
PROP_BINARY = 0.7
def generate_expressions(scope, num_exp, num_ops):
scope = list(scope) # make a copy first, append as we go
for _ in xrange(num_ops):
if random() < PROP_BINARY: # decide unary or binary operator
ex = choice(BINARIES) % (choice(scope), choice(scope))
if random() < PROP_PARANTHESIS:
ex = "(%s)" % ex
scope.append(ex)
else:
scope.append(choice(UNARIES) % choice(scope))
return scope[-num_exp:] # return most recent expressions
根据之前的回答,我只是随机在二元运算符周围加了一些括号,概率是 PROP_PARANTHESIS
(这有点作弊)。因为二元运算符比一元运算符更常见,所以我也留了一个配置项 PROP_BINARY
。下面是一个示例代码:
scope = [c for c in "abcde"]
for expression in generate_expressions(scope, 10, 50):
print expression
这段代码会生成类似这样的内容:
e / acos(tan(a)) / a * acos(tan(a)) ** (acos(tan(a)) / a + a) + (d ** b + a)
(a + (a ** sqrt(e)))
acos((b / acos(tan(a)) / a + d) / (a ** sqrt(e)) * (a ** sinh(b) / b))
sin(atan(acos(tan(a)) ** (acos(tan(a)) / a + a) + (d ** b + a)))
sin((b / acos(tan(a)) / a + d)) / (a ** sinh(b) / b)
exp(acos(tan(a)) / a + acos(e))
tan((b / acos(tan(a)) / a + d))
acos(tan(a)) / a * acos(tan(a)) ** (acos(tan(a)) / a + a) + (d ** b + a) + cos(sqrt(e))
(acos(tan(a)) / a + acos(e) * a + e)
((b / acos(tan(a)) / a + d) - cos(sqrt(e))) + sinh(b)
如果把 PROP_BINARY = 1.0
设置为1,并且应用
scope = range(100)
我们就能得到像这样的输出:
43 * (50 * 83)
34 / (29 / 24)
66 / 47 - 52
((88 ** 38) ** 40)
34 / (29 / 24) - 27
(16 + 36 ** 29)
55 ** 95
70 + 28
6 * 32
(52 * 2 ** 37)
是的,你可以用Python的方式生成随机的算术表达式。不过,你需要换个思路。不要试着生成一个字符串然后去数括号的数量。相反,你应该生成一个随机的表达式树,然后输出它。
这里的表达式树,指的是一个叫做Expression
的类的实例,下面还有一些子类,比如Number
、PlusExpression
、MinusExpression
、TimesExpression
、DivideExpression
和ParenthesizedExpression
。除了Number
以外,这些子类都会有类型为Expression
的字段。给每个类一个合适的__str__
方法。然后生成一些随机的表达式对象,最后只需打印出“根”节点。
你能接着往下做吗,还是需要我帮你写代码?
附加说明:这里有一些示例的起始代码。虽然现在还不能生成随机表达式,但这个功能可以后续添加……
# This is just the very beginning of a script that can be used to process
# arithmetic expressions. At the moment it just defines a few classes
# and prints a couple example expressions.
# Possible additions include methods to evaluate expressions and generate
# some random expressions.
class Expression:
pass
class Number(Expression):
def __init__(self, num):
self.num = num
def __str__(self):
return str(self.num)
class BinaryExpression(Expression):
def __init__(self, left, op, right):
self.left = left
self.op = op
self.right = right
def __str__(self):
return str(self.left) + " " + self.op + " " + str(self.right)
class ParenthesizedExpression(Expression):
def __init__(self, exp):
self.exp = exp
def __str__(self):
return "(" + str(self.exp) + ")"
e1 = Number(5)
print e1
e2 = BinaryExpression(Number(8), "+", ParenthesizedExpression(BinaryExpression(Number(7), "*", e1)))
print e2
** 附加说明 2 **
重新开始用Python编程真的很有趣。我忍不住实现了随机表达式生成器。这个生成器是基于上面的代码构建的。抱歉有些地方写死了!!
from random import random, randint, choice
def randomExpression(prob):
p = random()
if p > prob:
return Number(randint(1, 100))
elif randint(0, 1) == 0:
return ParenthesizedExpression(randomExpression(prob / 1.2))
else:
left = randomExpression(prob / 1.2)
op = choice(["+", "-", "*", "/"])
right = randomExpression(prob / 1.2)
return BinaryExpression(left, op, right)
for i in range(10):
print(randomExpression(1))
这是我得到的输出:
(23)
86 + 84 + 87 / (96 - 46) / 59
((((49)))) + ((46))
76 + 18 + 4 - (98) - 7 / 15
(((73)))
(55) - (54) * 55 + 92 - 13 - ((36))
(78) - (7 / 56 * 33)
(81) - 18 * (((8)) * 59 - 14)
(((89)))
(59)
看起来不是特别好。我觉得它生成了太多的括号。也许可以调整一下选择括号表达式和二元表达式的概率,这样可能会更好……