离散数学,自然推理系统,基于假言推理,不能使用消解法的自然推理系统

离散数学-假言推理系统

1. 问题描述

自然推理系统(基于假言推理,不能使用消解法的自然推理系统)。 跑通老师的例题占60分。提交word文档:包含源码截图,多组测试数据的运行结果截图。验收时:按助教小老师的要求输入测试数据,正确者给60分。 对老师代码的改进占40分。跟60分部分在同一 个word文档中,为什么改进? ,如何改进? ,改进代码截图。
验收时:给助教小老师讲解为什么,如何改?讲解清楚,, 回答问题正确,给分5-40分。由于每个助教只检查-道题,因此他非常清楚此题已经改进的地方。前3个改进者才有效得分。

2. 课本中的源码

(1) 抄写课本中的代码

  1. 先看着课本把代码全部编写完成,并编译保证代码能正常运行,源码如下

    #include <string>
    #include <iostream>
    #include <algorithm>
    #include <time.h>
    using namespace std;
    
    struct oTm {
    	string gs, gsLast, niyou; // 前件与后件及理由
    	int nLitter, nUsed, isLitter, isCond;
    };
    
    
    void nonoop2(string & aa) {
    	int i = 0, j = 0;
    	int len = aa.length();
    	while(i < len - 2) {
    		// 至少还有2个字符
    		if(((i + 1) < len) && (aa[i] == '!') && (aa[i + 1] == '!')) {
    			j = i;
    			aa = aa.substr(j + 2, len - 2);
    			break;
    		} else {
    			i++;
    		}
    	}
    }
    
    int setNiYou(struct oTm tmrec[], int np, string ny0, int j0, int j1, int nUsed0, int isCond0, int isLitter0) {
    	// 将字符串ny0与j0赋到推理意见中
    	string stmpj0, stmpj1;
    //	int nLen1 = 0, j = 0, nLenj0 = 0, nLenj1 = 0;
    //	nLen1 = ny0.length();
    	stmpj0 = to_string(j0 + 1);
    //	nLenj0 = stmpj0.length();
    	stmpj1 = to_string(j1 + 1);
    //	nLenj1 = stmpj1.length();
    	if(j0 == -1) {
    		// 原始前提
    		tmrec[np].niyou = ny0;
    	} else if(j1 == -1) {
    		//  由前一步所得结论
    		tmrec[np].niyou = "(" + stmpj0 + ")" + ny0;
    	} else {
    		// 由前二步推理所得
    		tmrec[np].niyou = "(" + stmpj0 + ")" + "(" + stmpj1 + ")" + ny0;
    	}
    	tmrec[np].nUsed = nUsed0; // 附加前提从未使用过
    	tmrec[np].isCond = isCond0; // 是条件式
    	tmrec[np].isLitter = isLitter0; // 是文字
    }
    
    int inputPrimary(struct oTm gs0[]) {
    	struct oTm tmp;
    	string pstate;
    	string ny0 = "前提条件";
    	string ny1 = "原命题的逆否命题";
    	string ny2 = "双条件导出的条件式";
    	string ny3 = "析取式转换为条件式";
    	int i = 0, j = 0, nLen = 0, k = 0;
    	int i0 = 0; // 原始条件
    	printf("输完一个前提条件请按回车,不输直接按回车则结束\n 析 +, 合*, 条-,双=,否定!\n");
    	while(1) {
    		getline(cin, pstate);
    		nLen = pstate.length();
    		if(nLen == 0) {
    			break;
    		}
    		// 设置nUsed, isLitter, isCond, nLitter的值
    		// 判断是否为文字
    		if(nLen == 1) {
    			// 标注单个文字
    			gs0[i].nLitter = nLen;
    			gs0[i].gs = pstate; // 前件
    			gs0[i].gsLast = ""; // 后件
    			setNiYou(gs0, i, ny0, -1, -1, 0, 0, 1);
    			// 前提类型, 无, 无, 未使用, 不是条件式, 是文字
    		} else if((nLen == 2) && (pstate[0] == '!')) {
    			// 标注!p
    			gs0[i].nLitter = nLen;
    			gs0[i].gs = pstate; // 前件
    			gs0[i].gsLast = ""; // 后件
    			setNiYou(gs0, i, ny0, -1, -1, 0, 0, 1);
    		} else {
    			for(j = 0; j < nLen; j++) {
    				if(pstate[j] == '-') {
    					// 标注条件式 p - q
    					gs0[i].nLitter = pstate.length();
    					gs0[i].gs = pstate.substr(0, j); // 复制前件
    					gs0[i].gsLast = pstate.substr(j + 1, nLen); // 复制后件
    					setNiYou(gs0, i, ny0, -1, -1, 0, 1, 0); // 前提,是条件,不是文字
    					// 产生逆否条件 !q-!p
    					i++;
    					gs0[i].nLitter = gs0[i - 1].nLitter;
    					gs0[i].gsLast = "!" + pstate.substr(0, j);
    					nonoop2(gs0[i].gsLast);
    
    					// 复制前件
    					gs0[i].gs = "!" + pstate.substr(j + 1, nLen);
    					nonoop2(gs0[i].gs);
    					setNiYou(gs0, i, ny1, i-1, -1, 0, 1, 0); // 前提,是条件,不是文字
    					break;
    				} else if(pstate[j] == '=') {
    					// 标注双条件式
    					// 先保存双条件
    					gs0[i].nLitter = pstate.length();
    					// 保存全部
    					gs0[i].gs = pstate;
    					gs0[i].gsLast = "";
    					setNiYou(gs0, i, ny0, -1, -1, 0, 0, 0); // 前提,不是条件,不是文字
    					// p-q
    					i++;
    					// 复制前件
    					gs0[i].nLitter = pstate.length();
    					gs0[i].gs = pstate.substr(0, j);
    					// 复制后件
    					gs0[i].gsLast = pstate.substr(j + 1, nLen);
    					setNiYou(gs0, i, ny2, i-1, -1, 0, 1, 0); // 前提,是条件,不是文字
    					
    					// 产生逆否条件 !q-!p
    					i++;
    					gs0[i].nLitter = gs0[i - 1].nLitter;
    					gs0[i].gsLast = "!" + pstate.substr(0, j);
    					nonoop2(gs0[i].gsLast);
    
    					// 复制前件
    					gs0[i].gs = "!" + pstate.substr(j + 1, nLen);
    					nonoop2(gs0[i].gs);
    					setNiYou(gs0, i, ny1, i-1, -1, 0, 1, 0); // 前提,是条件,不是文字
    					
    					// q-p
    					i++;
    					// 复制前件
    					gs0[i].nLitter = pstate.length();
    					gs0[i].gsLast = pstate.substr(0, j);
    					// 复制后件
    					gs0[i].gs = pstate.substr(j + 1, nLen);
    					setNiYou(gs0, i, ny2, i-2, -1, 0, 1, 0); // 前提,是条件,不是文字
    					
    					// 产生逆否条件 !p-!q
    					i++;
    					gs0[i].nLitter = gs0[i - 1].nLitter;
    					gs0[i].gs = "!" + pstate.substr(0, j);
    					nonoop2(gs0[i].gs);
    
    					// 复制前件
    					gs0[i].gsLast = "!" + pstate.substr(j + 1, nLen);
    					nonoop2(gs0[i].gsLast);
    					setNiYou(gs0, i, ny1, i-1, -1, 0, 1, 0); // 前提,是条件,不是文字
    					break;
    				} else if(pstate[j] == '+') {
    					// 标注析取式 p+q
    					// 保存析取式
    					gs0[i].nLitter = pstate.length();
    					// 保存全部
    					gs0[i].gs = pstate;
    					gs0[i].gsLast = "";
    					setNiYou(gs0, i, ny0, -1, -1, 0, 0, 0); // 前提,不是条件,不是文字
    					// !p-q
    					i++;
    					// 复制前件
    					gs0[i].nLitter = pstate.length();
    					gs0[i].gs = "!" + pstate.substr(0, j);
    					// 复制后件
    					gs0[i].gsLast = pstate.substr(j + 1, nLen);
    					setNiYou(gs0, i, ny3, i-1, -1, 0, 1, 0); // 前提,是条件,不是文字
    					nonoop2(gs0[i].gs);
    
    					// !q-p
    					i++;
    					// 复制前件
    					gs0[i].nLitter = pstate.length();
    					gs0[i].gsLast = pstate.substr(0, j);;
    					// 复制后件
    					gs0[i].gs[0] = '!';
    					gs0[i].gs = "!" + pstate.substr(j + 1, nLen);
    					setNiYou(gs0, i, ny3, i-2, -1, 0, 1, 0); // 前提,是条件,不是文字
    					nonoop2(gs0[i].gs);
    					break;
    				}
    			}
    			if(j >= nLen) { // 不是条件式,也不是文字, 则是普通条件
    				gs0[i].nLitter = pstate.length();
    				// 保存全部
    				gs0[i].gs = pstate;
    				gs0[i].gsLast = "";
    				setNiYou(gs0, i, ny0, -1, -1, 0, 0, 0); // 前提,不是条件,不是文字
    			}
    		}
    		i++;// 当前公式处理完之后,指针i的值增1
    	}
    	nLen = i; // 按字符串长度排序
    //	for(i = 0; i < nLen - 1; i++) {
    //		k = i;
    //		for(j = i + 1; j < nLen; j++) {
    //			if(gs0[k].nLitter > gs0[j].nLitter) {
    //				k = j;
    //			}
    //		}
    //		if(k > i) {
    //			tmp = gs0[i];
    //			gs0[i] = gs0[k];
    //			gs0[k] = tmp;
    //		}
    //	}
    	return nLen;
    }
    
    void printYsh(struct oTm tmrec[], int np) {
    	int i = 0;
    	for(i = 0; i < np; i++) {
    		if(tmrec[i].isLitter == 1) {
    			printf("(%d)\t%s为真\t\t\t%s---文字\n", i+1, tmrec[i].gs.c_str(), tmrec[i].niyou.c_str());
    		} else if(tmrec[i].isCond == 1) {
    			printf("(%d)\t%s-%s为真\t\t\t%s---条件式\n", i+1, tmrec[i].gs.c_str(), tmrec[i].gsLast.c_str(), tmrec[i].niyou.c_str());
    		} else {
    			printf("(%d)\t%s为真\t\t\t%s\n", i+1, tmrec[i].gs.c_str(), tmrec[i].niyou.c_str());
    		}
    	}
    }
    
    void printStruct(struct oTm tmrec[], int np) {
    	for(int i = 0; i < np; i++) {
    		cout << "{\n";
    		cout << "\tgs:" << tmrec[i].gs << "\n";
    		cout << "\tgsLast:" << tmrec[i].gsLast << "\n";
    		cout << "\tniyou:" << tmrec[i].niyou << "\n";
    		cout << "\tnLitter:" << tmrec[i].nLitter << "\n";
    		cout << "\tnUsed:" << tmrec[i].nUsed << "\n";
    		cout << "\tisLitter:" << tmrec[i].isLitter << "\n";
    		cout << "\tisCond" << tmrec[i].isCond << "\n";
    		cout << "}\n\n";
    	}
    }
    
    int main() {
    	struct oTm gs0[100]; // 推理前提条件
    	string result0; // 结论
    	struct oTm tmrec[1024]; // 最多1000步
    	string stmp;
    	string lastNiYou = " "; // 上个推理式的理由
    	string ny01 = "假言推理";
    	int i = 0, j = 0, k = 0;
    	int np = 1, np0 = 0, isOk = 0;
    	int i0 = 0, nPosLitter = 0, nPosCond = 0; // 文字起始位置,首个文字的位置,条件的位置
    
    	// 输入前提条件
    	np0 = inputPrimary(gs0);
    	// 输入结论
    	printf("输入推理式的结论,结论只是文字,\n 若是条件式、析取式请先手工转换为条件,将前件作为附加前提:\n");
    	getline(cin, result0);
    	for(i = 0; i < np0; i++) {
    		// 所有原始公式转抄到tmrec中
    		tmrec[i] = gs0[i];
    	}
    
    	np = i; // 推理队列的尾部指针
    	nPosLitter = 0; // 文字的位置号
    	nPosCond = 0; // 条件的位置号
    	isOk = 0;
    	i0 = -1;
    	while(1) {
    		i = i0 + 1; // 寻找下一个文字,i是起始位置,np是命令串的长度
    		while((i < np) && (tmrec[i].isLitter != 1)) {
    			i++;
    		}
    		if(i > np) {
    			break; // 找不到文字就没法推理了
    		}
    		i0 = i;
    		nPosLitter = i; // 记录文字的起始位置
    		stmp = tmrec[i].gs;// 保存当前的内容
    		np0 = np - 1;
    		while(np > np0) {
    			np0 = np;
    			for(i = 0; i < np; i++) {
    				// 找到一个没有用过的条件式
    				if((tmrec[i].isCond == 1) && (tmrec[i].nUsed == 0)) {
    					break;
    				}
    			}
    			if(i == np) {
    				break; // 没有找到则结束推理,所有条件式都用到了
    			}
    			while(i < np) {
    				// 若找到了这样的条件
    				if(tmrec[i].isCond == 1) {
    					// 若是条件式
    					if((lastNiYou != tmrec[i].niyou) ||
    					        ( lastNiYou == tmrec[i].niyou &&
    					          (tmrec[i].niyou[0] != '(') ) ) {
    						// 与上条命令的来源不同,
    						// 或者但是同为前提条件是可以的,即首个字符不是(
    						if(tmrec[i].gs == stmp) {
    							// 条件式的前件与文字相等
    							lastNiYou = tmrec[i].niyou;
    							tmrec[nPosLitter].nUsed++; // 这个文字用过一次了
    							tmrec[i].nUsed++; // 这个条件用过一次了
    							stmp = tmrec[i].gsLast; // 将次结果存到推到序列中
    							tmrec[np].gs = stmp;// 将推出来的结果保存起来
    							tmrec[np].gsLast[0] = '\0'; // 后件清空, 保存当前条件
    							// 前提类型,有,无,未使用,不是条件式,是文字
    							setNiYou(tmrec, np, ny01, nPosLitter, i, 0, 0, 1);
    							nPosLitter = np; // 记录当前文字的序号
    							np++;
    							// 推出结论同条原始条件的下一轮
    							if(result0 == stmp) {
    								isOk = 1;
    								break;
    							}
    
    						}
    					}
    				}
    				i++; // 判断下一个表达式是否为条件,是否为可推理的条件式
    			}
    			if(isOk == 1) {
    				break;
    			}
    		}
    		if(isOk == 1) {
    			break;
    		}
    	}
    	if(isOk == 1) {
    		printf("success,推理过程如下:\n");
    	} else {
    		printf("failed,推理过程如下:\n");
    	}
    //	printStruct(tmrec, np);
    	printYsh(tmrec, np);
    }
    
  2. 需要注意的是,setNiYou 函数应写在 inputPrimary 函数的前面,因为 inputPrimary 函数里面会用到它,如果写在后面会报 函数未定义 的错误导致程序无法运行;

