news 2026/9/18 9:21:50

用Python实现逻辑公式解析与真值表校验,兼谈SQL量词映射

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
用Python实现逻辑公式解析与真值表校验,兼谈SQL量词映射

简介:面向东北大学《逻辑学》课程学习者的在线平时作业2参考答案文档,以docx格式打包,供复习核对和考前突击使用,适合需要快速确认选择题判断结果的同学。内容覆盖性质判断的对当关系与负判断、三段论规则及其应用、充分条件和必要条件假言推理、类比推理与不完全归纳推理的区分、模态逻辑方阵、概念外延关系等高频考点,每道题均给出相应参考答案,便于对照查漏补缺。整份资源共1个docx文件,压缩包约18KB,轻量精简,下载后可直接打开阅读或按题号检索答案。目前已有43人学习,可作为逻辑学作业作答思路和期末复习的自测参考,帮助快速掌握不同推理形式的判断要点。

1. 打开一份逻辑学作业答案,不如先写一个真值表生成器

“20秋东北大学《逻辑学》在线平时作业2答案.docx”这类文档,第一眼是给人抄的,第二眼就值得琢磨:里面反复出现的命题符号化、真值表、等值演算、谓词翻译,和你每天写的条件分支、SQL 子查询、规则引擎其实是同一套底层语法。与其逐行核对答案,不如把整份在线平时作业当作一组逻辑表达式,用几百行 Python 把它解析出来、自动求值、验证等价性,再把谓词逻辑映射成 SQL 的 EXISTS 写法。下面按这条路线走:先实现命题公式的递归下降解析器,再做真值表和等价性双重校验,最后对照 SQL 量词翻译,并落成一个命令行脚本。这套工具做完,答案文档里每一个结论都能自己验一遍,以后再遇到相关的题也不用对着各种记法猜。

2. 命题公式解析器:把逻辑学作业里的公式串变成 AST

逻辑学作业的第一类题是“将自然语言符号化”,第二类是“构造公式真值表”。这两类的共同前提是:你手里有一条符号串,比如¬(A∧B)→(¬A∨¬B),计算机不能直接理解它。我一般先把字符串切成记号(token),再用递归下降分析法做成抽象语法树(AST),最后对着 AST 求值。这样公式的语义就与显示文本解耦,后续做真值表、等价性校验、中文解析都有同一棵 AST 可复用。

2.1 先约定运算符优先级,不然代码和教材会打架

逻辑学教材的运算顺序约定俗成:否定最高,合取、析取次之,蕴含和等值最低。这与 C 系语言里!高于&&高于||的规则是一致的,但蕴含和等值必须显式改写,因为编程语言没有对应运算符。下表是我在解析器里采用的优先级,也建议你写作业或写代码时沿用同一套:

语义逻辑学记号编程等价写法优先级
否定¬not最高
合取and
析取or
蕴含(not A) or B
等值(A and B) or (not A and not B)最低

注意一个细节:教科书通常规定蕴含是右结合的,也就是A→B→C理解为A→(B→C),而绝大多数递归下降示例默认左结合。我的解析器下面按左结合写,方便和 SQL 里的习惯对齐;你如果想把改成右结合,只需把parse_imp里的循环改成单次递归调用。

2.2 递归下降把符号串变成 AST

递归下降解析器的写法很固定:每个优先级对应一个函数,函数之间按优先级从低到高互相调用。以下代码可以完整运行,可直接保存为logic_parser.py

import re class Node: def __init__(self, op, args=None): self.op = op # 'var' | 'not' | 'and' | 'or' | 'imp' | 'iff' self.args = args or [] def tokenize(text): # 全角括号转半角,避免作业文档里的中文符号坑 text = text.replace('(', '(').replace(')', ')') # 只匹配单字母变量名;中文字符支持放到最后讲 tokens = re.findall(r'[A-Za-z]|[¬∧∨→↔()]', text) return tokens class Parser: def __init__(self, tokens): self.tokens = tokens self.pos = 0 def peek(self): return self.tokens[self.pos] if self.pos < len(self.tokens) else None def pop(self): t = self.peek() self.pos += 1 return t def parse(self): return self.parse_iff() def parse_iff(self): left = self.parse_imp() while self.peek() == '↔': self.pop() right = self.parse_imp() left = Node('iff', [left, right]) return left def parse_imp(self): left = self.parse_or() while self.peek() == '→': self.pop() right = self.parse_or() left = Node('imp', [left, right]) return left def parse_or(self): left = self.parse_and() while self.peek() == '∨': self.pop() right = self.parse_and() left = Node('or', [left, right]) return left def parse_and(self): left = self.parse_not() while self.peek() == '∧': self.pop() right = self.parse_not() left = Node('and', [left, right]) return left def parse_not(self): if self.peek() == '¬': self.pop() return Node('not', [self.parse_not()]) return self.parse_atom() def parse_atom(self): t = self.pop() if t is None: raise ValueError('表达式不完整') if t == '(': node = self.parse_iff() if self.pop() != ')': raise ValueError('括号不匹配') return node if re.fullmatch(r'[A-Za-z]', t): return Node('var', [t]) raise ValueError(f'无法识别的记号: {t}')

