第四章学习了串和数组的相关内容,以及学习了BF算法和KMP算法。这周上机课老师带着我们打了AI核心代码。
看到题目就觉得要求好多代码肯定很难写,听到老师说这道题目会有很多的边界问题要进行考虑,就感觉更难了,但是在老师的一步步指导下,完成了这道题,因此本章学习小结我将会分享一些完成这道题老师教给我们的思路以及我的一些心得体会。
本题要求你实现一个简易版的 AI 英文问答程序,规则是:
- 无论用户说什么,首先把对方说的话在一行中原样打印出来;
- 消除原文中多余空格:把相邻单词间的多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉;
- 把原文中所有大写英文字母变成小写,除了 I;
- 把原文中所有独立的 I 和 me 换成 you;
- 把原文中所有的问号 ? 换成惊叹号 !;
- 把原文中所有独立的 can you 换成 I can —— 这里“独立”是指被空格或标点符号分隔开的单词;
- 在一行中输出替换后的句子作为 AI 的回答。
首先看完这道题我们应该首先考虑的是主函数,按照题目的要求将主函数编写完成,在进行后续的补充完善。
同时处理题目规则一:无论用户说什么,首先把对方说的话在一行中原样打印出来以及规则七:在一行中输出替换后的句子作为 AI 的回答。此时我们调用了<cstdio>文件里的getchar()函数来吸收回车。
//主函数
int main()
{
int n;
string s;
cin >>n;
getchar();// 吸收回车 <cstdio>
for ( int i=1; i<=n ; i++)
{
getline(cin , s);
cout << s << endl ;
cout << "AI: ";
go(s); // 根据s输出AI的回答
}
return 0;
}
然后再根据主函数的代码内容,一步步完善代码,所以接下来就是要完成go函数的编写
首先需定义辅助变量t来copy s的字符串,题目有:每行给出一句不超过 1000 个字符的、以回车结尾的用户的对话,考虑到如果输入的字符全为大写的话。那么将数组长度定义为1000或者1001是不够的,所以要将数组长度定义为3001,包括结尾\0
void go(string s)
{
// 根据s输出AI的回答
//定义辅助变量t,来copy s的字符串
char t[3001];
int i, j; //定义两个下标; i :定义到s的第一个非空;
...
}
定义到s的第一个非空字符后,首先要考虑的是空格问题,后面的条件依照空格标准化。考虑删除空格的情况(删除前后空格,删除中间连续的空格保留一个,删除符号后的空格)首先处理规则二:消除原文中多余空格:把相邻单词间的多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉
void go(string s)
{// 根据s输出AI的回答
//定义辅助变量t,来copy s的字符串
char t[3001];
int i, j; //定义两个下标; i :定义到s的第一个非空;
for(i = 0 ; s[i]!= '\0' && s[i] == ' ' ; i++); //全为空格的时候会将s的最后一个字符‘\0’存储
j = 0;
//保证每次往后扫一次用for,往后扫的次数要跳跃无规律用while
while(s[i] != '\0'){ //把s串copy到t,但是连续的空格只copy一次
if(s[i] == ' ' && s[i-1] ==' ')//连续的空格第一个要,后面的都不要
{ //一个一个copy到t字符串同时判断
i++;
continue;
}
......
if(s[j]==' '&&s[j+1]=='\0') //删除后面空格
{
j++;
continue;
}
cout << t[j];
j++;
}
}
处理规则五:把原文中所有的问号 ? 换成惊叹号 !
void go(string s)
{// 根据s输出AI的回答
//定义辅助变量t,来copy s的字符串
char t[3001];
int i, j; //定义两个下标; i :定义到s的第一个非空;
for(i = 0 ; s[i]!= '\0' && s[i] == ' ' ; i++); //全为空格的时候会将s的最后一个字符‘\0’存储
j = 0;
//保证每次往后扫一次用for,往后扫的次数要跳跃无规律用while
while(s[i] != '\0'){ //把s串copy到t,但是连续的空格只copy一次
if(s[i] == ' ' && s[i-1] ==' ')//连续的空格第一个要,后面的都不要
{ //一个一个copy到t字符串同时判断
i++;
continue;
}
if(s[i]=='?'){
t[j++]='!';
++i;
continue;//回到循环开头
}
t [j]='\0';// 字符串结尾标志;
......
if(s[j]==' '&&s[j+1]=='\0') //删除后面空格
{
j++;
continue;
}
cout << t[j];
j++;
}
}
处理规则三:把原文中所有大写英文字母变成小写,除了 I,所以用if循环,将除了I以外的大写字母利用tolower函数接口将大写转为小写。
void go(string s)
{// 根据s输出AI的回答
//定义辅助变量t,来copy s的字符串
char t[3001];
int i, j; //定义两个下标; i :定义到s的第一个非空;
for(i = 0 ; s[i]!= '\0' && s[i] == ' ' ; i++); //全为空格的时候会将s的最后一个字符‘\0’存储
j = 0;
//保证每次往后扫一次用for,往后扫的次数要跳跃无规律用while
while(s[i] != '\0'){ //把s串copy到t,但是连续的空格只copy一次
if(s[i] == ' ' && s[i-1] ==' ')//连续的空格第一个要,后面的都不要
{ //一个一个copy到t字符串同时判断
i++;
continue;
}
if(s[i]=='?'){
t[j++]='!';
++i;
continue;//回到循环开头
}
if( s[i] != 'I')
{
t[j] = tolower(s[i]);
i++;
j++;
continue;
}
t[j++] = s[i++]; //不能copy连续的空格 ,先读变量值,为下一轮循环做准备
//此时t没有处理'\0' 后面读出字符串会出错
}
t [j]='\0';// 字符串结尾标志;
......
if(s[j]==' '&&s[j+1]=='\0') //删除后面空格
{
j++;
continue;
}
cout << t[j];
j++;
}
}
处理规则四:把原文中所有独立的 I 和 me 换成 you。需要注意的是独立的I才要换成you,判断I独立的条件是I的前后是空格或者是标点符号,其他情况的I则不需要变换。此外j为0时会越界。
void go(string s)
{// 根据s输出AI的回答
//定义辅助变量t,来copy s的字符串
char t[3001];
int i, j; //定义两个下标; i :定义到s的第一个非空;
for(i = 0 ; s[i]!= '\0' && s[i] == ' ' ; i++); //全为空格的时候会将s的最后一个字符‘\0’存储
j = 0;
//保证每次往后扫一次用for,往后扫的次数要跳跃无规律用while
while(s[i] != '\0'){ //把s串copy到t,但是连续的空格只copy一次
if(s[i] == ' ' && s[i-1] ==' ')//连续的空格第一个要,后面的都不要
{ //一个一个copy到t字符串同时判断
i++;
continue;
}
if(s[i]=='?'){
t[j++]='!';
++i;
continue;//回到循环开头
}
if( s[i] != 'I')
{
t[j] = tolower(s[i]);
i++;
j++;
continue;
}
t[j++] = s[i++]; //不能copy连续的空格 ,先读变量值,为下一轮循环做准备
//此时t没有处理'\0' 后面读出字符串会出错
}
t [j]='\0';// 字符串结尾标志;
j = 0;
while(t[j]!='\0')
{
if(t[j]=='I' && ( j == 0 || isDependent(t[j-1]) ) && isDependent(t[j+1]) )
{
cout << "you";
j++;
continue;
}
if(t[j]=='m' && t[j+1]=='e' && (j==0 || isDependent(t[j-1])) && isDependent(t[j+2]) ) //j+2不会越界
{
cout << "you";
j = j+2;
continue;
}
if(t[j]==' ' && isDependent(t[j+1]))
{
j++;
continue;
}
cout << t[j];
j++;
}
if(s[j]==' '&&s[j+1]=='\0') //删除后面空格
{
j++;
continue;
}
cout << t[j];
j++;
}
cout <<endl; }
处理I和me问题时调用了isDependent函数,因此我们需要定义isDependent函数来判断ch是否为分隔符。空格、标点、 \0<这三类是分隔符号>
bool isDependent(char ch)
{
ch = tolower(ch);
if((ch >='0' && ch<='9') || (ch>='a' && ch<='z'))
return false;
else return true;
}
最后就是要处理规则六:把原文中所有独立的 can you 换成 I can —— 这里“独立”是指被空格或标点符号分隔开的单词。首先我们需要判断can you 是否独立,因此需要定义iscanyou函数来完成这个功能。
bool canyou(char t[],int j)
{
if(t[j]=='c'&&t[j+1]=='a'&&t[j+2]=='n'&&t[j+3]==' '&&t[j+4]=='y'&&t[j+5]=='o'&&t[j+6]=='u')
return true;
else return false;
}
在go函数中,通过调用canyou函数来处理规则六。
void go(string s)
{// 根据s输出AI的回答
//定义辅助变量t,来copy s的字符串
char t[3001];
int i, j; //定义两个下标; i :定义到s的第一个非空;
for(i = 0 ; s[i]!= '\0' && s[i] == ' ' ; i++); //全为空格的时候会将s的最后一个字符‘\0’存储
j = 0;
//保证每次往后扫一次用for,往后扫的次数要跳跃无规律用while
while(s[i] != '\0'){ //把s串copy到t,但是连续的空格只copy一次
if(s[i] == ' ' && s[i-1] ==' ')//连续的空格第一个要,后面的都不要
{ //一个一个copy到t字符串同时判断
i++;
continue;
}
if(s[i]=='?'){
t[j++]='!';
++i;
continue;//回到循环开头
}
if( s[i] != 'I')
{
t[j] = tolower(s[i]);
i++;
j++;
continue;
}
t[j++] = s[i++]; //不能copy连续的空格 ,先读变量值,为下一轮循环做准备
//此时t没有处理'\0' 后面读出字符串会出错
}
t [j]='\0';// 字符串结尾标志;
j = 0;
while(t[j]!='\0')
{
if(t[j]=='I' && ( j == 0 || isDependent(t[j-1]) ) && isDependent(t[j+1]) ) //独立的I(前后是空格或者是标点符号)才要换成you ,j为0会越界
{
cout << "you";
j++;
continue;
}
if(t[j]=='m' && t[j+1]=='e' && (j==0 || isDependent(t[j-1])) && isDependent(t[j+2]) ) //j+2不会越界
{
cout << "you";
j = j+2;
continue;
}
if(t[j]==' ' && isDependent(t[j+1]))
{
j++;
continue;
}
if(canyou(t,j)&&(j==0||isDependent(t[j-1]))&&isDependent(t[j+7])) //canyou共有七个字符
{
cout<<"I can";
j=j+7;
continue;
}
cout <<endl;
}
最后将以上函数整合,如需声明函数的进行声明,代码就完成了。
本章完成实践和作业花费的时间比较长,感觉题目很长而且很难,考虑的要点太多了。但是这次老师亲自带我们做实践题之后,感觉对题目长的代码题没那么恐惧了,最重要的是理清思路,考虑全面了再从主函数开始入手,逐步完善每一个要点。
本章学习比上一章进步的一点是做到了选择用一个方法来写代码,即便出了bug也慢慢改,没有换各种方法来写题。
接下来的目标是希望自己看到新的编程题自己能够理清好思路,写好算法,然后在一步步实践中提高自己的编程能力。