(2) 理解程序的运行逻辑

该程序运行的大致流程如下:

  1. 首先用户输入每个前提
  2. 当用户输入完一个前提时就先处理该前提
    1. 如果输入的内容的长度为1,直接保存起来,标注为普通的文字,保存其理由
    2. 如果是长度为2,并且第一个字符为!时,直接保存起来,当作普通文字,保存其理由
    3. 以上两条都不是是,逐个遍历每一个字符,查找运算符
    4. 如果包含字符- 表示是一个条件式,将其前件后件拆开来保存,产生逆否命题后拆开来保存,保存其理由
    5. 如果包含字符=表示是一个双条件式,转换成两个条件式后将前件后件分开保存,保存其理由
    6. 如果包含字符+表示是一个析取式,将析取式转换成条件式保存,将其逆否命题也保存起来,保存其理由
    7. 如果以上都不是将输入的文字全部保存
  3. 当用户什么都没有输入时不再进行以上步骤
  4. 用户输入完成后将刚才保存起来的所有前提按照长度进行排序
  5. 输入需要推出的结论
  6. 开始保存假言推理
    1. 从已经保存的前提中找到一个未使用过的条件式
    2. 再找到一个未使用过的文字
    3. 如果该文字与条件式的前件相同记录一次假言推理,推出条件式的后件为真,将其保存起来
    4. 重复以上步骤直到找不到尚未使用过的普通文字或者已经推出了结论为止
  7. 以上步骤结束之后开始打印,打印步骤号、输入及推理出来的表达式、理由等信息