解析器入口是parse_iff,因为等值的优先级最低,必须最先拆分;parse_impparse_orparse_and依次往下处理。parse_not对自身做递归调用,这样¬¬A¬(A∧B)都能正确解析。Node只有opargs两个字段,变量节点的args[0]存变量名,二元运算节点的args[0]args[1]存左右子树。

使用这段代码有个前提:变量名必须是单字母,英文大小写均可。如果题目里出现p1q2这样的下标变量,把tokenize里的正则改成r'[A-Za-z][0-9]?|...'即可。括号不匹配或遇到不认识的中文连接词时,解析器会抛异常,这正是我们要的:宁可提前失败,也不要把错误公式送进真值表。

2.3 求值器:给公式一组真值,返回计算结果

AST 建好之后,求值就是一次树的后序遍历。每个节点根据op决定语义,变量节点查环境字典env

def evaluate(node, env): if node.op == 'var': return env[node.args[0]] if node.op == 'not': return not evaluate(node.args[0], env) if node.op == 'and': return evaluate(node.args[0], env) and evaluate(node.args[1], env) if node.op == 'or': return evaluate(node.args[0], env) or evaluate(node.args[1], env) if node.op == 'imp': left = evaluate(node.args[0], env) right = evaluate(node.args[1], env) return (not left) or right if node.op == 'iff': left = evaluate(node.args[0], env) right = evaluate(node.args[1], env) return left == right

Python 的andor有短路求值行为,逻辑学意义的合取、析取是两侧都先求出真假再运算,结果一致,所以这里直接用andor没有问题,只是注意a and b返回的不一定是布尔值,因此求值函数外层统一用not==来归一到布尔。imp的语义写成not left or right,这正是教材里「蕴含式等价于析取式」的代码形态。验证一个简单公式:解析A∧B并传入{'A': True, 'B': False},返回结果是False,与真值表预期一致。

3. 作业答案对不对?用真值表和等值演算双重校验

解析器和求值器就位后,最直接的好处是可以把“在线平时作业2”里所有真值表题变成程序自动输出。判断两个公式是否逻辑等价,也有两条路:爆搜真值表,或者按等值演算规则手工推。两条路我都给你落成代码,跑一遍就能对照答案文档里的结论。

3.1 穷举所有赋值:用位运算生成 2 的 n 次方种情况

n 个命题变量共有2^n种赋值组合。我习惯把变量排序后,用一个整数 mask 从 0 遍历到2^n - 1,mask 的二进制位就代表每个变量的真假。这样不需要递归,代码快而且直观:

def variables(node, seen=None): if seen is None: seen = set() if node.op == 'var': seen.add(node.args[0]) for child in node.args: variables(child, seen) return seen def truth_table(node): vars_ = sorted(variables(node)) n = len(vars_) print('\t'.join(vars_ + ['result'])) for mask in range(1 << n): env = {} for i, v in enumerate(vars_): # 高位对应排序靠前的变量,输出顺序看起来更自然 env[v] = bool((mask >> (n - 1 - i)) & 1) r = evaluate(node, env) row = [str(int(env[v])) for v in vars_] + [str(int(r))] print('\t'.join(row))

参数说明:range(1 << n)产生从 0 到2^n - 1的整数;(mask >> (n - 1 - i)) & 1把第n - 1 - i位取出来,保证第一列变量对应最高位。输出用 tab 分隔,可以直接粘进 Excel 或 Markdown 表格里做作业排版。当n不超过 10 时这个算法是秒出的,超过 12 个变量时行数会涨到 4096 以上,肉眼检查真值表就没有意义了,应该改用下面的等价性判定。

3.2 两公式是否等价:一次遍历发现反例

逻辑等价的意思是:对所有赋值组合,两个公式的真值都相同。所以等价性校验本质上就是合取真值表的逐行比较。我把它单独抽成函数,返回第一个反例,方便定位:

