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

本文涉及的产品
云解析DNS,个人版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
全局流量管理 GTM,标准版 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)。该程序的优势在于代码简洁清晰,空间复杂度低,缺点在于算法时间复杂度没有得到很好的改善,仍有待优化。这次的实践要求我们结合所学,根据实际问题合理地选择相应的存储结构,并设计出有效算法,较之前的理论课来说更具有挑战性。从选题到查找资料、确定思路,从源代码的完成、调试、修改、完善到撰写报告,我们加深了对算法与数据结构概念的理解,强化了面向对象的程序设计理念,培养了综合运用所学知识处理实际问题的能力。 实践过程中,我们经过查找参考资料、技术手册和撰写文档,进一步培养了软件工程师的综合素质;而通过验证自己设计的算法的正确性,我们学会有效利用基本调试方法,迅速找出程序代码中的错误并且修改。这次实践还让我们复习到了离散数学的相关知识,通过不断探寻改进更优解、写出简洁优美的算法,我们更为直观的体会到了算法之美。

相关文章
|
2月前
|
缓存 监控 程序员
Python中的装饰器是一种特殊类型的声明,它允许程序员在不修改原有函数或类代码的基础上,通过在函数定义前添加额外的逻辑来增强或修改其行为。
【6月更文挑战第30天】Python装饰器是无侵入性地增强函数行为的工具,它们是接收函数并返回新函数的可调用对象。通过`@decorator`语法,可以在不修改原函数代码的情况下,添加如日志、性能监控等功能。装饰器促进代码复用、模块化,并保持源代码整洁。例如,`timer_decorator`能测量函数运行时间,展示其灵活性。
27 0
|
4天前
|
Python
Python中的Lambda表达式
Python中的Lambda表达式
|
18天前
|
机器学习/深度学习 算法 数据挖掘
决策树算法大揭秘:Python让你秒懂分支逻辑,精准分类不再难
【8月更文挑战第2天】决策树算法以其直观性和解释性在机器学习领域中独具魅力,尤其擅长处理非线性关系。相较于复杂模型,决策树通过简单的分支逻辑实现数据分类,易于理解和应用。本示例通过Python的scikit-learn库演示了使用决策树对鸢尾花数据集进行分类的过程,并计算了预测准确性。虽然决策树优势明显,但也存在过拟合等问题。即便如此,无论是初学者还是专家都能借助决策树的力量提升数据分析能力。
21 4
|
1月前
|
存储 算法 索引
深度挖掘:Python并查集背后的秘密,让你的代码逻辑清晰如水晶!
【7月更文挑战第17天】并查集,一种高效处理集合合并与查询的数据结构,常用于图论、社交网络分析等。Python中的实现利用数组存储元素的父节点,通过路径压缩和按秩合并优化查找和合并操作。简单代码示例展示了查找和合并方法,以及应用在检测无向图环路。并查集以其优雅的解决方案在算法世界中闪耀,提升代码的清晰度和效率。
32 5
|
1月前
|
算法 搜索推荐 测试技术
python中算法逻辑错误(Logic Errors)
【7月更文挑战第18天】
40 2
|
1月前
|
算法 测试技术 Python
python中代码逻辑错误
【7月更文挑战第15天】
33 2
|
13天前
|
存储 JSON 程序员
Python文件操作与数据持久化:强大功能简化存储管理,助力程序员高效实现业务逻辑
【8月更文挑战第6天】数据是现代计算机程序的核心,但其存储与管理常常构成开发挑战。Python凭借其强大的文件操作与数据持久化机制,显著提升了编程效率。Python的文件处理简单直观,通过内置`open`函数即可轻松实现文本或二进制文件的读写。例如,仅需几行代码就能完成文本写入。此外,Python支持多种数据持久化方案,如文本文件、CSV、JSON及数据库操作。利用内置`json`模块,可以便捷地进行JSON数据的序列化与反序列化,实现数据的有效存储与检索。这些特性使得Python成为数据管理和存储的理想选择,让开发者能够更加专注于业务逻辑的实现。
25 0
|
2月前
|
存储 Python
在Python中,匿名函数(lambda表达式)是一种简洁的创建小型、一次性使用的函数的方式。
【6月更文挑战第24天】Python的匿名函数,即lambda表达式,用于创建一次性的小型函数,常作为高阶函数如`map()`, `filter()`, `reduce()`的参数。lambda表达式以`lambda`开头,后跟参数列表,冒号分隔参数和单行表达式体。例如,`lambda x, y: x + y`定义了一个求和函数。在调用时,它们与普通函数相同。例如,`map(lambda x: x ** 2, [1, 2, 3, 4, 5])`会返回一个列表,其中包含原列表元素的平方。
33 4
|
2月前
|
Python
在Python中,解包参数列表和Lambda表达式是两个不同的概念
【6月更文挑战第19天】在Python中,解包参数允许将序列元素作为单独参数传递给函数,如`greet(*names_and_ages)`。而Lambda表达式用于创建匿名函数,如`lambda x, y: x + y`。两者可结合使用,如`max(*numbers)`找列表最大值,但过度使用lambda可能降低代码可读性。
21 3
|
2月前
|
Python
Python教程:一文了解如何使用Lambda 表达式和 filter函数实现过滤器
在 Python 中,Lambda 表达式是一种匿名函数,也就是没有名称的函数。它允许您快速定义简单的单行函数,通常用于函数式编程中的一些场景,例如在高阶函数中作为参数传递。
43 2