以上就是程序的基本运行逻辑。

(3) 优化方案

  1. 改进1:把 setNiYou 函数应写在 inputPrimary 函数的前面,因为 inputPrimary 函数里面会用到它,如果写在后面会报 函数未定义 的错误导致程序无法运行

    修改前:

    int inputPrimary(struct oTm gs0[]) {
    	// 这里是函数内部的代码
    }
    
    int setNiYou(struct oTm tmrec[], int np, string ny0, int j0, int j1, int nUsed0, int isCond0, int isLitter0) {
    	// 这里是函数内部的代码
    }
    

    修改后:

    int setNiYou(struct oTm tmrec[], int np, string ny0, int j0, int j1, int nUsed0, int isCond0, int isLitter0) {
    	// 这里是函数内部的代码
    }
    
    int inputPrimary(struct oTm gs0[]) {
    	// 这里是函数内部的代码
    }
    
  2. 改进2:从C到C++,为什么改进?因为用C++编写程序相对于用C编写程序来说更方便,改进如下:

    1. 将所有的 char * 类型改写成 string 类型

      // 使用string 类型
      string gs, gsLast, niyou;
      
      // 而不是使用 char * 类型
      char gs[120], gsLast[120], niyou[120];
      
      // 整个源码中的所有地方都要替换
      
    2. 截取字符串时使用 substr 函数,而不是用 for 循环

      // 使用substr
      gs0[i].gs = pstate.substr(0, j); // 复制前件
      
      // 而不是for循环
      for(k = 0; k < j; k++) { // 复制前件
          gs0[i].gs[k] = pstate[k];
      }
      
      // 整个源码中的所有地方都要替换
      
    3. 使用相等运算符 == ,不用 strcmp 函数

      // 使用==或!=
      lastNiYou != tmrec[i].niyou
      
      // 而不是strcmp
      strcmp(lastNiYou, tmrec[i].niyou) != 0
          
      // 整个源码中的所有地方都要替换
      
    4. 使用赋值运算符 = ,不用 strcpy 函数

      // 使用=
      stmp = tmrec[i].gs;// 保存当前的内容
      
      // 而不是strcpy
      strcpy(stmp, tmrec[i].gs);// 保存当前的内容
      
      // 整个源码中的所有地方都要替换
      
  3. 改进3:去掉一些声明了但从未使用过的变量,因为我们不需要这些变量因此不需要声明它们

    // 例如该函数里面的 nLen1, j, nLenj0, nLenj1 等变量从未使用过,需要将其注释掉或删掉
    int setNiYou(struct oTm tmrec[], int np, string ny0, int j0, int j1, int nUsed0, int isCond0, int isLitter0) {
    	string stmpj0, stmpj1;
    //	int nLen1 = 0, j = 0, nLenj0 = 0, nLenj1 = 0;
    //	nLen1 = ny0.length();
    	stmpj0 = to_string(j0 + 1);
    //	nLenj0 = stmpj0.length();
    	stmpj1 = to_string(j1 + 1);
    //	nLenj1 = stmpj1.length();
    	
        // 该函数的其他代码
    }
    
  4. 改进4:将结构体名 tm 改为 oTm (ownTm),因为结构体名 tm已经存在于C++的头文件 time.h 中,如果之后引入改头文件会导致程序运行错误

    在这里插入图片描述

    // 修改前
    struct tm {
    	string gs, gsLast, niyou;
    	int nLitter, nUsed, isLitter, isCond;
    };
    
    // 修改后
    struct oTm {
    	string gs, gsLast, niyou;
    	int nLitter, nUsed, isLitter, isCond;
    };
    
  5. 改进5:双条件式产生两个条件式后,把它们的逆否命题也保存起来,因为有些时候需要用到它们,原程序中并未写出双条件式的两个条件式的逆否命题

    // 在inputPrimary函数的判断双条件式的if代码块里添加以下代码
    // 产生逆否条件 !q-!p
    i++;
    gs0[i].nLitter = gs0[i - 1].nLitter;
    gs0[i].gsLast = "!" + pstate.substr(0, j);
    nonoop2(gs0[i].gsLast);
    gs0[i].gs = "!" + pstate.substr(j + 1, nLen);
    nonoop2(gs0[i].gs);
    setNiYou(gs0, i, ny1, i-1, -1, 0, 1, 0); 
    // 产生逆否条件 !p-!q
    i++;
    gs0[i].nLitter = gs0[i - 1].nLitter;
    gs0[i].gs = "!" + pstate.substr(0, j);
    nonoop2(gs0[i].gs);
    gs0[i].gsLast = "!" + pstate.substr(j + 1, nLen);
    nonoop2(gs0[i].gsLast);
    setNiYou(gs0, i, ny1, i-1, -1, 0, 1, 0); 
    
  6. 改进6:去掉用户输入完之后的排序,因为按照文字长度排序会导致打印后的推理理由顺序混乱,并且该排序不是必要的,所以我们不需要这个排序

    在这里插入图片描述

    // 将inputPrimary函数最后面的排序注释掉或者删掉
    //	for(i = 0; i < nLen - 1; i++) {
    //		k = i;
    //		for(j = i + 1; j < nLen; j++) {
    //			if(gs0[k].nLitter > gs0[j].nLitter) {
    //				k = j;
    //			}
    //		}
    //		if(k > i) {
    //			tmp = gs0[i];
    //			gs0[i] = gs0[k];
    //			gs0[k] = tmp;
    //		}
    //	}
    

    在这里插入图片描述

