ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

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

2026/9/18 9:21:57 拓冰建站 浏览量
用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.pyimport re class Node: def __init__(self, op, argsNone): 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_imp、parse_or、parse_and依次往下处理。parse_not对自身做递归调用这样¬¬A、¬(A∧B)都能正确解析。Node只有op和args两个字段变量节点的args[0]存变量名二元运算节点的args[0]和args[1]存左右子树。使用这段代码有个前提变量名必须是单字母英文大小写均可。如果题目里出现p1、q2这样的下标变量把tokenize里的正则改成r[A-Za-z][0-9]?|...即可。括号不匹配或遇到不认识的中文连接词时解析器会抛异常这正是我们要的宁可提前失败也不要把错误公式送进真值表。2.3 求值器给公式一组真值返回计算结果AST 建好之后求值就是一次树的后序遍历。每个节点根据op决定语义变量节点查环境字典envdef 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 rightPython 的and、or有短路求值行为逻辑学意义的合取、析取是两侧都先求出真假再运算结果一致所以这里直接用and、or没有问题只是注意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 - 1mask 的二进制位就代表每个变量的真假。这样不需要递归代码快而且直观def variables(node, seenNone): 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) ≡ AA∧(A∨B) ≡ A3.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 的exists和not 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, encodingutf-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处理不了更复杂的“只有……才”句式遇到这类题目我建议手动改成符号串不要依赖自动翻译。批量校验的价值在于你不需要逐行看答案文档的花括号和箭头真值表全列出来哪些结论站得住脚一目了然。本文还有配套的精品资源点击获取