基于python实现通过真值表判断一个逻辑表达式

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 基于python实现通过真值表判断一个逻辑表达式

完整代码:https://download.csdn.net/download/weixin_55771290/87428985


设计要求


1.问题描述


一个逻辑表达式如果对于其变元的任一种取值都为真,则称为重言式;反之,如果对于其变元的任一种取值都为假,则称为矛盾式;然而,更多的情况下,既非重言式,也非矛盾式。试写一程序,通过真值表判断一个逻辑表达式属于哪一类。


2.需求分析


  1. 逻辑表达式从终端输入,长度不超过一行。逻辑运算符包括“|”,“&”和“~”,分别表示或、与和非,运算优先程度递增,但可以由括号改变,即括号内的运算优先。逻辑变元为大写字母,表达式中任何地方都可以含有多个空格符。
  2. 若是重言式或矛盾式,显示“True forever”或“False forever”,否则显示“Satisfactible”。若用户对表达式中变元取一组值,程序就求出并显示逻辑表达式的值。在判断的结果下方同时列出真值表。
  3. 对于一个简单的表达式求值运算规则如下:

a) 从左至右依次计算。


b) 先取反, 然后相与,后相或。


c) 先括号内,后括号外。


二、准备知识

我们可以将逻辑表达式的计算类比算术表达式的计算,通常借助堆栈来实现按运算符的优先级完成表达式的求值计算:一个是存放运算符的栈,另一个是存放变量或中间结果的栈。回顾一下中缀表达式转后缀表达式并计算的计算过程:

1、任何中缀表达式都由运算数,运算符,括号这三部分组成,其中括号的优先级最高,其次是乘除,最后是加减。


2、从左到右遍历中缀表达式,若遇到运算数时,则直接将压入数据栈。


3、若为运算符,以下几种情况直接将符号入栈:


栈为空;栈顶为左括号;该符号优先级比栈顶符号的优先级高。


4、若遇到右括号,表达括号内的中缀表达式已经扫描完毕,则执行计算步骤:从运算符栈弹出一个运算符号,从数据栈弹出两个数字进行一次运算并将结果入栈。重复此步骤直至遇到左括号,将左括号出栈。


5、如果该运算符的优先级小于或等于栈顶运算符的优先级时,重复4中的计算步骤,直到运算符栈空或栈顶为左括号。


6、最后扫描到中缀表达式的末尾,继续执行之前的计算步骤直到运算符栈空。


三、概要设计


image.png

算法整体流程


如图figure1所示,我们算法的整体思路非常清晰;对于给定的逻辑表达式,我们先解析得到所有的变元,然后我们依次给变元赋值0和1;对于由n个变元组成的逻辑表达式,那么最终将得到2^n个不含任何变元的逻辑表达式;最后我们通过将这2^n个表达式一一计算得到真值表;通过真值表判定该逻辑表达式是否是重言式或者矛盾式;对于有追加的赋值,一种方法是直接查刚才计算得出的真值表,另一种途径是提前将追加的变元赋值,然后再解析、算真值表;为了代码的复用,我们使用了第二种方法。


四、详细设计


代码的整体结构如下所示,我们采用了面向对象的编程思想;定义LogicExpression类完成所有功能; 定义字典代表所有字符符号的优先级;定义__init__方法完成对象的初始化;定义parse方法完成变元的解析;定义generate方法完成真值表(变元部分)的生成;operate方法、process方法、solve方法均为具体计算表达式的方法;定义main方法整合所有计算过程;定义conclusion方法完成最后表达式的判定;

classLogicExpression:priority={'(':4,'~':3,'&':2,'|':1}#定义符号的优先级def__init__(self,expression)defparse(self):defgenerate(self):defoperate(self,symbol,arg):#不定长参数defprocess(self,data,opt):defsolve(self,exp):defmain(self):@propertydefconclusion(self):

1.输入表达式


初始化部分非常简单,只是获取用户输入的表达式并删去多余的空格

def__init__(self,expression):self.expression=expression.replace(' ','')#初始表达式、并删掉多余空格

2.解析变量


而解析表达式,获得变元这部分我们使用了python的正则表达式模块,我们规定变元的命名标准同代码中变元的命名标准相同;此外用set去除出现过不止一次的变元;最后按照变元的原始顺序排序。

defparse(self):pattern=re.compile(r'[a-zA-Z_]w')# 变量规则与代码中变量定义规则相同 字母、 数字、 下划线组合,数字不能开头vars1=pattern.findall(self.expression)# 解析成变量列表(待去重)vars2=list(set(vars1))# 去重vars2.sort(key=vars1.index)# 保持原来顺序不变returnvars2


3.遍历赋值

在编历赋值这块,我们使用了python的自建模块itertools.product函数快速生成组合元祖;然后直接将变元用0和1替换;在返回时,使用yield函数依次返回一种组合,减少内存开销;

