JSON查询

目录

问题描述

输入格式

样例输入

解题思路

满分代码

一些STL使用的坑

substr

map的迭代器的问题


这道题,我比较感兴趣,是因为之前对网络爬虫十分感兴趣,因此利用python语言学习过相关网络爬虫的知识,也会用它爬取一些图片信息等,而且写了一个可以模拟登入post表单的爬虫,现在还在使用中,嘿嘿~而json文本的处理又是爬虫中很重要的一个知识,其实我也知识对这个略知一二,我没有系统学过JavaScript,不过还是比较了解的,所以选择它来做。并且通过它我也加深了STL容器和相关函数的使用。这道题来自于2017年CCF的9月考的第三题,奈何我菜狗一个,只能到第三题了,哈哈哈,就是如此也是改了好久才把bug统统找出来改正。嗐。讲解完这个题目,我会列出几点STL函数的坑,希望能帮助你下一次不要才进去,然后出不来了(T _ T) 

问题描述

  JSON (JavaScript Object Notation) 是一种轻量级的数据交换格式,可以用来描述半结构化的数据。JSON 格式中的基本单元是值 (value),出于简化的目的本题只涉及 2 种类型的值:
  * 字符串 (string):字符串是由双引号 " 括起来的一组字符(可以为空)。如果字符串的内容中出现双引号 ",在双引号前面加反斜杠,也就是用 \" 表示;如果出现反斜杠 \,则用两个反斜杠 \\ 表示。反斜杠后面不能出现 " 和 \ 以外的字符。例如:""、"hello"、"\"\\"。
  * 对象 (object):对象是一组键值对的无序集合(可以为空)。键值对表示对象的属性,键是属性名,值是属性的内容。对象以左花括号 { 开始,右花括号 } 结束,键值对之间以逗号 , 分隔。一个键值对的键和值之间以冒号 : 分隔。键必须是字符串,同一个对象所有键值对的键必须两两都不相同;值可以是字符串,也可以是另一个对象。例如:{}、{"foo": "bar"}、{"Mon": "weekday", "Tue": "weekday", "Sun": "weekend"}。
  除了字符串内部的位置,其他位置都可以插入一个或多个空格使得 JSON 的呈现更加美观,也可以在一些地方换行,不会影响所表示的数据内容。例如,上面举例的最后一个 JSON 数据也可以写成如下形式。
  {
  "Mon": "weekday",
  "Tue": "weekday",
  "Sun": "weekend"
  }
  给出一个 JSON 格式描述的数据,以及若干查询,编程返回这些查询的结果。

输入格式

  第一行是两个正整数 n 和 m,分别表示 JSON 数据的行数和查询的个数。
  接下来 n 行,描述一个 JSON 数据,保证输入是一个合法的 JSON 对象。
  接下来 m 行,每行描述一个查询。给出要查询的属性名,要求返回对应属性的内容。需要支持多层查询,各层的属性名之间用小数点 . 连接。保证查询的格式都是合法的。

输出格式

  对于输入的每一个查询,按顺序输出查询结果,每个结果占一行。
  如果查询结果是一个字符串,则输出 STRING <string>,其中 <string> 是字符串的值,中间用一个空格分隔。
  如果查询结果是一个对象,则输出 OBJECT,不需要输出对象的内容。
  如果查询结果不存在,则输出 NOTEXIST。

样例输入

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"

评测用例规模与约定

  n ≤ 100,每行不超过 80 个字符。
  m ≤ 100,每个查询的长度不超过 80 个字符。
  字符串中的字符均为 ASCII 码 33-126 的可打印字符,不会出现空格。所有字符串都不是空串。
  所有作为键的字符串不会包含小数点 .。查询时键的大小写敏感。
  50%的评测用例输入的对象只有 1 层结构,80%的评测用例输入的对象结构层数不超过 2 层。举例来说,{"a": "b"} 是一层结构的对象,{"a": {"b": "c"}} 是二层结构的对象,以此类推。

解题思路

