L1-064 估值一亿的ai核心代码 (分数20)字符串处理

天梯赛L1-064 😭已被打败

题目描述

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;
	
}

😄还没完,还有一种牛逼轰轰的写法

  1. (后续我发现并没有)用到了s.find_first_not_of(" ") 和 s.find_last_not_of(" ") 分别可以找到字符串第一个不为空格的下标,和最后一个不为空格的下标,完美地控制了主体部分的范围。然后遍历主体,如果s[i] 和 s[i+1] 都是空格,就可以continue,保证只加入了一个空格
  1. 其实这里可以在clean的过程中 改变大小写和问号换感叹号等等;
  1. 只需要考虑单词替换的问题。这里用到了正则表达式 。

这里只介绍regex的replace函数

  1. 单词替换
#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;
}
  1. clean过程
s = regex_replace(s, regex("^ +| +$"), "");          // 删去首尾空格
s = regex_replace(s, regex(" +"), " ");              // 多空格压成单空格
s = regex_replace(s, regex(" +([^a-zA-Z0-9 ])"), "$1"); // 删去标点前的空格 (匹配非字母数字空格)

解释说明

  1. \b表示边界,也可以说是与其他词的边界,在上述演示中,\b严格限制了替换的内容,它与find不同的是,如果字符串中有oldcan youschool,find会找到

can you并替换,而regex_replace找不到,它只能找到独立的can you。

  1. regex("^ +| +$")在该函数中起到了 搜索的作用 , ^表示从开头开始匹配 , 接下来的 “ +” 表示匹配开头的多个空格

"|"表示 , 即左右两种匹配搜索方式都可以 ,$$表示 结尾 , “ +$”表示匹配结尾前面的多个空格,实现了去除字符串

开头或结尾的多余空格

  1. 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;
}
posted @ 2026-03-10 12:45  shuiwangrenjia  阅读(9)  评论(0)    收藏  举报