时间限制:1.0s
内存限制:256MB
题目描述:
样例输入:
10 5
{
"firstName": "John",
"lastName": "Smith",
"address": {
"streetAddress": "2ndStreet",
"city": "NewYork",
"state": "NY"
},
"esc\\aped": "\"hello\""
}
firstName
address
address.city
address.postal
esc\aped
样例输出:
STRING John
OBJECT
STRING NewYork
NOTEXIST
STRING "hello"
分析
此题有以下几个难点,逐一分析
嵌套对象的存储与查找
很显然这种嵌套有两种处理方式,递归和顺序处理。
顺序处理
对于顺序处理,我们需要记录每一个对象的内容和其key。但是显然不能用一个map完成,一个map没办法对不同的类型value存储,并且也无法实现嵌套。
//像这样,map定义时就必须确定嵌套的层数
map<int,map<int,int> >
但是我们可以考虑,对每个对象{ }
都用一个map存储,因此我们定义一个map数组。
//假设总共最多100个对象
#define N 101
unordered_map<string, string> m[N];
那么对于每一个mapm[i]
,都存储一个{ }
内括起来的内容,其中可能有string:string
的映射,也可能有string:object
的映射。string:string
的映射正常insert()
,string:object
的映射就需要记录object所在的下标j。
用什么方法记录子对象所在map的下标呢,必须用string并且不会和正常的string:string
映射弄混。由于题目说字符串中不可能出现空格,我们就用空格串的长度记录下标。
那么现在仅仅需要确定对象们的存储顺序即可,我们就用DFS(用栈实现)的顺序来记录,例如:
{//i=0
"a":"v1",
"b":"v2",
"c":
{//i=1
"a":"v1",
"b":
{//i=2
"a":"v1"
}
},
"d":
{//i=3
"a":"v1"
},
"e":"v3"
}
递归
字符串处理
对于字符串处理有以下几点:
- 字符串中会有转义字符
'\'
- 字符串外面可能有任意空格和回车
对于转义字符,我们可以思考它存在的意义,本题中的转义字符仅仅转义"
和\
,这是由于字符串中的"
会带来歧义,编译器可能误认为它是字符串的结束;而\
则会让编译器误认为其要对后面的字符进行转义。如下例:
string s = "abc\"mm";//正确
string s = "abc"mm";//错误"
string s = "abc\\";正确
string s = "abc\";错误"
因此在处理含有转义字符的字符串时,只需要在一开始读字符串存入string的时候分辨好哪里是字符串的结束,去掉中间的多余转义字符即可。
对于回车和空格,由于无法确定在什么地方有多少,可以选择将其全部删除。可以使用getline(cin,s)
一行行读取,其默认分隔符为'\n'
,会读取一整行后将回车丢弃(个人理解),后面从第二行开始读取。可以使用下面代码替代字符串s中的空格:
s = regex_replace(s,regex(" "), "");
当然也可以单个字符读入时直接忽略。
解法
数据结构
存储json文件的map:
#define N 101
map<string , string> mp[N];
读入
预先处理: 读入json文件,首先一行行读入,并将每行去掉回车的内容整合成一个字符串,随后去掉其中的空格。
string s = "";
int tmpn = n;
while(tmpn--)
{
char tmps = "";
getline(cin,tmps);
s += tmps;
}
s = regex_replace(s,regex(" "),"");
单个字符读入:每次读入一个char,对其进行判断,如果是\n
则计数,记满n则读完;如果是{
则压栈;如果是"
则获取其包含的字符串s,压栈s;如果是}
则处理压栈的内容。
有以下几个小细节需要注意:
- 对于字符串为
"\\\\"
情况的处理————每处理一个转义字符,清空pre的记录 -
index
必须从1开始,如果从0开始则第一个对象的值处填的是空串,和其他的空格串不同,需要额外判断是空串或者空格串,增加工作量
string read_str()
{
string s = "";
char c = getchar();
char pre = c;
s += c;
while(c = getchar())
{
if(c == '"')
{
if(pre == '\\')
s[s.size()-1] = c;
else
break;
}
else if(c == '\\')
{
if(pre == '\\')
{
pre = 0;//注意此时'\\'是一个整体代表字符\,就算后面有"or\也不能再进行转义
continue;
}
else
s += c;
}
else
s += c;
pre = c;
}
return s;
}
//读入json文件
while(n)
{
char c = getchar();
if(c == '{')
st.push("{");
else if(c == '\n')
n--;
else if(c == '"')
st.push(read_str());
else if(c == '}')
{
index++;//注意index记录当前的对象编号,从1开始
while(st.top() != "{")
{
string value = st.top();
st.pop();
string key = st.top();
st.pop();
mp[index].insert(make_pair(key,value));
}
st.pop();//pop掉'{'
if(st.size() > 0)
st.push(string(index,' '));
}
}
查询
对于查询,首先需要将.
连接的字符串分割成一个个string,存入vector逐个查询。对于这一点的完成可以用函数strtok
,只能处理c串;也可以用regex_replace
将.
替换成" "
,再将替换后的字符串存成stringstream
,在其读出的时候会自动按空格分割。有以下细节需要注意:
- 正则表达式想要匹配
.
需要首先进行\.
转义,因为.
在正则表达式中具有特殊含义;而\
在正则表达式中也有特殊含义,因此需要用\\.
进行二次转义。 - 对于
sstream all
的读出all >> tmp
,当读完最后一个串时,sstream
中不含有字符了,故不会读入,tmp
仍然是之前读入的值。但是对于>>
运算会返回0值,因此可以用来判断。
分割好输入之后可以进行查询,其中index的值就是最外层也就是最后一个对象的编号,注意要多次查询不要将其覆盖。此外除了正常的未查到key、查到对象、查到值这三种情况外,还有一种是查询深度大于文件深度,此时也是NOTEXIST
void read_query(vector<string> &query)
{
string s = "";
cin >> s;
s = regex_replace(s,regex("\\.")," ");//注意需要二次转义
stringstream all;//头文件#include <sstream>
all << s;
string tmp = "";
while(all >> tmp)//相当于strtok
{
query.push_back(tmp);
}
}
//m个查询
while(m--)
{
vector<string> query;
read_query(query);
int j = index;//避免将index覆盖
for(int i = 0; i < query.size(); i++)
{
map<string,string>::iterator it = mp[j].find(query[i]);
if(it == mp[j].end())//map中没有该key
{
cout << "NOTEXIST" << endl;
break;
}
else
{
if(i == query.size()-1)
{
if(it->second[0] == ' ')//value是空格串
{
cout << "OBJECT" << endl;
break;
}
else//value是字符串
{
cout << "STRING " << it->second << endl;
break;
}
}
else
{
if(it->second[0] != ' ')//把字符串当作对象查询
{
cout << "NOTEXIST" << endl;
break;
}
j = it->second.size();
}
}
}
}
经验总结
此题有很多小细节容易疏忽,总结出经验如下
- 在处理输入的时候就可以构造输入进行测试,不然整个功能完成后对输入的一些限制条件已经记不清了。
- 也可以全部完成后再回头看题目的细节,构造可能的边界条件进行测试。
如:读题到字符串的转义部分时,发现可以出现
\\\\
的情况