借助于我对json的了解,他其实就是一个字典,pthon的字典有很强大功能,而c++没有这种,但是由于多重嵌套的原因,我还是选择用c++来实现,因为担心层数过深,导致效率太低。不过应该是没什么问题其实,如果你喜欢python可以实验一下。

首先就是定义一个object的结构题,我老结构体带师了,哈哈。这个结构体描述的具体是什么?他其实就是{花括号}里面的东西,就是一个object结构。不信你看看。我的object中含有两个map,分别是string映射到string的和string映射到object的。这就分别代表了,一个花括号中的“字符串键-字符串值”和“字符串键-另外一个花括号值”的两种对象。为什么用map来做,map在STL中底层是利用二叉树来存储的,它拥有强大查找功能,其查找的效率为对数级,那么我们的目的不正是查找某一个键对应的东西吗?

然后就是正儿八经的输入。但是难点还是处理字符串啊。按照题目的意思处理字符串。然后是查找算法。具体见代码。

满分代码

#include <iostream>
#include <string>
#include <map>
using namespace std;

struct object { //这就是我们的object类型,它表示一对花括号之间的内容。
	bool flag=0; 
    //这里设计一个红旗,用于快速判断这个花括号有没有内容,只要有内容就为真,利用它,就不要去看        它的字符串键值对和对象键值对了
	map<string, string> str_dict;
	map<string, object> obj_dict;
};
object json; //初始化我们的json对象

//读n行,把它们组合成一个样本字符串
inline void Getline(string &s, int n) { 
    //读取n行的json字符串,把它组成一个整体字符串,后面分析
	string t; s="";
	while (n--) {
		getline(cin, t); s+=t;
	}
}

//修饰美化字符串,题目给的键和字符串值都是由双引号包围起来,这表示它是字符串,但是我们读取进来不需要双引号,所以把它去掉,然后是字符串中间的"\""和"\\",这只是它表示它是'\'和'"'的标志,但是我们不需要它,把它修正
void prettify(string &s) {
	string::size_type l, r;
	l=s.find('\"'); r=s.rfind('\"');
	s=s.substr(l+1, r-l-1);
	string::iterator it=s.begin();
	while (it<s.end()-1) {
		if ((*it=='\\'&&*(it+1)=='\\')||(*it=='\\'&&*(it+1)=='\"')) it=s.erase(it);
		else ++it;
	}
}


//根据给定的字符串,这个字符串再最外层包含花括号,即是花括号把内容包括起来,根据字符串对传进来的object引用修改,把它改造成合格的object
void BuildObject(string s, object &obj) {
	obj.flag=1; //表示这个object不是空的,便于多层次查找
	
	//进入{}处理对象,去掉最外层的花括号,并在最后加上逗号
    //为什么加逗号:键字符串后面会接一个冒号,表示读到这之前的字符串是键;对于不是最后一堆键值对来说,值后面都有一个逗号,这表示前面读到的是值,所以把花括号去掉,然后再加上一个逗号,就可用循环来读取所有的键值对了。
	s=s.substr(s.find('{')+1, s.rfind('}')-1)+',';
	
	//计算键值对个数
	int num=0;
	int f=0;
    //这个循环是用来计算在这个object下有多少对键值对,但是一个重要的点是,你必须要警惕值是object的那些键值对的字符串,所以每次碰到一个左花括号就让f加一,右花括号减一,只有f是0,才能计数,这类似于栈的思想
	for (string::iterator t=s.begin(); t!=s.end(); ++t) {
		if (*t==':'&&!f) ++num;
		else if (*t=='{') ++f;
		else if (*t=='}') --f;
	}
	
	//建立obj
	string key_str, val_str;
	string::size_type p;
	while (num--) { //一次扫码num对键值对
	//以冒号结尾是键,这里是认为键中不会有冒号的假设,事实上确实如此,属性一般为单词,合理推断
        p=s.find(':'); 
		key_str=s.substr(0, p); s.erase(0, p+1);
		f=0; p=0;
        //以逗号结尾是值,要小心值是object的嵌套结构,具体解决办法类似计算num的时候
		while (1) {
			if (s[p]==','&&!f) break;
			if (s[p]=='{') ++f;
			else if (s[p]=='}') --f;
			++p;
		}
		val_str=s.substr(0, p); s.erase(0, p+1);
		//对象里面只有字符串
		prettify(key_str); 
		string::size_type l=val_str.find('{'), r=val_str.rfind('}');
		if (l==val_str.npos&&r==val_str.npos) { //值不为对象,是字符串,因为找不到花括号了
			prettify(val_str);
			obj.str_dict[key_str]=val_str;
		}
		else { //值中含对象
			BuildObject(val_str.substr(l, r-l+1), obj.obj_dict[key_str]); 
            //递归构造含花括号的值object
		}
	}
}