def equivalent(f1, f2): vars_ = sorted(variables(f1) | variables(f2)) for mask in range(1 << len(vars_)): env = {v: bool((mask >> (len(vars_) - 1 - i)) & 1) for i, v in enumerate(vars_)} if evaluate(f1, env) != evaluate(f2, env): return False, env return True, None

这个函数不打印整个表,只在乎是否存在反例。比如检验德摩根律¬(A∧B)¬A∨¬B是否等价,解析两串后调用equivalent,返回(True, None);再检验¬(A∧B)¬A∧¬B,会返回(False, {'A': True, 'B': True})。这一组对照正好是作业里最常见的坑:否定合取时,容易把误写成保持,而正确结果必须变成析取。

等值演算里常用规则表如下,建议在做化简题时对照使用:

规则名称公式
双重否定¬¬A ≡ A
德摩根律¬(A∧B) ≡ ¬A∨¬B;¬(A∨B) ≡ ¬A∧¬B
蕴含等值A→B ≡ ¬A∨B
假言易位A→B ≡ ¬B→¬A
等值展开A↔B ≡ (A→B)∧(B→A)
吸收律A∨(A∧B) ≡ A;A∧(A∨B) ≡ A

3.3 充分必要条件翻车现场:p→q不等于q→p

作业里“只要 p 就 q”这类自然语言,符号化结果是p→q;但很多人凭直觉写成q→p。用前面的函数验证一下:

f1 = Parser(tokenize('p→q')).parse() f2 = Parser(tokenize('q→p')).parse() ok, counter = equivalent(f1, f2) print(ok, counter) # False {'p': False, 'q': True}

反例是p为假、q为真时:p→q为真,q→p为假。这就是充分条件和必要条件最直观的区别:p→q只保证 p 是 q 的充分条件,反过来推不成立。碰到“只有……才……”这类句式时,我一般先在代码里跑一遍等价性,再回去核对翻译——比靠语义硬猜可靠得多。

4. 谓词逻辑和 SQL 查询对表:量词如何落进 WHERE 和 NOT EXISTS

命题逻辑只研究整体命题的真假,而作业里另一大块是谓词逻辑:∀x(P(x)→Q(x))∃x R(x)。不少程序员第一次看到这些符号觉得抽象,但其实数据库查询每天都在用它们。谓词就是 WHERE 条件,量词就是子查询的存在性判断。

4.1 谓词 P(x) 就是一条条件表达式

P(x)理解成“x 满足属性 P”,在 SQL 里对应一行记录满足某个 WHERE 条件。例如P(x)表示“x 选修了离散数学”,翻译成 SQL 片段就是WHERE course_name = '离散数学'。全称量词则代表集合内所有元素都满足,SQL 没有直接对应的关键字,但它有一条经典路:∀x φ(x)等价于NOT EXISTS (x WHERE NOT φ(x))。这种“双否定”写法是 SQL 表达全称量词的唯一自然方式。

下面用学生选课场景演示。目标:查找选修了所有课程的学生。“所有”是典型的全称量词:

SELECT s.id, s.name FROM students s WHERE NOT EXISTS ( SELECT 1 FROM courses c WHERE NOT EXISTS ( SELECT 1 FROM course_selection cs WHERE cs.student_id = s.id AND cs.course_id = c.id ) );

逻辑说明:最内层判断该学生是否选了课程 c;中间层对每门课程 c 做“不存在未选记录”的判断,等价于“对于所有课程 c,都存在选课记录”。注意别名作用域:内层子查询里的s.id引用的是最外层students s,这是关联子查询的常见写法,也最容易写漏。

4.2 存在量词 EXISTS 的正面用法

∃x φ(x)就是 SQL 的EXISTS,几乎没有坑。查找至少选修了一门名为“高等数学”课程的学生:

SELECT DISTINCT s.id, s.name FROM students s WHERE EXISTS ( SELECT 1 FROM course_selection cs JOIN courses c ON cs.course_id = c.id WHERE cs.student_id = s.id AND c.name = '高等数学' );

这里EXISTS子查询一旦返回任意一行就为真,SQL 优化器通常用 semi join 处理,不会重复扫描。实际开发里,很多团队把“有没有关联记录”统一写成EXISTS,而不是IN,主要原因是EXISTS在大表下更容易走到索引,且不会因为子查询返回 NULL 而出错。量词否定形式也是作业和 SQL 面试的共同考点,对应关系如下:

逻辑公式SQL 等价写法
∀x φ(x)NOT EXISTS (SELECT 1 ... WHERE NOT φ)
∃x φ(x)EXISTS (SELECT 1 ... WHERE φ)
¬∀x φ(x)EXISTS (SELECT 1 ... WHERE NOT φ)
¬∃x φ(x)NOT EXISTS (SELECT 1 ... WHERE φ)