(4) 测试

  1. 42页 例题1.6.1: A, A→B ⇒ B
    在这里插入图片描述

  2. 43页 例题1.6.3: A→B, B→C, A ⇒ C
    在这里插入图片描述

  3. 44页 例题1.6.4: A→B, C→D, A∨C, ¬B ⇒ D
    在这里插入图片描述

3. 我编写的假言推理系统

理解课本中杨老师给出的代码的基本思路之后,我也进行思考分析,自己用 html + JavaScript 写出了一个核心思路与原程序差不多的一个新的假言推理系统

(1) 程序逻辑思考过程

  1. 例如这个表达式 A→B, B→C, A ⇒ C ,如果一个人看到这个表达式会如何进行思考?怎么样从前提中推出结论 C ?我想应该是这样的:
    1. 首先从前提中找到与结论 C 有关的前提 B→C ,要推出 C 为真需要先推出条件式的前件 B 为真
    2. 再从前提中找到与 B 有关的前提 A→B ,要推出 B 为真需要先推出条件式的前件 A 为真
    3. 再从前提中找到与 A 有关的前提,我们直接找到了前提 A ,因此从 A 开始推理
    4. AA→B 为真时 B 为真
    5. BB→C 为真时 C 为真
  2. 就这样我推理出了 C 为真
  3. 我们可以把以上的思路转化成程序
  4. 我们不难发现以上的思路在程序中类似与一个递归过程

(2) 代码

在注释处指出了各个函数的作用、运行过程,各个变量的含义等等

1. 核心逻辑,js代码:
/**
 * 假言推理
 * @param {string} str 前提,每一个前提以换行符'\n'分隔
 * @param {string} target 结论
 * @returns {object} {推理是否成功,推理思路,写在一行的前提条件,结论}
 */
