时间限制:1.0s

内存限制:256MB

题目描述:

postgres查询json数组中的某个字段_转义


样例输入:

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"
}
递归

字符串处理

对于字符串处理有以下几点:

  1. 字符串中会有转义字符'\'
  2. 字符串外面可能有任意空格和回车

对于转义字符,我们可以思考它存在的意义,本题中的转义字符仅仅转义"\,这是由于字符串中的"会带来歧义,编译器可能误认为它是字符串的结束;而\则会让编译器误认为其要对后面的字符进行转义。如下例:

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;如果是}则处理压栈的内容。

有以下几个小细节需要注意:

  1. 对于字符串为"\\\\"情况的处理————每处理一个转义字符,清空pre的记录
  2. 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,在其读出的时候会自动按空格分割。有以下细节需要注意:

  1. 正则表达式想要匹配.需要首先进行\.转义,因为.在正则表达式中具有特殊含义;而\在正则表达式中也有特殊含义,因此需要用\\.进行二次转义。
  2. 对于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();
      }
    }
  }
}

完整代码

经验总结

此题有很多小细节容易疏忽,总结出经验如下

  1. 在处理输入的时候就可以构造输入进行测试,不然整个功能完成后对输入的一些限制条件已经记不清了。
  2. 也可以全部完成后再回头看题目的细节,构造可能的边界条件进行测试。

如:读题到字符串的转义部分时,发现可以出现\\\\的情况