规则引擎里也能看到同样的影子:Drools 的existsnot exists条件元素,语义与 SQL 完全一致;书写复合规则时,否定条件同样建议写成not exists而不是not (条件),因为后者在事实不存在时会因为逻辑三元性产生和预期不一致的激活结果。

4.3 “所有 S 都是 P” 不等于 “∀x(S(x)∧P(x))”

这是谓词逻辑作业里最容易扣分的一条。传统逻辑的“所有 S 都是 P”正确翻译是∀x(S(x)→P(x)),而不是∀x(S(x)∧P(x))。原因很简单:如果是在全宇宙论域下,∀x(S(x)∧P(x))要求所有个体既是 S 又是 P,那“班上没有不及格的人”这种为真的命题反而会被判假。用 SQL 理解更直接:∀x(S(x)→P(x))对应“对于每一行,如果它属于 S 集合,则它也在 P 集合”,处理的是条件过滤;而∀x(S(x)∧P(x))相当于要求查询结果全表都是交集,这基本不会出现在真实查询里。做翻译题之前,先把论域写出来,再决定要不要加蕴含号。

5. 把在线平时作业变成命令行校验器

最后给你一个能直接用的技巧:把平时作业里的题干整理成 JSON,用脚本批量校验,输出结果和反例。这个做法同样适配后续任何逻辑作业——不管它叫在线平时作业 2 还是期末考试模拟。

先定义题目文件questions.json

[ {"id": 1, "expr": "¬(A∧B)", "expected": "¬A∨¬B"}, {"id": 2, "expr": "p→q", "expected": "q→p"} ]

再写一个check.py,复用前面解析和等价性判断逻辑:

import json, sys # 前面的 Parser、tokenize、equivalent、variables 均省略,直接引用 def load_expr(text): # 将中文连接词先替换成标准符号 mapping = { '并非': '¬', '非': '¬', '并且': '∧', '且': '∧', '或者': '∨', '或': '∨', '蕴含': '→', '等值': '↔', '当且仅当': '↔', '如果': '', '那么': '→' } for k, v in mapping.items(): text = text.replace(k, v) return Parser(tokenize(text)).parse() if __name__ == '__main__': with open('questions.json', encoding='utf-8') as f: questions = json.load(f) for q in questions: f1 = load_expr(q['expr']) f2 = load_expr(q['expected']) ok, counter = equivalent(f1, f2) print(f"{q['id']}: {'通过' if ok else '不通过'} {counter}")

运行python check.py后,第 1 题显示通过,第 2 题会输出不通过以及反例{'p': False, 'q': True}。中文连接词替换的映射表里,如果被直接删掉,那么替换成,这样“如果 p 那么 q”会被处理成p→q,处理不了更复杂的“只有……才”句式,遇到这类题目我建议手动改成符号串,不要依赖自动翻译。批量校验的价值在于:你不需要逐行看答案文档的花括号和箭头,真值表全列出来,哪些结论站得住脚一目了然。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/18 9:21:05

深度学习训练核心:权重与偏置的调参实战解析

深度学习模型的训练过程&#xff0c;说透了就是在不断调整网络里的权重和偏置这两个核心参数。不管是刚入门的MNIST手写识别&#xff0c;还是动辄几十亿参数的Transformer&#xff0c;底层逻辑都一样&#xff1a;通过成千上万次迭代&#xff0c;把一组随机初始化的数字&#xf…

作者头像 李华
网站建设 2026/9/18 9:18:33

本地部署4步跑通ModelScope离线推理实战

本地部署4步跑通ModelScope离线推理实战 【免费下载链接】modelscope ModelScope: bring the notion of Model-as-a-Service to life. 项目地址: https://gitcode.com/GitHub_Trending/mo/modelscope 手头有个情感分析项目&#xff0c;数据不能出内网&#xff0c;又想让…

作者头像 李华
网站建设 2026/9/18 9:17:57

如果只给 TaoToken 的 Key,V4.1-Flash 多模态链路怎么拆 Token 账

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/18 9:17:10

从MES到RTD,上扬软件用二十五年技术积累定义半导体CIM的真实水准

什么是CIMCIM&#xff0c;即计算机集成制造系统&#xff08;Computer Integrated Manufacturing&#xff09;&#xff0c;是现代高科技制造业&#xff0c;尤其是半导体晶圆制造领域的核心数字化神经中枢。它并非单一软件&#xff0c;而是将制造执行、设备自动化、实时调度、过程…

作者头像 李华