用于创建随机但有效的数学表达式的算法

时间:2016-02-13 10:07:56

标签: python algorithm arithmetic-expressions

为了为表达式解析器生成大型测试数据(基于Dijkstra的调车场),我提出了以下 Python脚本

#!/usr/bin/python

import ast
import sys
import random
import operator as op

def gen_digit(n):

    i = 0
    digit = ""

    if random.randint(0, 1e06) % 17 == 0:
        digit = digit + "-"

    while i < n:
        if i == 0:
            digit = digit + str(random.randint(1, 9))
        else:
            digit = digit + str(random.randint(0, 9))

        i = i + 1

    return digit;

def rnd_op():
    ops = [ "+", "-", "*", "/", "%" ]
    return ops[random.randint(0, 4)]

operators = {ast.Add: op.add, ast.Sub: op.sub, ast.Mult: op.mul,
        ast.Div: op.truediv, ast.Mod: op.mod, ast.USub: op.neg}

def eval_(node):
    if isinstance(node, ast.Num):
        return node.n
    elif isinstance(node, ast.BinOp):
        return operators[type(node.op)](eval_(node.left), eval_(node.right))
    elif isinstance(node, ast.UnaryOp):
        return operators[type(node.op)](eval_(node.operand))
    else:
        raise TypeError(node)

def eval_expr(expr):
    return eval_(ast.parse(expr, mode='eval').body)

def right_op(op, expr):

    if op == "/" or op == "%":

        try:
            v = eval_expr(expr)
        except ZeroDivisionError:
            v = 0

        if v == 0:
            return op + " (" + expr + " + " + gen_digit(random.randint(1, 4)) + ")"
        else:
            return op + " " + expr

    else:
        return op + " " + expr

def gen_term():

    term = ""

    if random.randint(0, 1e06) % 17 == 0:
        term += "-"

    term += "(" + right_op(gen_digit(random.randint(1, 4)), \
            right_op(rnd_op() + " " + gen_digit(random.randint(1, 4)), \
            rnd_op() + " " + gen_digit(random.randint(1, 4)))) + ")"

    return term

def build_expr():
    return "(" + gen_term() + " " + \
        right_op(rnd_op(), gen_term()) + " " + \
        right_op(rnd_op(), gen_term()) + ")"

def rnd_expr(expr, m, d):

    if d < m:
        expr = "(" + build_expr() + " " + \
                right_op(rnd_op(), rnd_expr(expr, m, d + 1)) + " " + \
                right_op(rnd_op(), build_expr()) + ")"

    return expr

argc = len(sys.argv)

if argc > 1:

    dpth = int(sys.argv[1]);
    sys.setrecursionlimit(dpth * 10)
    print (rnd_expr(build_expr(), dpth, 0))

else:
    print (rnd_expr(build_expr(), 1, 0))

我的分流场实施(另一个C++项目)是正确的,接受四个基本算术运算符加%(modulo)

我想让生成的表达式有效,但是目前我偶尔会遇到 division / modulo by zero errors ,尽管我尝试过它们。此外,ast溢出的递归深度大于98

编辑 分区/模数为零错误不在 Python脚本中,而是通过使用{{}等外部工具进行解析1}}在 Linux 上。

有人有一个想法,为什么函数bc算法有时会失败。

1 个答案:

答案 0 :(得分:2)

实际上 Python脚本正在做它专门做的事情:生成有效的测试数据!

如果你改变了

def rnd_op():
    ops = [ "+", "-", "*", "/", "%" ]
    return ops[random.randint(0, 4)]

def rnd_op():
    ops = [ "+", "-", "*", "/", "%" ]
    return ops[random.randint(0, 3)]

即。省略模运算符% 的创建,而不是bash shell中的单行将证明它是正确的:

while(./rnd_expr.py 4 | perl -e 'my $exp = <STDIN>; if(!defined(eval($exp))) { print $@." ".$exp; exit(1) } else { print eval($exp)."\n"; exit(0); }' ); do true; done

没有更改时,它会抱怨模数为零。使用bc重新检查会显示相同的结果。

我的主要C++项目主要接受表达式,而perlbc都主要拒绝它。 所以我的主C++项目中有一个(可能)优先级错误。

修改: 两者都是正确的,perl/bc我的主C++项目。第一个是将结果解释为integer并截断中间结果,而我的主C++项目正在计算符号(即使用分数类)

另一个证明 rubberduck调试实际上正在运行:)