AI (人工智能不还是人写出来的吗)

7-2 AI核心代码 (30 point(s))

本题要求你实现一个简易版的 AI 英文问答程序,规则是:

  • 无论用户说什么,首先把对方说的话在一行中原样打印出来;
  • 消除原文中多余空格:把相邻单词间的多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉;
  • 把原文中所有大写英文字母变成小写,除了 I;
  • 把原文中所有独立的 I 和 me 换成 you;
  • 把原文中所有的问号 ? 换成惊叹号 !;
  • 把原文中所有独立的 can you 换成 I can —— 这里“独立”是指被空格或标点符号分隔开的单词;
  • 在一行中输出替换后的句子作为 AI 的回答。

这是一道又老师带着写得一道天梯赛的题目,课堂上听了之后回来自己重新整理思路发现错误还是很多的,有了解题的思路但也要细心地去敲代码,每一个括号,if else都要想清楚它的范围作用域,下面就讲讲自己的分析过程及遇到的各种问题

首先按照老师讲的做题的思路,先想好大的框架,输入n个字符串(用string保存),输出正确的格式(用一个函数实现),所以主函数其实只是两件简单的事,具体的操作再通过函数来实现

int main()
{
    string s;
    int n;
    cin>>n;
    getchar();  //接收N的回车否则第一个string就结束了
    for(int i=1;i<=n;i++)
    {  
        getline(cin,s);
        cout<<s<<endl;
        cout<<"AI: ";
        function(s);
    }
    return 0;
 } 
main

下面再来写function函数

如果我们直接在原来的S串上直接操作并直接输出比较复杂,每一个字符要经过很多次的检验可能会出现超时,所以我们不妨用一个新的数组存放处理的串再有选择地输出,因此我们先定义一个串string t;但是这样就出现问题了,因为我们给t分配一个放地址的空间却没有给它初始一个地址,它就只能放一个野地址了,后面我们在操作t[j]时是要用这个t的地址进行偏移,但是没有初始化连首地址都不知道要怎么操作呢,除非先随便给个字符给他个足够长的空间,所以还是定义一个字符数组吧,这个字符数组的长度应该开多大呢,我们看到题目中如果遇到I就会全部转换为you所以最坏的情况就是全部(1000)个全是I,长度就为3000,定义时多加一个放结尾符。然后再把S中的元素复制过来(当然没那么简单啦,复制过程中再进行判断)

void function(string s)
{
    char t[3005];
    int i,j;
    t[j]=a[i];
}
View Code

处理输入时前面的空格

方法:用一个空循环让指针指向s的第一个非空元素,再来存入T数组

指向非空格元素
for(i=0; s[i]!='\0'&&s[i]==' ';++i);  
    //在for循环里重复定义了int x=0导致后面的全部没输出但是第一个代码又可以输出 
    //cout<<s[i]; 检验得确实到了第一个非零 
    //制作一个空循环,让i到达第一个非空的字符,结束时a[i]为字符

接着处理串中的连续空格与结尾的连续空格

方法:先看来连续空格的特点就是当前空格的前一个还是空格,如果当前元素为空格且前一个也是空格就跳过空格不放进t数组并且进行下一个s串的判断,结尾的连续空格就被保留了一个存进T数组了

if(s[i]==' '&&s[i-1]==' ')
        {  //处理后面和中间的空格ABD____WE 判断是否为连续的空格,如果用I+1==' '的话 ,但后面还留有一个空格 
          ++i;   
          continue;  
          //跳出本次的循环继续下一个元素而不是跳出整个循环,有连续空格时会死循环    
        }    
中间,尾部空格

处理问号

方法:先判断问号,是的话就改成!存进t数组并进行下一个s串判断,注意i的值要进行更改,这是如果想着快捷直接在判断时i++就会出问题,因为如果判断了不是问号就直接跳过这个元素,把下一个元素存进t数组了