function main(str, target) { // 用main包裹main1, 产生闭包
    let premises = [] // 前提
    let steps = [] // 步骤
    let tuilisilu = '' // 推理思路

    // 增加一步
    function setSteps(index, str, reason) {
        steps.push({
            index,
            str,
            reason
        })
    }

    let globalI = 1; // 每一步的步骤
    let globalLastIndex = 1; // 上一个推理结论的编号

    // 条件运算符
    function rightArrow(me, target, cb, otherOp = false) {
        // 如果t在右边,则需要先推理左边
        me = me.replaceAll('!!', '')
        let arr = me.split('-')
        let bool = false
        if (target == arr[1]) { // 如果是后件,先帮我推理前件
            bool = cb(arr[0])
            if (bool) {
                !otherOp && setSteps(globalI++, me, '前提')
                return true
            }
        } else { // 如果是前件,利A-B=!B-!A将其华安成后件,并先推理!B
            bool = cb(('!' + arr[1]))
            if (bool) {
                setSteps(globalI++, me, '前提')
                setSteps(globalI++, `!${arr[1]}-!${arr[0]}`, `(${globalI - 2})原命题等于逆否命题p-q⇒!q-!p`)
                let s = `!${arr[1]}-!${arr[0]}`
                if (s.includes('!!')) {
                    setSteps(globalI++, s.replaceAll('!!', ''), `(${globalI - 2})的等值式!!p⇒p`)
                }
                return true
            }
        }
        return false
    }

    // 析取运算符
    function plus(me, target, cb) {
        let arr = me.split('+')
        let bool = false
        let t = ''
        if (arr[1] == target) { // A+B=B+A,保证目标在条件式的右边
            t = `!${arr[0]}-${arr[1]}`
        } else {
            t = `!${arr[1]}-${arr[0]}`
        }
        bool = rightArrow(t, target, cb, true)
        if (bool) {
            t = t.replaceAll('!!', '')
            setSteps(globalI++, me, '前提')
            setSteps(globalI++, t, `(${globalI - 2})条件式的等值式p+q⇒!p-q`)
            return true
        }
        return false
    }

    // 普通命题变元
    function emptyOp(me, target) {
        if(me != target) {
            return false
        }
        setSteps(globalI++, me, '前提');
        return true
    }

    // 合取运算符
    function hequ(me, target, cb) {
        let arr = me.split('*')
        setSteps(globalI++, me, '前提')
        if (arr[0] == target) { // 目标在左边时,先推理右边,再推理左边
            setSteps(globalI++, arr[1], `(${globalI - 2})及p*q⇒p,q`)
            setSteps(globalI++, arr[0], `(${globalI - 3})及p*q⇒p,q`)
        } else { // 目标在右边时,先推理左边,再推理右边
            setSteps(globalI++, arr[0], `(${globalI - 2})及p*q⇒p,q`)
            setSteps(globalI++, arr[1], `(${globalI - 3})及p*q⇒p,q`)
        } // 目的是保证目标是在该运算符的最后一步得到的
        return true
    }

    // 双条件
    function doubleArrow(me, target, cb) {
        let arr = me.split('=')
        let bool = false
        if (arr[0] == target) { // 双条件式的前件是目标,将顺序调换过来,从B-A得到A
            bool = rightArrow(`${arr[1]}-${arr[0]}`, target, cb, true)
            setSteps(globalI++, me, '前提')
            setSteps(globalI++, `${arr[1]}-${arr[0]}`, `(${globalI - 2})及p=q⇒p-q,q-p`)
        } else { // 否则从A-B得到B
            bool = rightArrow(`${arr[0]}-${arr[1]}`, target, cb, true)
            setSteps(globalI++, me, '前提')
            setSteps(globalI++, `${arr[0]}-${arr[1]}`, `(${globalI - 2})及p=q⇒p-q,q-p`)
        }
        return bool
    }

    // 运算符集
    let operators = [
        {
            name: '=',
            run: doubleArrow
        },
        {
            name: '*',
            run: hequ
        },
        {
            name: '-',
            run: rightArrow
        },
        {
            name: '+',
            run: plus
        },
        {
            name: '',
            run: emptyOp
        }
    ]

    // 解析前提
    function parsePremises(str) {
        let operator = operators.find(o => o.name != '' && str.includes(o.name));
        return {
            str,
            operator: operator ? operator.name : '',
            visited: false
        }
    }

    /**
     * 从前提推出目标
     * @param {object} tPre 前提
     * @param {string} target 目标
     * @param {function} run 推理函数,回调
     * @returns {boolean} 是否推理成功
     */
    function mp(tPre, target, run) {
        target = target.replaceAll('!!', '')
        // console.log(333, tPre)
        tuilisilu += `,推出结论${target}可能会用到前提 ${tPre.str}\n` // 记录推理思路
        tPre.visited = true;
        let op = operators.find(o => o.name == tPre.operator); // 找到运算符对象
        let bool = op.run(tPre.str, target, run) // 执行运算符,得到target结论
        if (bool && (tPre.operator != '' && tPre.operator != '*')) {
            // 如果是条件式那就做一次假言推理
            setSteps(globalI++, target, `(${globalLastIndex})(${globalI - 2})及假言推理p,p-q⇒q`)
            globalLastIndex = globalI - 1;
            return true
        } else if (bool) {
            // 否则我们上一个推理结果要求在这一步得到了
            globalLastIndex = globalI - 1;
            return true
        }
        return false
    }

    /**
     * 从前提中找出符合推理目标的前提并进行推理
     * @param {string} target 需要推理出的结论
     * @returns {boolean} 是否推理成功
     */
    function run(target) {
        tuilisilu += `需要推出结论 ${target} 为真` // 记录推理思路
        // console.log(target)
        let tPre = premises.filter(p => !p.visited && p.str == target); // 是否有完全相等的前提
        if(!tPre.length) { tuilisilu += `\n暂时无法从前提中直接推出 ${target},需要用其他前提` } // 记录推理思路
        if (!tPre.length) { // 如果没有找到符合要求的前提
            tPre = premises.filter(p => !p.visited && p.str.includes(target)); // 是否有包含目标的前提
        }
        if (!tPre.length && target.includes('!')) { // 还没找到符合要求的前提
            let nTarget = target.replaceAll('!', '')
            tPre = premises.filter(p => !p.visited && p.str.includes(nTarget)); // 是否有去掉!之后包含目标的前提
        }
        let bool = false // 是否推理成功
        if (!tPre.length) { // 没有找到符合要求的前提
            bool = false // 推理失败
        } else if (tPre.length == 1) { // 只有一个前提
            bool = mp(tPre[0], target, run) // 进行假言推理
        } else if (tPre.length > 1) { // 有多个前提
            for (let i = 0; i < tPre.length; i++) { // 对每一个前提进行假言推理
                bool = mp(tPre[i], target, run) // 看看其中一个是否能推出结论
                if (bool) { // 如果推出结论了就不用再推理了
                    break
                } else {
                    tPre[i].visited = false
                }
            }
        }
        if(!tPre.length && !bool) { tuilisilu += `,从前提条件中无法推出 ${target}` } // 记录推理思路
        return bool
    }

    function main1(str, target) {
        let arr = str.split('\n') // 将输入的文字以换行符分割
        let expInOneLine = '' // 存放在一行的表达式,将所有的表达式拼接到一起
        for (let i = 0; i < arr.length; i++) {
            if (i + 1 != arr.length) {
                expInOneLine += arr[i].trim() + ', '
            } else {
                expInOneLine += arr[i].trim() + ' '
            }
            premises.push(parsePremises(arr[i].trim())) // 解析该前提并存到前提列表中
        }

        let bool = run(target) // 开始假言推理,需要推理出target,返回true表示推理成功

        return {
            success: bool,
            steps,
            tuilisilu,
            expInOneLine,
            target
        }
    }

    return main1(str, target)
}
2. 页面的构成和页面逻辑,html + js代码:
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="./index.css">
    <title>假言推理系统</title>
</head>

