一、题目
二、解题
1.题目
实现一个JSON查询器。它读取一个JSON格式的字符串,解析为一个map<string, string>的字典,键为JSON中的路径,值为JSON中对应路径的值。然后它读取一些查询字符串,并根据查询字符串查找对应的值并输出。
程序的实现分为三个部分:
- 解析JSON字符串部分
这部分代码主要实现了解析JSON格式的字符串并将其解析为一个键值对的字典。首先调用parseObject函数,该函数接收JSON字符串,一个前缀(表示JSON中各层级的路径),一个引用的空字典,以及一个int型变量i(表示位置)的引用作为参数。parseObject函数主要采用递归的方式实现对JSON字符串的解析,具体步骤如下:
(1) 如果当前位置的字符是左括号,则说明该位置代表一个对象(object),idx加1之后进行下一步解析。
(2) 首先设置一个标识符strType,该变量初始值为false,表示当前解析的是键,而不是值。然后进入循环,按照从左到右依次解析每个键值对:
- 如果当前位置的字符是双引号,则说明后面跟着的是一个字符串,需要调用parseString函数解析出该字符串的值。
- 如果strType为false,则说明当前解析的是键,需要把当前解析出的字符串累加到前缀上,并设置strType为true表示后面需要解析值。
- 如果strType为true,则说明当前解析的是值,需要把当前解析出的字符串作为值存入字典中,并把strType重新置为false,表示需要解析下一个键。
- 如果当前位置的字符是逗号,则说明后面还有其他的键值对,需要把strType置为false,表示需要解析下一个键。循环接着进行下一次迭代。
- 如果当前位置的字符是左括号,则说明后面跟着的是一个新的对象(object),需要在字典中为当前键设置一个空值,并递归地调用parseObject函数解析这个新的对象。
- 如果当前位置的字符是右括号,则说明当前的对象解析完成,跳出循环。
(3) 如果当前位置的字符是右括号,则说明当前的对象解析完成,函数结束。
- 读取查询字符串部分
这部分代码通过循环读取查询字符串,并调用map的find方法查找该查询字符串是否存在于解析后的字典中。如果不存在,则输出NOTEXIST;如果存在,则分两种情况输出结果:
- 如果该查询字符串在字典中对应的值为空,说明该字符串是一个路径,但不是叶子节点,输出OBJECT;
- 如果该查询字符串在字典中对应的值不为空,说明该字符串是一个叶子节点的路径,输出STRING和对应的值。
- 主函数部分
这部分代码主要是读入N和M的值,以及读取N行输入的JSON字符串,调用parseObject函数解析JSON字符串,并读取M个查询字符串,调用map的find方法查找每个查询字符串在字典中的值并输出。
2.代码
dev c++ 5.11
#include<iostream>
#include<map>
#include<cassert>
using namespace std;
string parseString(string &str,int &idx){
string tmp;
if(str[idx]=='"') idx++;
else assert(0);
while(idx<str.size()){
if(str[idx]=='\\' ){
idx++;
tmp+=str[idx];
idx++;
}
else if(str[idx]=='"'){
break;
}
else{
tmp+=str[idx];
idx++;
}
}
if(str[idx]=='"') idx++;
else assert(0);
return tmp;
}
void parseObject(string &str,string prefix, map<string,string> &dict,int &idx){
if(str[idx]=='{') idx++;
else assert(0);
string key,value;
bool strType=false;//false对应key,true对应value
while(idx<str.size()){
if(str[idx]=='"'){//遇到:,解析key-value
string tmp=parseString(str,idx);
if(strType){
value=tmp;
dict[key]=value;
}
else{
key=prefix+(prefix==""?"":".") + tmp;
}
}else if(str[idx]==':'){//遇到:,说明现在开始解析value
strType=true;
idx++;
}else if(str[idx]==','){//遇到,,说明value解析完成,开始解析下一个key
strType=false;
idx++;
}else if(str[idx]=='{'){//遇到{,就是解析子对象的情况,将子对象的key设置为空字符串
dict[key]="";
parseObject(str,key,dict,idx);//递归解析
}else if(str[idx]=='}'){
break;
}else{
idx++;
}
}
if(str[idx]=='}') idx++;
else assert(0);
}
int main(){
int N,M;
cin>>N>>M;
string json;
if(cin.peek()=='\n') cin.ignore();//清除回车
for(int n=0;n<N;n++){
string tmp;
getline(cin,tmp);
json+=tmp;
}
map<string,string> dict;
int idx=0;
parseObject(json,"",dict ,idx);
string query;
for(int m=0;m<M;m++){
getline(cin,query);
if(dict.find(query)==dict.end()){//查询不到
cout<<"NOTEXIST"<<endl;
}else{
if(dict[query]==""){
cout<<"OBJECT"<<endl;
}else {
cout<<"STRING "<<dict[query]<<endl;
}
}
}
}
3.提交结果
总结
1.解释
- 输入数据
if(cin.peek()=='\n') cin.ignore();//清除回车
for(int n=0;n<N;n++){
string tmp;
getline(cin,line);
json+=tmp;
}
首先,if(cin.peek()==‘n’) cin.ignore(); 的作用是在输入完 N 后,忽略掉一个回车符。因为在输入完 N 之后,按下回车键会把回车符n留在输入流中,如果不忽略掉这个回车符,下一行输入 getline(cin, tmp) 就会读取到这个回车符,导致接下来的输入有问题。
其次,for(int n=0; n<N; n++) 循环用于读取 N 行 JSON 文本,并将读取到的每一行文本都附加到 json 变量末尾,以便之后进行解析。getline(cin, tmp) 意思是将 cin 流中的一行输入读入到 tmp 变量中,这里一行输入对应着一个 JSON 对象。每次读取完一行后,将 tmp 的内容附加到 json 变量末尾。最终,json 变量就包含了输入流中的所有文本。
- 函数
parseObject(json, "", dict, i)
是对读入的 JSON 进行解析,解析完后将结果存储到 dict 变量中,以便之后查询某个 key 的值。
- str:表示待解析的JSON字符串;
- prefix:表示当前对象的前缀,即此对象在整个JSON串中的完整路径,如"a.b.c";
- dict:表示解析得到的键值对,是一个map类型,用来存储JSON对象中的所有键值对;
- i:表示当前解析到的字符在JSON字符串中的下标位置。
dict[key] = "";
这个程序中,dict[key] = “”; 的意思是将一个空字符串作为当前父对象(prefix)下key所代表的子对象({})的值存储到了map中,以便在后续的查询(if语句中dict[query]==“”)时能够识别当前的查询对象是否是一个空的子对象。
举例说明:假设有JSON字符串{“person”:{“name”:“Alice”,“address”:{}}},将该JSON字符串解析后存储到map中,其中prefix为空字符串,解析开始时dict为空。遇到"person"时,key=“person”,strType=false,idx向后移动。遇到"name"时,key=“person.name”,strType=false,idx向后移动。遇到"Alice"时,value=“Alice”,strType=true,将"person.name"和"Alice"存储到dict中。遇到"address"时,key=“person.address”,strType=false, dict[key]=“”;将一个空串作为当前父对象下"address"所代表的子对象的值存入dict中,再递归解析该空对象。因为该子对象为空,解析该子对象时会将dict[“person.address”]设置为空串。
这样,当查询"person.address"时,由于dict[“person.address”]==“”,所以程序会输出OBJECT来表示查询对象是一个空子对象,否则将输出查询对象的字符串值。
key=prefix+(prefix==""?"":".") + tmp;
这句代码是将解析得到的key值与一个前缀字符串 prefix 拼接起来,得到完整的 key 值,用于存储到 map 容器(字典)。它前面的条件语句 (prefix==“”?“”:“.”) 的作用是在非空的前缀字符串前加上一个点号,表示当前 key 值是由前缀字符串及当前字符串组成。举个例子:
如果当前的 key 是 “age”,前缀字符串是 “person”,那么拼接起来的完整 key 就是 “person.age”。
如果当前的 key 是 “name”,没有前缀字符串,那么拼接起来的完整 key 就是 “name”。