CCF/CSP认证-第一次-命令行选项
1.问题
1.1 命令行选项
请你写一个命令行分析程序,用以分析给定的命令行里包含哪些选项。每个命令行由若干个字符串组成,它们之间恰好由一个空格分隔。这些字符串中的第一个为该命令行工具的名字,由小写字母组成,你的程序不用对它进行处理。在工具名字之后可能会包含若干选项,然后可能会包含一 些不是选项的参数。
选项有两类:带参数的选项和不带参数的选项。一个合法的无参数选项的形式是一个减号后面跟单个小写字母,如"-a" 或"-b"。而带参数选项则由两个由空格分隔的字符串构成,前者的格式要求与无参数选项相同,后者则是该选项的参数,是由小写字母,数字和减号组成的非空字符串。
该命令行工具的作者提供给你一个格式字符串以指定他的命令行工具需要接受哪些选项。这个字符串由若干小写字母和冒号组成,其中的每个小写字母表示一个该程序接受的选项。如果该小写字母后面紧跟了一个冒号,它就表示一个带参数的选项,否则则为不带参数的选项。例如, "abⓂ️" 表示该程序接受三种选项,即"-a"(不带参数),"-b"(带参数), 以及"-m"(带参数)。
命令行工具的作者准备了若干条命令行用以测试你的程序。对于每个命令行,你的工具应当一直向后分析。当你的工具遇到某个字符串既不是合法的选项,又不是某个合法选项的参数时,分析就停止。命令行剩余的未分析部分不构成该命令的选项,因此你的程序应当忽略它们。
输入格式
输入的第一行是一个格式字符串,它至少包含一个字符,且长度不超过 52。格式字符串只包含小写字母和冒号,保证每个小写字母至多出现一次,不会有两个相邻的冒号,也不会以冒号开头。
输入的第二行是一个正整数 N(1 ≤ N ≤ 20),表示你需要处理的命令行的个数。
接下来有 N 行,每行是一个待处理的命令行,它包括不超过 256 个字符。该命令行一定是若干个由单个空格分隔的字符串构成,每个字符串里只包含小写字母,数字和减号。
输出格式
输出有 N 行。其中第 i 行以"Case i:" 开始,然后应当有恰好一个空格,然后应当按照字母升序输出该命令行中用到的所有选项的名称,对于带参数的选项,在输出它的名称之后还要输出它的参数。如果一个选项在命令行中出现了多次,只输出一次。如果一个带参数的选项在命令行中出 现了多次,只输出最后一次出现时所带的参数。
样例输入
albw:x
4
ls -a -l -a documents -b
ls
ls -w 10 -x -w 15
ls -a -b -c -d -e -l
样例输出
Case 1: -a -l
Case 2:
Case 3: -w 15 -x
Case 4: -a -b
2.题解
2.1 字符串 + 哈希表 + 数组的综合应用(90分,有问题尚未解决,但不知道实际测试用例!!! 去除参数是否为命令的判断后拿到100分了)
思路
1.首先我们处理格式字符串,用一张哈希表记录有哪些命令和哪些命令需要参数
2.然后我们开始读取命令字符串,并且开始分解该字符串.
这里有几个注意点:
2.1 我们使用cin读取时,它自动以空格或者换行符为分隔符读取一个字符串,但这里很显然我们需要读取一整行的字符串,包含了空格。
所以我们采用getline来读取这一行字符串,但是这里有个问题,上面使用cin读取cnt个数时,是不读取末尾的换行符的,所以这里必须单独处理一下换行符,使用cin.ignore()或者getchar()即可
2.2 C++中并没有Java中类似split的拆分方法,但是
- 1.如果我们知道字符串的格式(固定),可以直接使用sccanf进行读取
- 2.如果不知道,就可以使用stringstream或者isstringstream流读取,配合while循环将字符拆出来
2.3 处理字符串时,略去第一个ls,我们开始进行拆解
- 1.如果表达式成立, 那么第一个一定是一个命令而不是一个参数, 之后每次我们要么单独处理一个命令,要么处理一个命令以及它的参数。所以每次开头的还是一个命令才是合法的(解释第一个if); 然后我们开始处理有参数和无参数情况(这里参数似乎只要是减号,小写字符和数字组成即可,虽然这就有可能组成一个命令(减号配字母),但这里好像如果是重复命令也判断为参数);对于不合法的情况直接break跳出,然后输出即可。
- 2.为了处理带参数的命令,只保留最后一次出现的参数,我们这里使用一个哈希表存储 命令 以及 其对应的参数(不用数组是因为我无法快速知道这个命令之前是否出现过,还得遍历)
- 3.对于使用哈希表,其自身无序性会破坏原有的输入顺序,我们在之后用一个vector数组接收哈希表中的数据,并进行升序排序(发现这里的命令输入,其实是按字节序增大的方向输入的)
2.4 这里使用正则表达式判断是否为合法参数的方式也可以学习一下
^表示开头,$表示结尾
[]表示可以包含这些元素, +代表至少有一个(非空)
如果^在[]内,则表示不包括的意思
代码
#include <bits/stdc++.h>
using namespace std;
// 检测是否为合法参数
bool isValidParameter(const string& str) {
// 使用正则表达式检查字符串是否只包含小写字母、数字和减号,并且非空
regex pattern("^[a-z0-9-]+$");
return regex_match(str, pattern);
}
int main() {
string op;
unordered_map<char, int> mp;
cin >> op;
// 初始化格式字符串
for (int i = 0; i < op.length(); i++) {
if(op[i] == ':') continue;
if (i + 1 < op.length() && op[i + 1] == ':') {
mp[op[i]] = 1; // 带参数选项
i++;
} else {
mp[op[i]] = 0; // 不带参数选项
}
}
int cnt;
cin >> cnt;
cin.ignore(); // 忽略换行符, 为下面的getline做准备
for (int i = 0; i < cnt; i++) {
// 1. 拆分字符串
string input;
getline(cin, input);
istringstream iss(input);
string temp;
vector<string> words;
while (iss >> temp) {
words.push_back(temp);
}
printf("Case %d: ", i + 1);
unordered_map<char, string> ans;
bool error = false;
// 2. 开始处理字符串
for (int j = 1; j < words.size(); j++) {
string word = words[j];
// 当前字符串是命令 且 在格式字符串中出现(只要抓住第一个肯定是命令,后面如果遇到参数就有j++,就又到了判断命令,所以不用单独列一个判断参数的)
if (word[0] == '-' && word.length() == 2 && mp.count(word[1])) {
char option = word[1];
// 带参数选项
if (mp[option] == 1) {
// 判断是否还有可能有参数且参数是否合法
if (j + 1 < words.size() && isValidParameter(words[j + 1]) ) {
ans[option] = words[j + 1];
j++; // 跳过参数
} else {
break; // 非法参数 / 无后续字符串
}
} else { // 不带参数选项
ans[option] = "";
}
} else {
break; // 未出现的命令 / 非法选项(参数开头/连续参数),停止解析
}
}
// 按字母升序输出结果
vector<pair<char, string>> sortedAns(ans.begin(), ans.end());
sort(sortedAns.begin(), sortedAns.end());
for (auto &p : sortedAns) {
if (p.second.empty()) {
cout << "-" << p.first << " ";
} else {
cout << "-" << p.first << " " << p.second << " ";
}
}
cout << endl;
}
return 0;
}
2.2 方法一优化-既定数组
思路
这里相比上面的思路,是我们在得知了他输入的顺序是按字节序增大方向的输入顺序之后写出的,我们发现既然顺序提前已知(用哈希表保存不了输入顺序,用未定数组(push_back)无法处理参数的最后一个参数);
这里就使用已定数组,一共26个小写字母,使用一个vector,前面步骤一样,记录该字母是有参还是无参,后面再进行判断的时候可以进行新增或者参数更新即可!
由于顺序已定,我们在输出的时候,直接按字节序顺序进行遍历,如果该位存在,那么便输出,否则继续遍历
代码
#include <bits/stdc++.h>
using namespace std;
const int N = 30;
bool op1[N], op2[N]; //op1存储无参数选项,op2存储有参数选项
int n;
string ans[N];
int main()
{
string str;
cin >> str; //读入所有的参数
for (int i = 0; i < str.size(); i ++) //解析参数是否带参数
{
if (i + 1 < str.size() && str[i + 1] == ':') //如果下一位没有越界,并且下一位是冒号,则该位为带参选项
{
op2[str[i] - 'a'] = true; //表明str[i]为带参选项
i ++; //跳过冒号
}
else
{
op1[str[i] - 'a'] = true;//表明str[i]为不带参选项
}
}
cin >> n;
getchar();//过滤回车
for (int C = 1; C <= n; C ++)
{
printf("Case %d:", C);
getline(cin, str); //读入整行需要用到getline,使用getline(如果上一个读入使用的是cin)需要将上一行的回车进行过滤
string t;
stringstream ssin(str);
vector <string> ops;
while(ssin >> t) ops.push_back(t);//将str按空格分层存储在数组
for (int i = 0; i < 26; i ++) ans[i].clear();//将ans清空
for (int i = 1; i < ops.size(); i ++) //第一个是名字,略过
{
if (ops[i][0] != '-' || ops[i][1] < 'a' || ops[i].size() != 2) break;
int k = ops[i][1] - 'a';
if(op1[k])
{
ans[k] = '*';
}
else if(op2[k] && i + 1 < ops.size())
{
ans[k] = ops[i + 1];
i ++;
}
else break;
}
for (int i = 0; i < 26; i ++)
{
if(ans[i].size())
{
cout << " -" << char('a' + i);
if(op2[i])
{
cout << ' ' << ans[i];
}
}
}
cout << endl;
}
return 0;
}
2.3 双指针处理字符串
思路
这里提供一种不适用stringstream或者isstringstream处理字符串分隔符的思路,使用快慢双指针将字符串分隔
代码
#include <iostream>
#include <unordered_map>
#include <cstring>
#include <vector>
#include <algorithm>
using namespace std;
unordered_map <string, int> m; // <-a, 1/2>
vector <string> work(string str) //利用双指针将串根据空格进行分割
{
vector <string> res;
res.clear();
for (int i = 0; i < str.size(); i ++)
{
int j = i; //i是字符
string t = "";
while (j < str.size() && str[j] != ' ') j ++; //j = str.size() 或 str[j] = ' '
t = str.substr(i, j - i);
res.push_back(t);
//cout << t << " ";
i = j;
}
return res;
}
int main()
{
string str;
cin >> str;
for (int i = 0; i < str.size(); i ++) //将格式串是否带参存储起来
{
string t = "-";
t += str[i];
if (str[i] == ':') continue;
if ((i < str.size() - 1 && str[i + 1] != ':') || i == str.size() - 1) m[t] = 1;
else m[t] = 2;
//cout << str[i] << " " << m[str[i]] << endl;
}
int n;
cin >> n;
getchar(); //读取回车
// 处理字符串
for (int i = 1; i <= n; i ++)
{
str = "";
getline(cin, str); //读取整一行字符串
vector <string> txt;
txt.clear();
txt = work(str);//将这行串根据空格进行拆分
unordered_map <string, string> res; //<选项,参数》
res.clear();
for (int j = 1; j < txt.size(); j ++)
{
string x = txt[j];
if (m[x] == 1)
{
res[x] = "*";
}
else if (m[x] == 2 && j < txt.size() - 1)
{
res[x] = txt[++j];
}
else break;
}
unordered_map <string, int> f;
f.clear();
// 按字节序进行排序
sort(txt.begin(), txt.end());
printf("Case %d:", i);
for (auto x : txt)
{
if (res[x] != "" && f[x] == 0)
{
f[x] = 1;
cout << " " << x;
if (res[x] != "*") cout << " " << res[x];
}
}
cout << endl;
}
return 0;
}