<body>
    <!-- 居中容器 -->
    <div id="main">
        <h1>假言推理</h1>
        <p class="tip">
            <span class="svg-alert" style="color: #fc8800;"></span> 提示<br>
            每一行为一个前提条件<br>
            各个逻辑运算符用以下符号代替:析取(+)、合取(*)、条件(-)、双条件(=)<br>
            结论仅支持输入单个命题变元,如果结论不是单个命题变元时你可以做以下的处理:<br>
            (1)条件式 p→q 时将前件 p 作为附加前提写到前提条件列表中,推出结论 q ;<br>
            (2)合取式 p∧q 时先推出结论 p,再推出结论 q ;<br>
            (3)析取式 p∨q 时利用性质 p∨q⇒¬p→q ,再使用步骤(1);<br>
            (4)双条件式 p↔q 时利用性质 (p→q)∧(q→p) ,再使用步骤(2);<br>
            <span class="close svg-close" onclick="document.querySelector('.tip').style.display='none';"></span>
        </p>
        <!-- 输入前提 -->
        <div class="inputs-area">
            <div>
                <span>前提 </span>
                <textarea type="text" autocomplete="off"></textarea>
            </div>
        </div>
        <!-- 输入结论 -->
        <div class="inputs-area">
            <div>
                <span>结论</span>
                <input autocomplete="off" id="target" type="text">
            </div>
        </div>
        <!-- 功能区 -->
        <div style="text-align: right;">
            <button id="btnClear">清空</button>
            <button class="blue">开始推理</button>
        </div>
        <!-- 推理过程 -->
        <h3>推理过程</h3>
        <div class="result">
            <p>尚未进行推理</p>
        </div>
        <!-- 习题集 -->
        <h3>习题集 <button id="btnTuiliAll">推理全部</button></h3>
        <div class="testItems"></div>
    </div>
    <!-- 引入假言推理js代码 -->
    <script src="./sy2.js"></script>
    <!-- 页面逻辑代码 -->
    <script>
        let btnStart = document.querySelector('.blue');
        // 开始推理按钮按下
        btnStart.onclick = () => {
            // 获取页面中的两个输入框内容
            let str = document.querySelector('.inputs-area textarea').value;
            let target = document.querySelector('#target').value;
            // 验证非空性
            if (str.length == 0 || target.length == 0) {
                alert('请输入前提和结论');
                return;
            }
            // 调用推理函数
            let obj = main(str, target);
            // 显示推理结果
            document.querySelector('.result').innerHTML = getHtmlByResult(obj);
        }

        // 按照推理结果拼接字符串
        function getHtmlByResult(obj) {
            // 开始拼接页面上显示的内容
            let resStr = '';
            console.log(obj)
            if (obj.success) { // 推理成功
                resStr = `<p>推理成功!推理过程如下:<P>`;
                resStr += `<p>${obj.expInOneLine}${obj.target}<P>`;
                obj.steps.forEach(i => {
                    resStr += `<p class="step"><span>(${i.index})</span><span>${i.str} 为真</span><span>${i.reason}</span></p>`;
                });
            } else { // 推理失败
                resStr = '<p>推理失败!推理思路如下:<P>';
                resStr += `<p>${obj.expInOneLine}${obj.target}<P>`;
                obj.tuilisilu.split('\n').forEach(p => {
                    resStr += `<p>${p}</p>`;
                })
            }

            // 替换-!=*+
            return resStr.replaceAll('-', '→')
                .replaceAll('!', '¬')
                .replaceAll('=', '↔')
                .replaceAll('*', '∧')
                .replaceAll('+', '∨').replaceAll('↔"', '="')
        }

        // 结论输入框中按回车时触发开始推理按钮
        document.querySelector('#target').onkeydown = (e) => {
            if (e.keyCode == 13) {
                btnStart.click();
            }
        }

        // 习题集
        let arrTestItems = [
            { str: 'A*B\nB-A\n!C-!B\n!C+D\nC+D\nD-x', target: 'x', origin: '测试-存在干扰项' },
            { str: 'y*A\nA-B\n!C-!B\n!C+D\n!E-D\n!x-!E', target: 'x', origin: '测试-较复杂的前提' },
            { str: 'A\nA-B', target: 'B', origin: '42页 例题1.6.1' },
            { str: 'A*B', target: 'A', origin: '42页 例题1.6.2' },
            { str: 'A*B', target: 'B', origin: '42页 例题1.6.2' },
            { str: 'A-B\nB-C\nA', target: 'C', origin: '43页 例题1.6.3' },
            { str: 'A-B\nC-D\nA+C\n!B', target: 'D', origin: '44页 例题1.6.4' },
            { str: 'A-B\nC-D\n!B+!D\nA', target: '!C', origin: '45页 例题1.6.5' },
            { str: 'M+R\nM-C\nC-!F\nF', target: 'R', origin: '48页 例题1.6.8' },
            { str: 'A-B\n!B', target: '!A', origin: '49页 习题1.(1)' },
            { str: 'A+B\n!B', target: 'A', origin: '49页 习题1.(2)' },
            { str: 'A=B\nB=C\nA', target: 'C', origin: '49页 习题1.(3)(1)' },
            { str: 'A=B\nB=C\nC', target: 'A', origin: '49页 习题1.(3)(2)' },
            { str: '!p+q\n!q+r\nr-s\np', target: 's', origin: '49页 习题2.(1)' },
            { str: 'p-!q\n!q*!r\nr', target: '!p', origin: '49页 习题2.(4)' },
            { str: 'p-q\np', target: 'p', origin: '49页 习题2.(5)(1)' },
            { str: 'p-q\np', target: 'q', origin: '49页 习题2.(5)(2)' },
            { str: 'q-p\nq=s\ns=t\nt=r\nr', target: 'p', origin: '49页 习题2.(6)(1)' },
            { str: 'q-p\nq=s\ns=t\nt=r\nr', target: 'q', origin: '49页 习题2.(6)(2)' },
            { str: 'p-r\nq-s\np*q', target: 'r', origin: '49页 习题2.(7)(1)' },
            { str: 'p-r\nq-s\np*q', target: 's', origin: '49页 习题2.(7)(2)' },
            { str: '!p+r\n!q+s\np*q\nt', target: 'r', origin: '49页 习题2.(8)(1)' },
            { str: '!p+r\n!q+s\np*q\nt', target: 's', origin: '49页 习题2.(8)(2)' },
            { str: 'p-!q\n!r+q\nr*!s', target: '!p', origin: '49页 习题2.(11)(1)' },
            { str: 'p+q\np-r\nq-s\n!r', target: 's', origin: '49页 习题2.(12)(2)' },
            { str: 'p-q\n!q+!r\nr', target: '!p', origin: '68页 习题(4)' }
        ]


        // 习题集区域中的推理按钮按下
        function tuili(i) {
            document.querySelector('.inputs-area textarea').value = arrTestItems[i].str
            document.querySelector('#target').value = arrTestItems[i].target
            btnStart.click()
        }

        // 拼接习题集区域的内容
        let testItemsStr = '';
        arrTestItems.forEach((i, index) => {
            testItemsStr += `
            <p>
                <span>${index + 1}</span>
                <span>${i.origin}</span>
                <span>${i.str.replaceAll('\n', ', ')
                    .replaceAll('-', '→')
                    .replaceAll('!', '¬')
                    .replaceAll('=', '↔')
                    .replaceAll('*', '∧')
                    .replaceAll('+', '∨') + ' ⇒ ' + i.target}</span>
                <button οnclick="tuili(${index})">推理</button>   
            </p>
            `;
        })
        document.querySelector('.testItems').innerHTML = testItemsStr;

        // 清空按钮按下
        document.querySelector('#btnClear').onclick = () => {
            document.querySelector('.inputs-area textarea').value = ''
            document.querySelector('#target').value = ''
        }

        // 推理全部 按钮按下
        document.querySelector('#btnTuiliAll').onclick = () => {
            let resStr = '';
            arrTestItems.forEach((i, index) => {
                resStr += `<h4>${index + 1}. ${i.origin}:</h4>`
                let obj = main(i.str, i.target)
                resStr += getHtmlByResult(obj)
            })
            document.querySelector('.result').innerHTML = resStr;
        }

        // 往页面插入svg图标
        let svgs = {
            '.svg-close': `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" focusable="false" aria-hidden="true"><path d="M17.6568 19.7782C18.2426 20.3639 19.1924 20.3639 19.7782 19.7782C20.3639 19.1924 20.3639 18.2426 19.7782 17.6568L14.1213 12L19.7782 6.34313C20.3639 5.75734 20.3639 4.8076 19.7782 4.22181C19.1924 3.63602 18.2426 3.63602 17.6568 4.22181L12 9.87866L6.34313 4.22181C5.75734 3.63602 4.8076 3.63602 4.22181 4.22181C3.63602 4.8076 3.63602 5.75734 4.22181 6.34313L9.87866 12L4.22181 17.6568C3.63602 18.2426 3.63602 19.1924 4.22181 19.7782C4.8076 20.3639 5.75734 20.3639 6.34313 19.7782L12 14.1213L17.6568 19.7782Z" fill="currentColor"></path></svg>`,
            '.svg-alert': `<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" width="1em" height="1em" focusable="false" aria-hidden="true"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.2268 2.3986L1.52616 19.0749C0.831449 20.4064 1.79747 22 3.29933 22H20.7007C22.2025 22 23.1686 20.4064 22.4739 19.0749L13.7732 2.3986C13.0254 0.965441 10.9746 0.965442 10.2268 2.3986ZM13.1415 14.0101C13.0603 14.5781 12.5739 15 12.0001 15C11.4263 15 10.9398 14.5781 10.8586 14.0101L10.2829 9.97992C10.1336 8.93495 10.9445 8.00002 12.0001 8.00002C13.0556 8.00002 13.8665 8.93495 13.7172 9.97992L13.1415 14.0101ZM13.5001 18.5C13.5001 19.3284 12.8285 20 12.0001 20C11.1716 20 10.5001 19.3284 10.5001 18.5C10.5001 17.6716 11.1716 17 12.0001 17C12.8285 17 13.5001 17.6716 13.5001 18.5Z" fill="currentColor"></path></svg>`,
        }
        for (const key in svgs) {
            document.querySelector(key).innerHTML = svgs[key];
        }
    </script>