void BuildJson(int n) { //这是总的构造函数
	string s; Getline(s, n);
	BuildObject(s, json);
}


//查找函数,“.”结尾的字符串,以这个点为分割,把它查找的不同层次划分出来,依次进入查找
void Check(string &key, object json) {
	while (key!="") {
		string::size_type p=key.find('.');
		string k=key.substr(0, p);
		key.erase(0, p+1);
		if (json.str_dict[k]!=""&&key=="") {
			cout<<"STRING "<<json.str_dict[k]<<endl;
			return;
		} else if (json.obj_dict[k].flag) {
			if (key=="") cout<<"OBJECT"<<endl;
			else Check(key, json.obj_dict[k]);
		}
		else {cout<<"NOTEXIST"<<endl; return;}
	}
}

int main() {
	int n, m; cin>>n>>m; getchar();
	BuildJson(n);
	string query_key;
	while (m--) {
		cin>>query_key; 
		query_key+=".";
		Check(query_key, json);
	}
	return 0;
} 


一些STL使用的坑

这里提出一些STL使用的坑,太苦了我了。嗐。

substr

如果它传的是一个参数,他表示它从这个参数位置截取到最尾巴。这个参数可意识string::size_type类型的变量,也就是类似于整数,用于下标遍历的;也可以是字符串的迭代器。

如果它传的是两个参数,这是我要着重强调的,因为我就是一直卡在这个坑里,找不出bug。它如果传两个参数,有两种情况。第一种是它如果传的是两个整数,也就是string::size_type类型时候,第一个变量,表示从某一个位置开始截取,第二个参数表示截取几个字符!这两个参数不是代表了一个区间不是区间!如果你以为这是区间,碰巧你的第一个参数可能是0,那么刚好后面一个参数既可以是区间的右坐标,也可以是字符个数,因为它们大小相同,然而这只是巧合,你却很难发现,这意味有一些情况结果符合预期,你会蒙蔽。还有一种情况,即是传参数是两个迭代器,那这就是截取一个区间了。

map的迭代器的问题

注意map是已经排好序的容器(sorted),它自动排序,这源于它底层利用二叉平衡树的原因。这种已序容器,它的迭代器不是随即迭代器,是双向迭代器。同样情况还有set,multiset,map,multimap,list,随机迭代器可以进行迭代器运算,就是说迭代器iterator可以加上一个数,然后到对应位置,但是map不行,它的迭代器只能++,--,!=,==等运算,不能+2,-3,<,<=这样的运算。这是不合法的,原因是没有定义这样的运算,这也是STL迭代器类型不同造成的。

所以一个要注意的点是循环中,如下

for (string::iterator t=s.begin(); t!=s.end(); ++t)

这个代码中注意到是t!=s.end(),而不是小于,这个比较细节,不然你可能异常了,也不知道哪里报错。

当然也不需要我提醒,大概大部分人都知道,++t比t++效率要高,原因是后者需要创建一个临时变量保存原来的值,因为后者用来赋值做右值式子表示是加之前的值。--的运算类似。

差不多就这么多,感谢你的阅读,觉得写的不错可以点赞,点一波关注~我会把我的刷题过程中碰到觉得值得的题目updown的~

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值