CSP-J2021第三题——字符串处理

原题链接
先说一下今年的题目特点,那就是并没有考到复杂的算法,而是考了一些应用性较强的内容,比如第三题的字符串处理。很多OI选手对字符串处理都不擅长,导致这道题失分。其实,今年的题目还是很简单的。下面,我们来探讨一下这道题的几种解法。(只介绍字符串处理部分,其他地方太简单,正常人都能写出来)

一、正则表达式匹配

先介绍一下正则表达式:(详细教程看这里
正则表达式是匹配字符串的一种模式。经常用到的语法有:

字符意义
+表示前面的字符至少出现1次,无上限。
*表示前面的字符至少出现0次,无上限。
?表示前面的字符只能出现0或1次。
[]匹配括号中的字符之一。如果字符在一个区间里,可以用-连接,如[0-9]表示匹配一个数字,[a-zA-Z]表示匹配一个字母。
.匹配任意非换行的字符。
\转义字符,所有在该表格内的字符(当然还包括其它的),如果想匹配它,必须前面加上一个\。而一些普通字符加上了\,就有了特殊含义。注意,在C++中,\本来就表示转义字符,所以要写两个\,比如匹配一个星号,要写"\\*"
{m,n}表示前面的字符至少出现m次,最多出现n次。
{m,}表示前面的字符至少出现m次,无上限。(注意,m后面有逗号)
{m}表示前面的字符只能出现m次。
\d匹配一个数字,即[0-9]。
()有两个作用,第一个是表示一串字符是一个整体,第二个是匹配后可以查看括号里具体匹配了什么。

除了有特殊含义的字符,其它的字符的正则就是它本身,比如想匹配"hello",正则就是"hello"
要注意的是,*,+,{}都是贪婪的,也就是说会尽可能匹配更多的字符,如对字符串"你好123"使用正则"(.*)([0-9]+)",结果并不是"你好","123",而是"你好12","3"。想避免这种情况,要在它们后面加一个 ? ,如上例,如果用"(.*?)([0-9]+)",结果就是"你好","123"了。但是,这些都有一个前提,那就是必须先保证匹配成功,才考虑贪婪不贪婪的问题。如"(.*?)([0-9]+)",尽管它不是贪婪的,但.*还是会匹配"你好",而不是一个字符也不匹配,因为那样就无法匹配成功了。
接下来,我们学习一下C++中的正则:C++从C++11开始支持正则,而今年的CSP-J标准是C++14,所以说可以使用。使用正则,首先要包含这个头文件:

#include<regex>//STL的一部分,包含关于正则的内容。

C++中关于正则主要有regex_search和regex_match这两个函数,前者要求字符串部分匹配,后者要求字符串完全匹配。还有一个容器,是smatch容器,用于存储正则匹配的结果。
我们来看一下代码(主函数是我写的测试,包含了我能想到的所有错误情况,并不是题解):

#include <iostream>
#include<regex>
bool match(const std::string& str)
{
	std::smatch m;
	if (std::regex_match(str, m, std::regex("([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\:([0-9]{1,5})")))
	{
		for (size_t i = 1; i < m.size(); ++i)//注意,这里的i要从1开始,因为smatch容器第一个是匹配的字符串整体
		{
			const std::string& s=m.str(i);
			if ((s.size() > 1 && s[0] == '0')/*前导0*/ || (i != 5 && s > "255") || (s > "65535")/*超出范围*/)
			{
				return false;
			}
		}
		return true;
	}
	return false;
}
int main()
{
	const std::string strs[] = {"127.0.0.1:185","255.255.255.255:65535","0.0.0.0:0","256.1.1.1:1","125.0.0.0:1288a",
	"hehe","呵呵","127.0.0:1","7:7:7:7.7","05.04.5.7:8","999999999.99999.9999999.999999:-888"};
	for (const auto& s : strs)
	{
		std::cout << match(s) << '\n';
	}
	return 0;
}

运行结果:
运行结果

二、sscanf匹配

sscanf函数大家应该熟悉,就是从字符串输入元素,代码很简单,唯一要注意的是,必须用%d输入,%s无法正确匹配(因为第一个就会把所有字符都匹配)。所以这样就有些麻烦了,判断前导0必须求出每个数字的位数,然后一加,和原字符串长度比较。另外,sscanf返回值是成功读入数据数量。
代码:

#include <iostream>
#include<regex>
#include<functional>
bool match(const std::string& str)
{
	std::function<int(int)>func([](int x) {
		int i = 1;
		while (x /= 10)
			i++;
		return i;
	});
	int ip[5];
	if (sscanf(str.data(), "%d.%d.%d.%d:%d", &ip[0], &ip[1], &ip[2], &ip[3], &ip[4]) == 5)
	{
		for (int i = 0; i < 5; i++)
		{
			if ((i != 4 && ip[i] > 255) || ip[i] > 65535)
			{
				return false;
			}
		}
		int sum = 4;
		for (int i = 0; i < 5; ++i)
		{
			sum += func(ip[i]);
		}
		return sum == str.size();
	}
	return false;
}
int main()
{
	const std::string strs[] = {"127.0.0.1:185","255.255.255.255:65535","0.0.0.0:0","256.1.1.1:1","125.0.0.0:1288a",
	"hehe","呵呵","127.0.0:1","7:7:7:7.7","05.04.5.7:8","999999999.99999.9999999.999999:-888"};
	for (const auto& s : strs)
	{
		std::cout << match(s) << '\n';
	}
	return 0;
}

运行结果:
运行结果

三、手写

自己写个代码来匹配,很麻烦,不推荐使用。

几种算法的比较

正则和sscanf这两个在时间复杂度上,都差不多,单看匹配的速度,sscanf应该比正则快 (等等,我突然发现sscanf好像也支持正则…) ,但sscanf需要求出位数,所以应该不相上下。
而自己手写的,应该是最快的,一般不会超时,但很浪费答题时间,而且还可能有bug,所以还是老老实实用库函数好…

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值