if(s[i]=='?')
        {  //如果当前元素是?就吧它转化为!存入T数组 
            t[j++]='!';
            ++i;  //要先判断了才可以i++否则就算不是?在判断后就已经自动跳到下个元素,当前的元素就没有处理 
            continue;
        }
处理?

处理非I大写换小写

方法:如果不是I的大写就换成小写,当然这里可以先判断大小写 1.islower(ch) 2.ch>='A'&&ch<='Z' 再转换ch+='a'-'A' ,也可以直接用函数toslower(),加函数头cstdio

if(s[i]!='I')  //s[j]
        {  //如果当前的元素不是I就判断大小写并转换 
            t[j]=tolower(s[i]);  //直接用函数判断加转换 
            ++i;  ++j;  //如果当前元素!=I且是大写的话? 
            continue;
        }
处理大写

把处理了空格,?,大写即一些其他的元素存进T数组,由于这个循环是以s[i]!='\0'为条件的所以数组T中是没有字符串的结尾符,手动在循环结束后加一个,由于前面存放元素时已经为下一个元素开了一个位子所以就直接 t[j]='\0'

处理到一大半先输出数组试试吧

//cout<<t<<endl;   char数组

//cout<<t.data(); string类 

存入T数组的是前面没有连续空格,问号转换成!,元素间没有来连续空格,结尾至少有一个空格及其他的字母(I)数字标点之类的元素,我们在输出T数组的时候再判断筛选输出

while(t[j]!='\0')

{cout<<t[j]<<endl;}

of couse 没辣么简单~~    

转换输出独立I为you

方法:先判断是否为I再判断独立( 这里“独立”是指被空格或标点符号分隔开的单词),判断前一个后一个是否为空格或者标点(用一个independent(ch)函数),if中的t[j-1]和t[j+1]会越界吗?如果输入的第一个字符就是'I',t[j-1]会越界;但是t[j+1]不会越界,其边界情况是t[j+1]为'\0',如果是就输出you并且指针指向下一个T数组元素跳出本次的循环进行下一个T元素的判断

I->YOU
if(t[j]=='I'&& (j==0||independent(t[j-1])) && independent(t[j+1]))
         {  //把I转换,考虑前一个后一个是否为独立,前一个注意越界用J==0判断||,后一个最坏情况就是下一个为|0 
             cout<<"you";
             ++j;
             continue;
}

bool independent(char ch)
{  //判断是否为空格或标点即非数字字母和I(小写字母 I 数字 空格 标点 |0)
    ch=tolower(ch);  //先转换为小写比较方便而且输出也是小写 
    if(ch>='0'&&ch<='9' || ch>='a'&&ch<='z' || ch=='I')  //前面装S时没有对I处理,输出T时独立为后面是空格或标点 
    return false;
    else
    return true;
}
independent

转换输出me->you

方法:判断me再判断是否独立,如果输入的第一个字符就是'm',t[j-1]会越界;但是t[j+1]不会越界,因为进行独立判断的前提是前面两个字符是me其边界情况是t[j+2]为'\0',如果是就输出you,指针指向下下个并跳出对于这个元素的判断进行一个T元素的判断

if(t[j]=='m'&&t[j+1]=='e'&& (j==0||independent(t[j-1])) && independent(t[j+2]))
         {  //把me转换,判断前一个和后两个是否独立,后两个最坏就是下一个为|0但能判断独立的前提是有me 
             cout<<"you";
             j+=2;  //J往后移两个 
             continue;
         }
me->you

处理标点前的空格

方法:如果当前元素为空格且下一个为标点(用ispunctuation(ch)函数)就跳过空格,指针后移并跳出对于这个元素的判断进行一个T元素的判断

if(t[j]==' '&& ispunctuation(t[j+1]))
         {  //处理标点前的空格abv .dc 
             ++j;
             continue;
         }
