L1-064 估值一亿的ai核心代码 (分数20)字符串处理
题目描述
ai处理字符串规则是:
•无论用户说什么,首先把对方说的话在一行中原样打印出来;
•消除原文中多余空格:把相邻单词间的多个空格换成 1 个空格,把行首尾的空格全部删掉,把标点符号前面的空格删掉;
•把原文中所有大写英文字母变成小写,除了 I;•把原文中所有独立的 can you、could you 对应地换成 I can、I could—— 这里“独立”是指被空格或标点符号分隔开的单词;
•把原文中所有独立的 I 和 me 换成 you;
•把原文中所有的问号 ? 换成惊叹号 !;
•在一行中输出替换后的句子作为 AI 的回答。
现在请你模拟手搓一个能满足这些功能代码来模拟ai。
我来模拟ai?!真的假的(👿)
输入格式
输入首先在第一行给出不超过 10 的正整数 N,随后 N 行,每行给出一句不超过 1000 个字符的、以回车结尾的用户的对话,对话为非空字符串,仅包括字母、数字、空格、可见的半角标点符号。
输出格式
按题面要求输出,每个 AI 的回答前要加上 AI: 和一个空格。
解题思路
算法一:朴素做法
我先要介绍一种处理字符串中单词之间多个空格的方法
我们可以像之前L1-049那样,用一个last来记录上一个字符是啥状态 💪\
比如,bool last_is_space= true; 再利用isalnum(char)函数完成对输入字符串的整理,记整理后的字符串为clean
由于这里需要先输出原句,所以肯定需要使用getline,不能用cin自动跳过不可见字符
遍历字符串的过程中,我们需要分三种情况讨论:1. 单词(包含数字和字母) 2. 空格 3. 标点符号 ;
●如果该 字符 是 单词 , 就直接放进clean字符串;
●如果是空格,检查last_is_space,如果是false,就放进clean,并改变状态
●如果是标点符号,要检查last_is_space和clean.size() , 因为要去掉标点符号前的空格,但是标点符号可能出现在字符串第一个,而刚开始
last_is_space 的状态就是true,所以还要检查clean.size(),任何访问容器元素的操作都要判断是否为空;或者可以检查clean.size() && clean.back()
如果有空格就 pop.back() 后者更符直觉,前者符合前面的代码设计
注意: 常见的容器 如vector 和 string 都有 front()和 back() ,之前我也不知道这个o(╥﹏╥)o
然后把整个字符串clean之后,就是对单词大小写(为了方便可以在clean的过程中转换为小写),特定词的替换,问号进行处理。
ac代码✅️
#include<iostream>
#include<string>
#include<vector>
#include<cctype>
using namespace std;
bool isSymbol(char c)
{
return !isalnum(c);
}
int main()
{
int n;
cin>>n;
cin.ignore();
while(n--)
{
string s;
getline(cin,s);
cout<<s<<endl;
string clean;
bool lastIsSpace = true;
for(int i = 0 ; i < s.size() ; i++)
{
char c = s[i];
if(c == ' ')
{
if(!lastIsSpace)
{
clean += ' ';
lastIsSpace = true;
}
}
else if( isSymbol(c) )
{
//这里提个问题,为啥 “只” 要检查 “末尾” 是否是空格 , 前面不可能有空格吗?
if(lastIsSpace && clean.size()) clean.pop_back();
if(c == '?') c = '!';
clean += c;
lastIsSpace = false;
}
else
{
if(c != 'I') c = tolower(c);
clean += c;
lastIsSpace = false;
}
}
if(clean.size() > 0 && clean.back() == ' ') clean.pop_back();
vector<string> tokens;
string temp = "";
for(int i = 0 ; i < clean.size() ; i++)
{
if(isalnum(clean[i])) temp += clean[i];
else
{
if(temp != "")
{
//这里的作用是把每个单词放进tokens,便于单词替换
//遇到空格或者标点符号说明放完一个单词了
//接下来要清空temp便于下一个单词的放入
tokens.push_back(temp);
temp = "";
}
//char要转换为string才能放入
string sym(1,clean[i]);
tokens.push_back(sym);
}
}
//clean的行末可能有空格,可能没有,如果没有,会有一个temp没被加
//需要在循坏结束后特判一下
if(temp != "") tokens.push_back(temp);
for(int i = 0 ; i < tokens.size() ; i++)
{
string &now = tokens[i];
if(now == "can" && i + 2 < tokens.size() && tokens[i+1] == " " && tokens[i+2] == "you")
{
tokens[i] = "I";
tokens[i+2] = "can";
i+=2;
}
else if(now == "could" && i + 2 < tokens.size() && tokens[i+1] == " " && tokens[i+2] == "you")
{
tokens[i] = "I";
tokens[i+2] = "could";
i += 2;
}
else if(now == "I") tokens[i] = "you";
else if(now == "me") tokens[i] = "you";
}
cout<<"AI: ";
for(const string &t : tokens) cout<<t;
cout<<endl;
}
return 0;
}
😄还没完,还有一种牛逼轰轰的写法
- (后续我发现并没有)用到了s.find_first_not_of(" ") 和 s.find_last_not_of(" ") 分别可以找到字符串第一个不为空格的下标,和最后一个不为空格的下标,完美地控制了主体部分的范围。然后遍历主体,如果s[i] 和 s[i+1] 都是空格,就可以continue,保证只加入了一个空格
- 其实这里可以在clean的过程中 改变大小写和问号换感叹号等等;
- 只需要考虑单词替换的问题。这里用到了正则表达式 。
这里只介绍regex的replace函数
- 单词替换
#include<iostream>
#include<regex>
using namespace std;
int main()
{
string s = "I love you,can you love me?";
s = regex_replace(s , regex("\\bcan you\\b"), "I can");
cout<<s<<endl;
return 0;
}
- clean过程
s = regex_replace(s, regex("^ +| +$"), ""); // 删去首尾空格
s = regex_replace(s, regex(" +"), " "); // 多空格压成单空格
s = regex_replace(s, regex(" +([^a-zA-Z0-9 ])"), "$1"); // 删去标点前的空格 (匹配非字母数字空格)
解释说明
- \b表示边界,也可以说是与其他词的边界,在上述演示中,\b严格限制了替换的内容,它与find不同的是,如果字符串中有oldcan youschool,find会找到
can you并替换,而regex_replace找不到,它只能找到独立的can you。
- regex("^ +| +$")在该函数中起到了 搜索的作用 , ^表示从开头开始匹配 , 接下来的 “ +” 表示匹配开头的多个空格
"|"表示 或 , 即左右两种匹配搜索方式都可以 ,$$表示 结尾 , “ +$”表示匹配结尾前面的多个空格,实现了去除字符串
开头或结尾的多余空格
- regex(" +([^a-zA-Z0-9 ])")表示匹配([^a-zA-Z0-9 ])前面的多个空格,[]表示指定一个字符集,其内容为^a-zA-Z0-9 (这里有个空格请注意),^
为反的意思,a-zA-Z0-9 就是字符串中不是字母也不是数字还不是空格的字符,在题目限制下就是标点符号(逗号,感叹号等等),将标点符号和前面的空格
全部换成标点符号,"$1"表示[]匹配到的内容,这里就是标点符号。
ac代码✅️✅️
#include <iostream>
#include <string>
#include <regex>
#include <cctype>
using namespace std;
void solve()
{
string s;
getline(cin, s);
cout << s << endl; // 第一步:原样输出
// 1. 朴素版:大小写转换与问号替换 (这一步必须手动,最快最稳)
for (int i = 0; i < s.size(); i++)
{
if (s[i] >= 'A' && s[i] <= 'Z' && s[i] != 'I')
{
s[i] = tolower(s[i]);
}
else if (s[i] == '?')
{
s[i] = '!';
}
}
// 2. 正则版:清理空格 (顺序千万不能乱)
s = regex_replace(s, regex("^ +| +$"), ""); // 删去首尾空格
s = regex_replace(s, regex(" +"), " "); // 多空格压成单空格
s = regex_replace(s, regex(" +([^a-zA-Z0-9 ])"), "$1"); // 删去标点前的空格 (匹配非字母数字空格)
// 3. 正则版:单词替换 (利用大写 A 作为绝妙占位符)
s = regex_replace(s, regex("\\bcan you\\b"), "A can");
s = regex_replace(s, regex("\\bcould you\\b"), "A could");
s = regex_replace(s, regex("\\bI\\b"), "you");
s = regex_replace(s, regex("\\bme\\b"), "you");
s = regex_replace(s, regex("\\bA\\b"), "I"); // 最后把 A 变回 I
cout << "AI: " << s << endl;
}
int main()
{
int n;
if (cin >> n)
{
cin.ignore();
while (n--)
{
solve();
}
}
return 0;
}

浙公网安备 33010602011771号