问题 F: 真值表(Ⅰ)
同学们都学习过《离散数学》这门课程,知道真值表是用于逻辑中的一类数学用表,用来计算逻辑表示式在每一个逻辑变量取值组合下的值。在这里我们给定一个逻辑表达式,要求生成对应的真值表。提示一下,数据结构教材中介绍了数学表达式的处理算法,可以将其改造以适用于我们的项目。
项目分为三个子项目,第一部分为词法分析,即将逻辑表达式分隔为多个词(token)。下面给出两个例子。
例一:
逻辑表达式pq中有p、和q共三个词;
例二:
逻辑表达式p(qr)中有p、、(、q、、r和)共七个词。
逻辑联结词有五个,见下表,这些符号和教材上的有所不同,主要是为了方便。
引入括号,规定基本逻辑联接词优先顺序从高到低依次是:( )、!、∧、||、->、<->。 同一优先级,从左到右顺序进行。
输入
输入由多行组成,每行都是一个正确的逻辑表达式。
逻辑表达式小于100个字符。
一个正确的逻辑表达式可以包含小写字母,空格和逻辑联结词(含括号)。单个小写字母表示一个逻辑变量,一个表达式中逻辑变量的个数不超过10。空格作为分隔符, 不是词,同一个词的字符之间不能有空格。
输出
每一个逻辑表达式产生如下的输出:
第一行按顺序输出表达式中的所有词。每个词之间用空格分开。
第二行按字母序输出表达式中的所有逻辑变量,用空格分开。
第三行开始输出逻辑变量值的所有组合情况。
样例输入
p
p->q
p||q
样例输出
p
p
1
0
p -> q
p q
1 1
1 0
0 1
0 0
p || q
p q
1 1
1 0
0 1
0 0
计算过程
真值表是使用于逻辑中(特别是在连结逻辑代数、布尔函数和命题逻辑上)的一类数学用表,用来计算逻辑表示式在每种论证(即每种逻辑变数取值的组合)上的值。尤其是,真值表可以用来判断一个命题表示式是否对所有允许的输入值皆为真,亦即是否为逻辑有效的。
真值表被用来计算以“决策程序”建构的命题表示式的值。命题表示式可以是一个原子公式(命题常数、命题变数或命题函数,如Px或P(x)),或以逻辑算子(如逻辑与( ∧ {\displaystyle \land }
)、逻辑或( ∨ {\displaystyle \lor })、逻辑非( ¬ {\displaystyle \lnot }))由原子公式建构出来的公式。举例来说, F x ∧ G x {\displaystyle Fx\land Gx}即是个命题表示式。
真值表中的列标题展示了 (i)命题函数与/或变量,和 (ii)建造自这些命题函数或变量和运算符的真值泛函表达式。行展示对 (i)和 (ii)的T或F指派的每个可能的求值。换句话说,每行都是对 (i)和 (ii)的不同解释。
经典(就是说二值)逻辑的真值表限定于只有两个真值是可能的布尔逻辑系统,它们是“真”或“假”,通常在表中简单的表示为T和F。
使用汇编语言实现该题目时,需将输入内容存储为字符串类型并遍历三次,第一次遍历字符串,去除空格并计算字母个数,确认符号位;第二次遍历字符串,去除重复字母并按字母顺序排序;第三次遍历字符串,输出每个字母的二进制编码。
在第二个整合后的程序中,我将原来的 main 函数中的逻辑移动到了名为 processString 的函数中。现在 main 函数主要负责读取输入,并调用 processString 函数来处理输入的字符串。这样做使得代码更结构化和易于管理。
以下是代码的逐步解析:
-
宏定义:
MAX_LENGTH
定义了字符串的最大长度为103。ASCII_a
和ASCII_z
分别定义了小写字母a和z的ASCII码。
-
主函数
main
:- 定义了一个字符数组
ch
,用来存储输入的字符串。
- 定义了一个字符数组
-
输入循环:
- 使用
gets
函数从标准输入读取字符串,直到输入结束。注意:gets
函数是不安全的,因为它不检查缓冲区溢出,所以现代编程实践中推荐使用fgets
。
- 使用
-
第一次遍历字符串:
- 计算字符串的长度
sum
。 - 定义一个标志变量
flag
用来记录字母的个数。 - 遍历字符串,执行以下操作:
- 打印出非空格字符。
- 当遇到字母时,打印一个空格,并增加
flag
的值。 - 当遇到非字母字符,且不是特定符号(如
->
、||
、<->
)时,打印一个空格。
- 计算字符串的长度
-
第二次遍历字符串:
- 尝试去除重复的字母,但代码逻辑存在问题,因为
ch[i]
被设置为'1'
后,后续的比较和排序操作将不会按预期执行。 - 尝试对字母进行排序,但排序算法实现不正确,因为交换操作没有正确地更新索引
i
和j
。
- 尝试去除重复的字母,但代码逻辑存在问题,因为
-
打印字母:
- 遍历字符串,如果字符是字母,则打印该字母后跟一个空格。
-
第三次遍历字符串:
- 对于每个可能的二进制数(由
flag
确定位数),执行以下操作:- 计算当前二进制数的每一位。
- 遍历字符串中的每个字母,根据当前二进制位的值打印 “0” 或 “1”。
- 打印一个换行符结束当前二进制数的输出。
- 对于每个可能的二进制数(由
-
结束:
- 程序返回0,表示正常退出。
问题和改进:
gets
函数是不安全的,应该替换为fgets(ch, sizeof(ch), stdin);
。- 第二次遍历字符串的代码逻辑需要重写,以正确去除重复字母并排序。
- 第三次遍历字符串的输出逻辑可能需要根据具体需求进行调整,以确保输出正确。
部分实现
代码如下( 第一次遍历字符串,去除空格并计算字母个数,确认符号位):
for (int i = 0; i < sum; i++) {
if (ch[i] != ' ') {printf("%c", ch[i]);}
if (ch[i] >= ASCII_a && ch[i] <= ASCII_z) {flag++;printf(" ");}
if (ch[i] != ' ' && !(ch[i] >= ASCII_a && ch[i] <= ASCII_z) && !(ch[i] == '-' && ch[i + 1] == '>') && !(ch[i] == '|' && ch[i + 1] == '|') && !(ch[i] == '<' &&ch[i + 1] == '-' && ch[i + 2] == '>') && !(ch[i - 1] == '<' && ch[i] == '-' && ch[i + 1] == '>') && !(ch[i - 2] == '<' && ch[i] == '-' && ch[i + 1] == '>')) {printf(" ");}
}
printf("\n");
代码如下(第二次遍历字符串,去除重复字母并按字母顺序排序):
for(int i=0;i<sum;i++)for(int j=0;j<sum;j++)if(ch[i]==ch[j]&&i!=j&&ch[i]<='z'&&ch[i]>='a'){
ch[i]='1';flag--;
}for(int i=0;i<sum;i++)for(int j=0;j<sum;j++){
if(i<j&&ch[i]>ch[j]&&ch[i]<='z'&&ch[i]>='a'&&ch[j]<='z'&&ch[j]>='a'){
char m=ch[i];
ch[i]=ch[j];
ch[j]=m;
}
}
for(int i=0;i<sum;i++)if(ch[i]<='z'&&ch[i]>='a')printf("%c ",ch[i]);
printf("\n");
代码如下(第三次遍历字符串,输出每个字母的二进制编码):
for (int j = 1; j <= pow(2, flag); j++) {
int x = pow(2, flag);
int y = flag - 1;
int z = (j - 1) % x;
if (j == pow(2, flag) + 1) {z = pow(2, flag); }
for (int i = 0; i < sum; i++) {
if (ch[i] >= ASCII_a && ch[i] <= ASCII_z)
{
int n = pow(2, y);
if (n == 1)
{
int o = j / n;
if ((o % 2 == 0)) {printf("0 ");}
else {printf("1 "); }
}
else
{
int o = z / n;
if ((o % 2 == 0)) {printf("1 ");}
else {printf("0 ");}
}
y--;
}
}
printf("\n");
}
AC代码(第一个为计算过程存在于主函数之内,第二个为计算过程自定义函数)
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#define MAX_LENGTH 103
#define ASCII_a 97
#define ASCII_z 122
int main() {
char ch[MAX_LENGTH];
while (gets(ch)) {
int sum = strlen(ch);
int flag = 0;
// 第一次遍历字符串,去除空格并计算字母个数,确认符号位
for (int i = 0; i < sum; i++) {
if (ch[i] != ' ') {printf("%c", ch[i]);}
if (ch[i] >= ASCII_a && ch[i] <= ASCII_z) {flag++;printf(" ");}
if (ch[i] != ' ' && !(ch[i] >= ASCII_a && ch[i] <= ASCII_z) && !(ch[i] == '-' && ch[i + 1] == '>') && !(ch[i] == '|' && ch[i + 1] == '|') && !(ch[i] == '<' &&ch[i + 1] == '-' && ch[i + 2] == '>') && !(ch[i - 1] == '<' && ch[i] == '-' && ch[i + 1] == '>') && !(ch[i - 2] == '<' && ch[i] == '-' && ch[i + 1] == '>')) {printf(" ");}
}
printf("\n");
// 第二次遍历字符串,去除重复字母并按字母顺序排序
for(int i=0;i<sum;i++)for(int j=0;j<sum;j++)if(ch[i]==ch[j]&&i!=j&&ch[i]<='z'&&ch[i]>='a'){
ch[i]='1';flag--;
}for(int i=0;i<sum;i++)for(int j=0;j<sum;j++){
if(i<j&&ch[i]>ch[j]&&ch[i]<='z'&&ch[i]>='a'&&ch[j]<='z'&&ch[j]>='a'){
char m=ch[i];
ch[i]=ch[j];
ch[j]=m;
}
}
for(int i=0;i<sum;i++)if(ch[i]<='z'&&ch[i]>='a')printf("%c ",ch[i]);
printf("\n");
// 第三次遍历字符串,输出每个字母的二进制编码
for (int j = 1; j <= pow(2, flag); j++) {
int x = pow(2, flag);
int y = flag - 1;
int z = (j - 1) % x;
if (j == pow(2, flag) + 1) {z = pow(2, flag); }
for (int i = 0; i < sum; i++) {
if (ch[i] >= ASCII_a && ch[i] <= ASCII_z)
{
int n = pow(2, y);
if (n == 1)
{
int o = j / n;
if ((o % 2 == 0)) {printf("0 ");}
else {printf("1 "); }
}
else
{
int o = z / n;
if ((o % 2 == 0)) {printf("1 ");}
else {printf("0 ");}
}
y--;
}
}
printf("\n");
}
}
return 0;
}
#include <stdio.h>
#include <string.h>
#include <math.h>
#define MAX_LENGTH 103
#define ASCII_a 97
#define ASCII_z 122
void processString(char ch[]) {
int sum = strlen(ch);
int flag = 0;
// 第一次遍历字符串,去除空格并计算字母个数
for (int i = 0; i < sum; i++) {
if (ch[i] != ' ') {
printf("%c", ch[i]);
}
if (ch[i] >= ASCII_a && ch[i] <= ASCII_z) {
flag++;
printf(" ");
}
if (ch[i] != ' ' && !(ch[i] >= ASCII_a && ch[i] <= ASCII_z) &&
!(ch[i] == '-' && ch[i + 1] == '>') &&
!(ch[i] == '|' && ch[i + 1] == '|') &&
!(ch[i] == '<' && ch[i + 1] == '-' && ch[i + 2] == '>') &&
!(ch[i - 1] == '<' && ch[i] == '-' && ch[i + 1] == '>') &&
!(ch[i - 2] == '<' && ch[i] == '-' && ch[i + 1] == '>')) {
printf(" ");
}
}
printf("\n");
// 第二次遍历字符串,去除重复字母并按字母顺序排序
for (int i = 0; i < sum; i++) {
for (int j = 0; j < sum; j++) {
if (ch[i] == ch[j] && i != j && ch[i] <= 'z' && ch[i] >= 'a') {
ch[i] = '1';
flag--;
}
}
}
for (int i = 0; i < sum; i++) {
for (int j = 0; j < sum; j++) {
if (i < j && ch[i] > ch[j] && ch[i] <= 'z' && ch[i] >= 'a' && ch[j] <= 'z' && ch[j] >= 'a') {
char m = ch[i];
ch[i] = ch[j];
ch[j] = m;
}
}
}
for (int i = 0; i < sum; i++) {
if (ch[i] <= 'z' && ch[i] >= 'a') {
printf("%c ", ch[i]);
}
}
printf("\n");
// 第三次遍历字符串,输出每个字母的二进制编码
for (int j = 1; j <= pow(2, flag); j++) {
int x = pow(2, flag);
int y = flag - 1;
int z = (j - 1) % x;
if (j == pow(2, flag) + 1) {
z = pow(2, flag);
}
for (int i = 0; i < sum; i++) {
if (ch[i] >= ASCII_a && ch[i] <= ASCII_z) {
int n = pow(2, y);
if (n == 1) {
int o = j / n;
if ((o % 2 == 0)) {
printf("0 ");
} else {
printf("1 ");
}
} else {
int o = z / n;
if ((o % 2 == 0)) {
printf("1 ");
} else {
printf("0 ");
}
}
y--;
}
}
printf("\n");
}
}
int main() {
char ch[MAX_LENGTH];
while (gets(ch)) {
processString(ch);
}
return 0;
}