删除标点前的空格
bool ispunctuation(char a)
{  //判断后一个是否为标点符号!(字母 数字 空格 I) 
    a=tolower(a);  //先转换为小写比较方便而且输出也是小写 
    if(a>='0'&&a<='9' || a>='a'&&a<='z' || a=='I' || a==' ')  //前面装S时没有对I处理,输出T时独立为后面是空格或标点 
    return false;
    else
    return true;
}
ispunctuation

转换输出I can

方法:如果当前的7个元素是can you就输出I can(为保证函数的封装性就用函数iscanyou(ch [],j)判断,开始是想着有没有可以复制从M到N的函数呢,在遇到c时就把7个元素放进一个串或数组再用strcmp来比较字符串,于是看到了一些比较字符数组及字符串的方法,c++基础不牢的看了会有一些收获

char a[] = "aaa",b[]="aaa";
string A = "AAA", B = "AAA";
cout <<"*a和*b的值分别是:" <<*a << "" << *b << endl;    a,a
cout <<"*“aaa”的值是:"<< *"aaa" << endl;     a
cout <<"利用 == 比较a,b两个字符串,结果是(相等为1,不等为0):" <<(a==b) << endl;     0 比较的是首地址
cout << "利用strcmp()比较a,b两个字符串,结果是(相等为0,不等非0):"<<strcmp(a,b) << endl;     0
cout << "利用 == 比较A,B两个string,结果是(相等为1,不等为0):"<<(A==B) << endl;     1
cout << "利用compare()比较A,B两个string,结果是(相等为0,不等非0):" << A.compare(B) << endl;    0

发现好像没有这样的函数,就想着说用这种方法,但是进入了死循环,这样的话如果C后面没有6个字符长度就会死循环

int flag=j;
char b[7];
            for(int k=0;k<7;k++)
                b[k]=t[j++];    
            j=flag;
             if(!strcmp(b,"can you") &&(j==0||independent(t[j-1])) &&independent(t[j+7]))
             {
               cout<<"I can";
                j+=7;
                continue;
            }

处理删除结尾剩余的空格

方法:如果当前元素为空格且下一个为'\0'就跳过,输出就结束啦,但是这里我有个疑问就是为什么还要用continue呢,不是都已经结束了么就不需要进行下一轮的判断呀,但后来想了想如果不跳出循环就会执行下一句输出T[j],输出结尾符了,但是在PTA上也是过的了的,上网看了一下也没有特别准确的解释

if(s[j]==' '&&s[j+1]=='\0')
        {//处理结尾剩余得一个空格
               ++j;
            continue;
        }   
处理结尾空格

结束~~

总的过程如下

#include <iostream>
#include <cstring>
#include <cstdio> 
using namespace std;

bool independent(char ch)
{  //判断是否为空格或标点即非数字字母和I(小写字母 I 数字 空格 标点 |0)
    ch=tolower(ch);  //先转换为小写比较方便而且输出也是小写 
    if(ch>='0'&&ch<='9' || ch>='a'&&ch<='z' || ch=='I')  //前面装S时没有对I处理,输出T时独立为后面是空格或标点 
    return false;
    else
    return true;
}
bool ispunctuation(char a)
{  //判断后一个是否为标点符号!(字母 数字 空格 I) 
    a=tolower(a);  //先转换为小写比较方便而且输出也是小写 
    if(a>='0'&&a<='9' || a>='a'&&a<='z' || a=='I' || a==' ')  //前面装S时没有对I处理,输出T时独立为后面是空格或标点 
    return false;
    else
    return true;
}
bool iscanyou(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')
    {
        if((j==0||independent(t[j-1]) )&&independent(t[j+7]))
            return true; 
    } 
    else   //这个要放在外面,因为不是can you时判断就是错误了 
    return false;
            
}

void function(string s)
{//处理串并输出 
    int j=0,i;
    char t[3001];  //定义一个字符数组装原来的串来进行操作与比较
    for(i=0; s[i]!='\0'&&s[i]==' ';++i);  
    ///在for循环里重复定义了int x=0导致后面的全部没输出但是第一个代码又可以输出 
    //cout<<s[i]; 检验得确实到了第一个非零 
    //制作一个空循环,让i到达第一个非空的字符,结束时a[i]为字符
    while(s[i]!='\0')
    {  // 暂时先处理连续空格和后面空格处理剩下一个 ,处理?,小写换大写 
        if(s[i]==' '&&s[i-1]==' ')
        {  //处理后面和中间的空格ABD____WE 判断是否为连续的空格,如果用I+1==' '的话 ,但后面还留有一个空格 
          ++i;   
          continue;  
          //跳出本次的循环继续下一个元素而不是跳出整个循环,有连续空格时会死循环    
        }    
        if(s[i]=='?')
        {  //如果当前元素是?就吧它转化为!存入T数组 
            t[j++]='!';
            ++i;  //要先判断了才可以i++否则就算不是?在判断后就已经自动跳到下个元素,当前的元素就没有处理 
            continue;
        }
        if(s[i]!='I')  //s[j]
        {  //如果当前的元素不是I就判断大小写并转换 
            t[j]=tolower(s[i]);  //直接用函数判断加转换 
            ++i;  ++j;  //如果当前元素!=I且是大写的话? 
            continue;
        }
        t[j++]=s[i++];
     } 
     t[j]='\0';  //最后到|0前一个会停下所以不会把S的|0也复制过去,而且前面的J已将加了1 
     //cout<<t<<endl;  cout<<t.data() string类 
     
     j=0;
     while(t[j]!='\0')
    {  //输出
         if(t[j]=='I'&& (j==0||independent(t[j-1])) && independent(t[j+1]))
         {  //把I转换,考虑前一个后一个是否为独立,前一个注意越界用J==0判断||,后一个最坏情况就是下一个为|0 
             cout<<"you";
             ++j;
             continue;
         }
         if(t[j]=='m'&&t[j+1]=='e'&& (j==0||independent(t[j-1])) && independent(t[j+2]))
         {  //把me转换,判断前一个和后两个是否独立,后两个最坏就是下一个为|0但能判断独立的前提是有me 
             cout<<"you";
             j+=2;  //J往后移两个 
             continue;
         }
         if(t[j]==' '&& ispunctuation(t[j+1]))
         {  //处理标点前的空格abv .dc 
             ++j;
             continue;
         }
         if(iscanyou(t,j))
         {
             cout<<"I can";
             j+=7;
             continue;
         }
         
            /*int flag=j;
             char b[7];
            for(int k=0;k<7;k++)
                b[k]=t[j++];    
            j=flag;
             if(!strcmp(b,"can you") &&(j==0||independent(t[j-1])) &&independent(t[j+7]))
             {
               cout<<"I can";
                j+=7;
                continue;这样的话如果C后面没有6个字符长度就会死循环 
            }*/
        if(s[j]==' '&&s[j+1]=='\0')
        {//处理结尾剩余得一个空格
               ++j;
            continue;
        }   
        cout<<t[j++]; 
    }
        cout<<endl;
}
 
int main()
{
    string s;
    int n;
    cin>>n;
    getchar();  //接收N的回车否则第一个string就结束了
    for(int i=1;i<=n;i++)
    {
        getline(cin,s);
        cout<<s<<endl;
        cout<<"AI: ";
        function(s);
    }
    return 0;
 } 
Code

这次的收获我觉得最重要的是思想,先想好一个大的框架就是先写main()函数,具体的操作处理用函数接口实现,然后在主函数前(不用函数声明)或者后面增加具体的函数内容,在函数内部最好只对那一层的数据进行操作就不要对数据,如果需要操作更具体的数据就可以再用函数来进行(函数内嵌函数)。

 

 

 

posted @ 2019-04-12 15:41  蓝jingjing  阅读(544)  评论(3编辑  收藏  举报