defgenerate(self):l=[0,1]forcaseinproduct([l]len(self.vars)):#case为生成的组合元祖: 两个变量的情况下为(0, 0)(0, 1)(1, 0)(1, 1)exp=self.expressionforiinrange(len(self.vars)):exp=exp.replace(self.vars[i],str(case[i]))yieldexp# 使用python的迭代器依次返回代入值后的表达式


4.计算表达式的值


计算表达式的值(不带变元)是整个代码的核心;我们的思想是维护两个栈分别存放逻辑符号和逻辑值。遍历表达式中的所有字符,按照优先级规则和栈内情况具体决定压栈和出栈,具体标准请参照下图:


  1. 对于逻辑值直接进逻辑栈;
  2. 遇到 ) 则进行出栈计算直到逻辑符号栈顶为 ( ,并将 ( 出栈
  3. 逻辑符号直接进栈的三种情况:符号栈为空、符号栈顶为(、符号栈顶符号的优先级小于该符号,在这三种情况下直接进栈
  4. 其余情况下,根据栈顶符号类型,取出相应数量的逻辑值进行运算,然后压入逻辑值栈;如此往复,直至当前符号满足进栈规则3
  5. 最后如果符号栈尚有元素,则继续运算
  6. 逻辑值栈最后存在的值即为表达式的解


fd35eb58a2ad42db00b315d0d637cfb0.png



Figure2 出入栈标准

对应代码如下:


defsolve(self,exp):data=[]# 逻辑栈opt=[]# 操作符号栈foriinexp:ifi.isdigit():#如果是数字则直接进逻辑


其中还定义了两个子函数process和operate; processs 函数的作用是模拟取出符号栈和逻辑值栈元素运算的过程;operate函数作用是具体计算一个!&和|的运算,代码示例如下:

defoperate(self,symbol,arg):#不定长参数'''symbol:逻辑符号~&|arg:逻辑值(不定长)'''ifsymbol=='~'andlen(arg)==1:#~的情况returnnotarg[0]elifsymbol=='&'andlen(arg)==2:# & 的情况returnarg[0]andarg[1]elifsymbol=='|'andlen(arg)==2:# | 的情况returnarg[0]orarg[1]else:raise"operate error"defprocess(self,data,opt):'''data:逻辑值栈存放true和falseopt:符号栈存放~&|'''symbol=opt.pop()ifsymbol=='~':logic1=data.pop()data.append(self.operate(symbol,logic1))returnelifsymbolin('&','|'):logic1=data.pop()logic2=data.pop()data.append(self.operate(symbol,logic1,logic2))return

5.判断永真或者永假


最后判断是否永真或者永假,非常简单只要判断结果列表里面是否存在true(false)的情况

@propertydefconclusion(self):ifFalsenotinself.results:return'True forever'elifTruenotinself.results:return'False forever'else:return'Satisfactible'

6.网页部分


我们还设计了一个小网页用作交互,如下图所示:

网页部分使用python的flask引擎搭建;后端也很简单;分别对url的get请求和post请求做响应;post请求中调用实现好的LogicExpression类运算,而get请求则直接返回原始网页。


@app.route("/",methods=["GET","POST"])defmain():ifrequest.method=='POST':expression=request.form.get('expression')addition=request.form.get('addition')ifaddition:expression=withAddition(expression,addition)le=LogicExpression(expression)varss,results,conclusion=le.vars,le.results,le.conclusionlogic_table=tuple(product([[0,1]]len(varss)))returnrender_template("index.html",form=request.form,logic_table=logic_table,varss=varss,results=results,conclusion=conclusion)else:returnrender_template("index.html",form={})

710b7a1b85556c72d8503289944c3842.png


Figure3 网页外观


489ac7eae45c2d607b8edc0dd98e7715.png


Figure4 判断结果


五、程序测试


  1. 第一次进行测试时,发现输入逻辑表达式 P | P & Q 穷举出来的结果有三个False,一个True,与实际不符(正确结果应为两个False,两个True),检查发现程序没有考虑到运算符 & 和 | 之间的优先级不同,所以先算了表达式前半部分,相当于是计算了(P | P)& Q 的结果。更正后自定义了各个符号对应的优先级,在符号出入栈时增加了比较优先级的步骤,再次进行测试。
  2. 为了避免人工构造逻辑表达式产生的局限性,我们编写了一段程序,随机生成了5000条表达式,保证了测试的覆盖完整性,提高了测试效率。生成的部分测试用例如图所示:

f053584e344d70ce418e595ca4665f85.png

Figure5 测试用例

  1. 人工验证结果难免疏漏,且想要人工验证5000条结果必定耗时巨大。为了更好的验证结果的正确性,我们选用了事先写好的两段不同的程序运行上述的5000条测试用例,再将两者的结果进行比对,结果完全一致。以下是其中一个程序的部分运行结果,及两边结果的比对:


9a4fb38169d13ab59572917100ed0582.png


Figure6 程序运行结果


bd32da9791e9ef18e1f815b49c3053a4.png

Figure7 结果比对


六、总结


该程序运用了 python语言对数据的存储结构和算法进行描述,实现了命题逻辑公式的类型判断。设输入的逻辑表达式的长度为m,其中有n个变元,则算法时间复杂度为T(n)=O(2^n),两个堆栈的长度不超过m,空间复杂度为O(m)。该程序的优势在于代码简洁清晰,空间复杂度低,缺点在于算法时间复杂度没有得到很好的改善,仍有待优化。这次的实践要求我们结合所学,根据实际问题合理地选择相应的存储结构,并设计出有效算法,较之前的理论课来说更具有挑战性。从选题到查找资料、确定思路,从源代码的完成、调试、修改、完善到撰写报告,我们加深了对算法与数据结构概念的理解,强化了面向对象的程序设计理念,培养了综合运用所学知识处理实际问题的能力。 实践过程中,我们经过查找参考资料、技术手册和撰写文档,进一步培养了软件工程师的综合素质;而通过验证自己设计的算法的正确性,我们学会有效利用基本调试方法,迅速找出程序代码中的错误并且修改。这次实践还让我们复习到了离散数学的相关知识,通过不断探寻改进更优解、写出简洁优美的算法,我们更为直观的体会到了算法之美。

相关文章
|
22天前
|
Python
Python编程中正则表达式的使用
【10月更文挑战第22天】正则表达式,一种强大的文本处理工具,在Python编程中有着广泛的应用。本文将介绍如何使用Python中的re库来使用正则表达式,包括如何创建、匹配、查找和替换字符串等。通过学习本文,你将能够掌握Python中正则表达式的基本使用方法。
|
1月前
|
存储 Java 编译器
Python学习三:学习python的 变量命名规则,算数、比较、逻辑、赋值运算符,输入与输出。
这篇文章是关于Python编程语言中变量命名规则、基本数据类型、算数运算符、比较运算符、逻辑运算符、赋值运算符以及格式化输出与输入的详细教程。
18 0
Python学习三:学习python的 变量命名规则,算数、比较、逻辑、赋值运算符,输入与输出。
|
2月前
|
Python
Python中正则表达式(re模块)用法详解
Python中正则表达式(re模块)用法详解
36 2
|
1月前
|
程序员 Python
Python中Lambda表达式的优缺点及使用场景
Python中Lambda表达式的优缺点及使用场景
22 0
|
1月前
|
存储 算法 API
Python学习五:函数、参数(必选、可选、可变)、变量、lambda表达式、内置函数总结、案例
这篇文章是关于Python函数、参数、变量、lambda表达式、内置函数的详细总结,包含了基础知识点和相关作业练习。
26 0
|
2月前
|
机器学习/深度学习 算法 数据挖掘
决策树算法大揭秘:Python让你秒懂分支逻辑,精准分类不再难
【9月更文挑战第12天】决策树算法作为机器学习领域的一颗明珠,凭借其直观易懂和强大的解释能力,在分类与回归任务中表现出色。相比传统统计方法,决策树通过简单的分支逻辑实现了数据的精准分类。本文将借助Python和scikit-learn库,以鸢尾花数据集为例,展示如何使用决策树进行分类,并探讨其优势与局限。通过构建一系列条件判断,决策树不仅模拟了人类决策过程,还确保了结果的可追溯性和可解释性。无论您是新手还是专家,都能轻松上手,享受机器学习的乐趣。
47 9
|
3月前
|
程序员 Python
Python控制语句和现实逻辑表达
将现实世界的逻辑表达为Python代码,关键在于将复杂的逻辑分解为简单的、可用控制语句表达的部分。以下是一些示例,展示如何将现实逻辑用Python代码表达。
38 3
|
3月前
|
测试技术 Python
解锁Python魔法!装饰器:让你的代码翩翩起舞,简化繁琐逻辑,让编程成为一场戏剧性的华丽变身!
【8月更文挑战第21天】在Python编程中,当需要为函数添加如日志记录、性能测试等功能时,手动重复编写相同代码既冗长又难维护。装饰器提供了解决方案:它是一种特殊函数,包裹目标函数以添加额外功能,而不改变原函数结构。装饰器增强了代码复用性、解耦及灵活性。例如,可通过装饰器轻松记录函数执行时间。更高级用法包括带参数的装饰器、多层装饰器以及使用类作为装饰器。掌握装饰器能显著提升Python代码的质量和效率。
37 5
深入浅出python的lambda表达式
今天我们来聊聊Python中一个常用的特性 - lambda表达式。别被这个听起来很高大上的名字吓到,其实它就是个匿名函数的实现机制。
|
3月前
|
Python
Python中的Lambda表达式
Python中的Lambda表达式