</body>

</html>
3. 页面美化,css代码:
* {
    box-sizing: border-box;
}
/* 整体水平居中 */
#main {
    width: 1200px;
    margin: 0 auto;
}

/* 一些元素的默认样式 */
p {
    margin: 20px 0;
}

h3 {
    border-bottom: 1px solid #ccc;
}

/* 输入框区域 */
.inputs-area {
    margin: 20px;
}

.inputs-area div {
    display: flex;
    /* align-items: center; */
}

.inputs-area div span {
    width: 80px;
    margin-right: 20px;
    font-size: 20px;
    text-align: right;
}

/* 按钮 */
button {
    background-color: #f4f5f5;
    color: #1c1f23cc;
    border: none;
    outline: none;
    padding: 5px 20px;
    border-radius: 5px;
    cursor: pointer;
    font-size: 20px;
}

button:hover {
    background-color: #eceded;
}

button:active {
    background-color: #e4e4e5;
}

/* 蓝色按钮 */
button.blue {
    background-color: #0064fa;
    color: #fff;
}
button.blue:hover {
    background-color: #0062d6;
}
button.blue:active {
    background-color: #004fb3;
}

/* 输入框 */
input,textarea {
    width: 100%;
    height: 40px;
    border: 1px solid #ccc;
    border-radius: 5px;
    padding: 10px;
    background-color: #f4f5f5;
    outline: none;
    border: 1px solid transparent;
    font-size: 20px;
    font-family: '宋体';
}

textarea {
    height: 180px;
}

input:hover,textarea:hover {
    background-color: #eceded;
}

input:focus,textarea:focus {
    background-color: #f4f5f5;
    border: 1px solid #0064fa;
}

/* 推理结果区域 */
.result p, .testItems p {
    font-family: '宋体';
    font-size: 20px;
}

.result .step {
    display: grid;
    grid-template-columns: 1fr 4fr 15fr;
    justify-items: left;
}

/* 习题集区域 */
.testItems p {
    display: grid;
    grid-template-columns: 1fr 2fr 5fr 1fr;
    justify-items: left;
}

/* 提示 */
.tip {
    background-color: #fff8ea;
    padding: 10px;
    border-radius: 5px;
    color: #1c1f23cc;
    position: relative;
    border: 1px solid #fed998;
}

/* 提示右上角的关闭按钮 */
.close {
    position: absolute;
    right: 10px;
    top: 10px;
    cursor: pointer;
    user-select: none;
    width: 30px;
    height: 30px;
    border-radius: 5px;
    display: flex;
    justify-content: center;
    align-items: center;
}

.close:hover {
    background-color: #f4eee1;
}

(3) 关于程序

1. 运行逻辑

程序的运行过程如下:

