任意表达式求值(算符优先算法)

任意表达式求值

任意表达式求值的算法是很常用的一个功能,虽然python有自带的eval,但必须基于python语法。而我们很多时候想创建自己的DSL,表达式求值都是核心的功能。

算符优先算符

实现这个功能,其实就是编译器的功能,有很多种实现方法,本文使用的是经典的算符优先算法。这个算符非常简洁优雅,可扩展性很强,不但可以计算表达式,如果适度扩展,完全可以实现图灵完备的DSL。

双栈

算符优先算法的第一个核心是双栈,一个算符栈,一个计算数栈。如果一个算符只能对常数计算就太弱了,所以本文算法支持了外挂的变量系统。
只需对isVar和getValue赋值,即可实现变量功能。

如果不定义isVar,则所有变量直接当作字符串处理

算符定义

算符优先算法的第二个核心是算符定义,虽然是经典算法,但或许有同学忘了也说不定。所以这里简单介绍一下。
任何一个算符(除了永不入栈的右括号)都有两个优先级,一个是栈外优先级,一个是栈内优先级。
这个算符的运算包括几个运算数。
以及实际执行计算的函数。

opDict={}
def addoptr(ch, outLev, inLev, func, parmNum=2):
    obj= {'name':ch, 'out':outLev, 'in':inLev, 'func':func, 'parmNum':parmNum}
    opDict[ch]= obj
addoptr('#', 1, 1, None)
addoptr('(', 90, 2, None)
addoptr(')', 2, None, None)
addoptr('&', 13, 14, lambda x: x[-1] and x[-2])
addoptr('|', 11, 12, lambda x: x[-1] or x[-2])
addoptr('~', 16, 17, lambda x: not x[-1],1)
addoptr('=', 22, 23, lambda x: x[-1]==x[-2])
addoptr('>', 22, 23, lambda x: x[-2]>x[-1])
addoptr('<', 22, 23, lambda x: x[-2]<x[-1])
addoptr('+', 31, 32, lambda x: x[-1]+x[-2])
addoptr('-', 31, 32, lambda x: x[-2]-x[-1])
addoptr('*', 41, 42, lambda x: x[-1]*x[-2])
addoptr('/', 41, 42, lambda x: x[-2]/x[-1])

注意大部分算符的栈内优先级都高于栈外优先级,是为了在连续遇到时可以优先计算前面的部分,比如3+5+2,第二个加号出现的时候,由于栈外优先级低于站内优先级,所以前面的3+5就被计算求值。

处理逻辑

处理逻辑相当简洁,见代码

    def eval(this, cond):
        stack=[]
        this.pushoptr('#')
        while True:
            aword,kind= this.readWord(cond)
            cond= cond[len(aword):].strip()
            if kind=='#':
                this.pushoptr('#')
                break
            elif kind=='optr':
                this.pushoptr(aword)
            else:
                this.pushopnd(aword)
            print(aword, cond)
            print(this.optrStack)
            print(this.opndStack)
        return this.popopnd()

逐个从字符串中读取操作数和操作符,这里除了已经定义的操作符和空格之外,全都被定义为操作数。

未实现的功能

目前实现了逻辑、关系、计算表达式,可以直接适用于很多情况。
由于项目所需功能有限,所以并没有实现完整。未实现的功能有:

  1. 负号/减号 这种一个符号两个含义的操作符暂未支持
  2. >=, <=, != 这种双字符的符号暂未支持
  3. 浮点数暂未支持

如有兴趣,添加这些功能并不难。

使用方法

简单用法(无变量)

    a= exprEngine()
    b= a.eval('3 + 5 = 8 & ~(7 < 2)')
    print(b)

有变量的用法

    a= thing()
    a.addInfo('水位', '高')
    b= a.eval('水位=高&3+5=8&~(7<2)')  
    #其中 a.eval的函数这样写
    en= exprEngine(this.isattr, this.getValue)
    return en.eval(cond)