在这里插入图片描述

  1. 先从前提列表中找出一个与结论有关的前提
  2. 判断该前提的运算符号
  3. 如果直接是我们要的结论,则推理成功,记录步骤该前提为真,继续进行往后的步骤
  4. 如果是单条件:
    1. 如果结论是条件式的后件,则将前件作为结论,递归推理
    2. 这个递归推理成功时,记录步骤该前提为真
    3. 如果结论是条件式的前件,则产生逆否命题,递归推理
    4. 这个递归推理成功时,记录步骤该前提为真,记录步骤原命题等于逆否命题
  5. 如果是双条件式:
    1. 如果结论是该双条件式的前件,产生一个前后件调换后的条件式(为了把结论放在后件),交给单条件式函数处理
    2. 这个条件式推理成功时,记录步骤该前提为真,记录步骤双条件转换单条件等值式
    3. 如果结论是该双条件式的后件,产生一个条件式,交给单条件式函数处理
    4. 这个条件式推理成功时,记录步骤该前提为真,记录步骤双条件转换单条件等值式
  6. 如果是析取式:
    1. 尽量的把结论放在析取式的右边
    2. 产生条件式,交给单条件式函数处理
    3. 这个条件式推理成功时,记录步骤该前提为真,记录步骤析取式转换单条件等值式
  7. 如果是合取式:
    1. 该前提为真时,合取式的两个命题变元都为真,推理成功
    2. 记录步骤该前提为真,记录步骤合取式为真时左右都为真
  8. 如果推理成功,则会再次回到各个运算符号的函数中,添加推理步骤,也就是以上提到过的记录步骤的过程
  9. 除了直接是前提和合取式的运算符外,该运算符推理成功时,记录步骤假言推理
2. 结果分析

程序运行后会返回这样的一个对象:

{
    "success": true, // 是否推理成功
    "steps": [ // 推理的每一步过程
        {
            "index": 1, // 推理步骤号
            "str": "A", // 表达式
            "reason": "前提" // 理由
        },
        {
            "index": 2,
            "str": "A-B",
            "reason": "前提"
        },
        {
            "index": 3,
            "str": "B",
            "reason": "(1)(2)及假言推理p,p-q⇒q"
        }
    ],
    "tuilisilu": "需要推出结论 B 为真\n暂时无法从前提中直接推出 B,需要用其他前提,推出结论B可能会用到前提 A-B\n需要推出结论 A 为真,推出结论A可能会用到前提 A\n", // 推理思路
    "expInOneLine": "A, A-B ", // 写在一行之内的前提列表
    "target": "B" // 结论
}

(4) 特色

  1. 好看及人性化的操作界面
    在这里插入图片描述

  2. 前提条件支持输入析取(+)、合取(*)、条件(-)、双条件(=)

  3. 推理成功后,非常适合阅读的表达式(用→∧∨⇒↔等符号代替-+=*)
    在这里插入图片描述

  4. 推理失败时,给出非常详细的推理思路
    在这里插入图片描述

  5. 提前录入好的大量习题,只需要按 推理 按钮既可以看到该题的推理过程

    代码中的:

    // 习题集
    let arrTestItems = [
        { str: 'A*B\nB-A\n!C-!B\n!C+D\nC+D\nD-x', target: 'x', origin: '测试-存在干扰项' },
        { str: 'y*A\nA-B\n!C-!B\n!C+D\n!E-D\n!x-!E', target: 'x', origin: '测试-较复杂的前提' },
        { str: 'A\nA-B', target: 'B', origin: '42页 例题1.6.1' },
        { str: 'A*B', target: 'A', origin: '42页 例题1.6.2' },
        { str: 'A*B', target: 'B', origin: '42页 例题1.6.2' },
        { str: 'A-B\nB-C\nA', target: 'C', origin: '43页 例题1.6.3' },
        { str: 'A-B\nC-D\nA+C\n!B', target: 'D', origin: '44页 例题1.6.4' },
        { str: 'A-B\nC-D\n!B+!D\nA', target: '!C', origin: '45页 例题1.6.5' },
        { str: 'M+R\nM-C\nC-!F\nF', target: 'R', origin: '48页 例题1.6.8' },
        { str: 'A-B\n!B', target: '!A', origin: '49页 习题1.(1)' },
        { str: 'A+B\n!B', target: 'A', origin: '49页 习题1.(2)' },
        { str: 'A=B\nB=C\nA', target: 'C', origin: '49页 习题1.(3)(1)' },
        { str: 'A=B\nB=C\nC', target: 'A', origin: '49页 习题1.(3)(2)' },
        { str: '!p+q\n!q+r\nr-s\np', target: 's', origin: '49页 习题2.(1)' },
        { str: 'p-!q\n!q*!r\nr', target: '!p', origin: '49页 习题2.(4)' },
        { str: 'p-q\np', target: 'p', origin: '49页 习题2.(5)(1)' },
        { str: 'p-q\np', target: 'q', origin: '49页 习题2.(5)(2)' },
        { str: 'q-p\nq=s\ns=t\nt=r\nr', target: 'p', origin: '49页 习题2.(6)(1)' },
        { str: 'q-p\nq=s\ns=t\nt=r\nr', target: 'q', origin: '49页 习题2.(6)(2)' },
        { str: 'p-r\nq-s\np*q', target: 'r', origin: '49页 习题2.(7)(1)' },
        { str: 'p-r\nq-s\np*q', target: 's', origin: '49页 习题2.(7)(2)' },
        { str: '!p+r\n!q+s\np*q\nt', target: 'r', origin: '49页 习题2.(8)(1)' },
        { str: '!p+r\n!q+s\np*q\nt', target: 's', origin: '49页 习题2.(8)(2)' },
        { str: 'p-!q\n!r+q\nr*!s', target: '!p', origin: '49页 习题2.(11)(1)' },
        { str: 'p+q\np-r\nq-s\n!r', target: 's', origin: '49页 习题2.(12)(2)' },
        { str: 'p-q\n!q+!r\nr', target: '!p', origin: '68页 习题(4)' }
    ]
    

    渲染到页面后:
    在这里插入图片描述

  6. 查看所有习题的推理过程,只需要按 推理全部 按钮即可

  7. 较完美的推理过程,当前提条件与课本上的一致时,推理过程也与课本上的完全一直

    程序推理的:
    在这里插入图片描述

    课本(离散数学第二版 科学出版社 杨圣洪)上的48页例题1.6.8:
    在这里插入图片描述

  8. 智能选择前提条件,例如:
    在这里插入图片描述

    这个题中推出 B 时如果选择前提 B→A 是无法推理出来的,但是程序可以选出可以推理出来的前提 A→B

  9. 较复杂的前提条件照样能行
    在这里插入图片描述

(5) 测试

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • 6
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AI麥爾

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

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

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

打赏作者

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

抵扣说明:

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

余额充值