完整代码

# 任意表达式的计算引擎
# 目前不支持负数、浮点数
# 不支持>=和<=等双字节符号
# 使用算符优先算法,双栈
opDict={}
def addoptr(ch, outLev, inLev, func, parmNum=2):
    obj= {'name':ch, 'out':outLev, 'in':inLev, 'func':func, 'parmNum':parmNum}
    opDict[ch]= obj
addoptr('#', 1, 1, None)
addoptr('(', 90, 2, None)
addoptr(')', 2, None, None)
addoptr('&', 13, 14, lambda x: x[-1] and x[-2])
addoptr('|', 11, 12, lambda x: x[-1] or x[-2])
addoptr('~', 16, 17, lambda x: not x[-1],1)
addoptr('=', 22, 23, lambda x: x[-1]==x[-2])
addoptr('>', 22, 23, lambda x: x[-2]>x[-1])
addoptr('<', 22, 23, lambda x: x[-2]<x[-1])
addoptr('+', 31, 32, lambda x: x[-1]+x[-2])
addoptr('-', 31, 32, lambda x: x[-2]-x[-1])
addoptr('*', 41, 42, lambda x: x[-1]*x[-2])
addoptr('/', 41, 42, lambda x: x[-2]/x[-1])

class exprEngine:
    def __init__(this, isVar=None, getValue=None):
        this.opndStack=[]
        this.optrStack=[]
        this.isVar= isVar
        this.getValue= getValue

    def readWord(this, cond):
        cond= cond.strip()
        if cond=='':
            return '', '#'
        if cond[0] in '()&|~+-*/=><':
            return cond[0], 'optr'
        part= ''
        for ch in cond:
            if ch in '()&|~+-*/=>< ':
                break
            part+=ch
        return part, 'opnd'
    def pushoptr(this, optr):
        op= opDict[optr].copy()
        if len(this.optrStack)==0:
            this.optrStack.append(op)
            return
        opTop= this.optrStack[-1]
        if op['out']> opTop['in']:
            this.optrStack.append(op)
        elif op['out']< opTop['in']:
            this.popoptr()
            # 这里递归
            this.pushoptr(optr)
        elif op['out']== opTop['in']:
            # 消括号对,简单弹出
            this.optrStack.pop()
    def popoptr(this):
        opTop= this.optrStack[-1]
        a= opTop['parmNum']
        if len(this.opndStack)<a:
            raise Exception('操作数不足,可能有语法错误!')
        ret= opTop['func'](this.opndStack[-a:])
        this.opndStack= this.opndStack[:-a]
        this.opndStack.append(ret)
        this.optrStack.pop()
    def pushopnd(this, opnd):
        if this.isVar and this.isVar(opnd):
            this.opndStack.append(this.getValue(opnd))
        else:
            if opnd.isdigit():
                this.opndStack.append(int(opnd))
            else:
                this.opndStack.append(opnd)
    def popopnd(this):
        if len(this.opndStack)==1:
            return this.opndStack[0]
        else:
            print(this.opndStack)
            print(this.optrStack)
            raise Exception('可能存在语法错误。')
    def eval(this, cond):
        stack=[]
        this.pushoptr('#')
        while True:
            aword,kind= this.readWord(cond)
            cond= cond[len(aword):].strip()
            if kind=='#':
                this.pushoptr('#')
                break
            elif kind=='optr':
                this.pushoptr(aword)
            else:
                this.pushopnd(aword)
            print(aword, cond)
            print(this.optrStack)
            print(this.opndStack)
        return this.popopnd()

if __name__=='__main__':
    # print(opDict)
    a= exprEngine()
    # a.addInfo('水位', '低')
    b= a.eval('3 + 5 = 8 & ~(7 < 2)')
    print(b)

为了调试,中间加了一些print输出,正式使用时,自行取消。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

圣手书生肖让

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值