zoukankan      html  css  js  c++  java
  • UVA

    /*
      这题自己想的时候,想到可以用栈来匹配和拆掉括号,这个还是比较容易想到,而判断是“定义语句”,还是“赋值语句”,也可以想到用find函数,看能否找到等号。
      
      但是,具体到怎么完整实现的时候,思路就有点懵了,可见做题量还是不够T^T
      
      最后还是借用别人的思路,按照别人的框架,自己理解以后,再独立写了一遍
    */


    /*
      法一:
      参考自blog:http://www.cnblogs.com/kunsoft/p/5312710.html
      (法一非常短,这个博主的功底真.相当不错)
      
      收获:
      * 逗号表达式,将逗号运算符后面的值作为表达式的返回值
      http://blog.csdn.net/fjb2080/article/details/5174171
      
      * 关于string::npos(在find函数找不到某个字符时,就会返回string::npos)
      http://blog.csdn.net/devil_pull/article/details/25478525
      
      * 可用 to_string函数和 stoi函数,实现string类型和int类型之间的相互转换
      http://www.cplusplus.com/reference/string/stoi/
      http://www.cplusplus.com/reference/string/to_string/
      
      * stoi函数 和 atoi函数的区别
      https://stackoverflow.com/questions/20583945/what-is-the-difference-between-stdatoi-and-stdstoi
      (atoi是C语言对char*字符串的函数,如果要对string使用,应该先调用c_str函数进行转化)
      
      
      * count函数,可计算某个字符在字符串中出现的次数
      也可以计算某个数组中,某个特定的值一共出现了多少次
      http://www.cplusplus.com/reference/algorithm/count/
      
      * 我觉得他代码最大的亮点,其实是他递归的处理。
      递归的写法总是有些抽象和难看懂的,看懂尚且难,能想到怎么写就更难了,所以,这道题最值得回顾的是他的递归思路。他是怎么递归的
      
      以及,还需要想明白,对赋值语句的左值和右值,他分别又是怎么用substr()函数分离出部分字符串,并传入同一个处理函数一起处理的
      
      这两点想清楚以后,这题也就差不多了,虽然他的代码不长,看懂却也不太容易,我自己看时,就加上了很多注释,以防复习时,又看不懂了
    */

    #include <iostream>
    #include <map>
    #include <string>
    #include <sstream>
    #include <vector>
    #include <algorithm> // count函数 
    using namespace std;
    
    int bug = -1;
    vector<string> code; //存放所有代码 
    map<char, int> arr; //array,存放所有已定义的数组的大小
    map<string, int> val; //value,表示已经被初始化的元素,和其对应值的对应关系 
    
    int get_val(const string& str) 
    {
    	if ( str.find("[") == string::npos ) return stoi(str);//说明中括号都已被去掉,字符串中只剩下数字(这种情况出现在 lval的计算调用get_val时)
    	
    	if ( count(str.begin(), str.end(), '[') == 1)
    	{
    		if (!val.count(str)) return -1;
    		return val[str];
    	} 
    	//只有一个中括号时,不需要再成对去括号了,直接检查是否有定义该变量,有定义则返回
    	if ( count(str.begin(), str.end(), '[') >= 2)
    	{
    		string cur = str.substr(str.find('[') + 1);
    		int v = get_val(cur);
    		//不断递归,每次去掉一层数组名和一个[
    		if ( !val.count( str.substr(0, 2) + to_string(v) ) )
    		return -1;
    		return val[str.substr(0, 2) + to_string(v)];
    		//补上这个数组名和[,并且确认此值之前是否初始化,若有则可直接返回,否则返回-1
    	}
    	
    		//我自己敲的时候,由于if ( !val.count( str.substr(0, 2) + to_string(v) ) )   这句多敲了一个分号,导致WA,检查了特别久的bug(一定要细心细心再细心,不然这样的错误,真的很难发现)
    		//其实讲道理,我这次本来都不可能发现的,我还是把原博主的AC代码,一段段复制过来修改,缩小范围,确定我的代码是在哪个区域由AC变为WA的,不断不断复制过来改动再缩小错误范围...折腾了很久,才发现是当初多敲了一个分号 T^T
    }
    
    void add_arr(const string& str)
    {
    	stringstream ss(str.substr(2));//下标为2的那一位,为数组的大小
    	int v;
    	ss >> v;
    	arr[str[0]] = v;
    }
    
    int main()
    {
    	cin.tie(0);
    	cin.sync_with_stdio(false);
    	string str;
    	
    	while (code.clear(), val.clear(), bug = -1, true)
    	{
    		cin >> str;
    		if (str == ".") break;
    		while (true)
    		{
    			code.push_back(str);//代码都存入vector中,输入完再一起处理
    			cin >> str;
    			if (str == ".") break;
    		}
    		
    	for (size_t i = 0; i != code.size(); i++)
    		{
    			if (code[i].find("=") == string::npos)
    			add_arr(code[i]);
    			else
    			{
    				string left = code[i].substr(0, code[i].find("="));
    				string right = code[i].substr(code[i].find("=") + 1); //将字符串以 = 为界,分为两个串
    				
    				
    				int l_val = get_val( left.substr( left.find("[") + 1,  left.find("]") - left.find("[") - 1 )  );
    				
    				//get_value的参数,去掉了字符串l中,所有的],还去掉了数组名和第一个[, 剩下的便只有[了
    				//之所以可以这样写,是因为只有下标越界,和使用未初始化变量,这两种bug,所以默认括号都是一一匹配的,直接一次性去掉所有],再一层层去掉[,并不会影响最终结果
    				
    				//之所以要这样写,是为了分离出第一层[]外的部分,例如g[b[5]] = 7这句代码,b[5]是要判断,其是否有定义并初始化的;而g则是要判断,g数组是否有定义,g数组的元素个数,是不是大于其下标,也就是b[5]的值,故而要将第一层中括号里面的元素提取出来,单独传入函数处理
    				
    				//-------------分割线-------------
    				
    				//这条语句就是只删去所有末尾的]
    				//因为作为右值的时候,层层[]嵌套的右值的数据,必须在拆完所有括号以后,得到一个确切的数值,只有对左值的合法性判断时,才需要先提取出第一个中括号种的内容,再进行有无初始化的函数判断
    				
    				int r_val = get_val( right.substr( 0, right.find("]") ) ); //此处在自己敲时,也犯了特别粗心的错误,不小心把]敲成了[,导致经历了很久不知为何的程序崩溃
    				
    				
    				if (l_val == -1 || r_val == -1)
    				{
    					bug = i; break;
    				}//数组没定义,或者下标越界
    				
    				// l_val表示的是坐值最外层中括号里包围的数,也即最外层数组的下标,对于 a[b[c[7]]]而言,则是指的b[c[7]]表示的整数值 
    				if ( !arr.count(code[i][0]) || l_val >= arr[code[i][0]] )
    				{
    					bug = i; break;
    				}
    				
    				string temp = code[i].substr(0, 2) + to_string(l_val); // temp是类似这样的字符串"arr[8",是不带]的
    				val[temp] = r_val;//标注该元素已被初始化
    			}
    		}
    		cout << bug + 1 << endl;
    	}
    	return 0;
    }

    /*
      < 其他看过并理解了代码的方法:(做新题遇到障碍时,可以回来用别的思路重敲一次,对这题的理解应该会更深) >
      
      法二:
      http://blog.csdn.net/majing19921103/article/details/44409711
      不太容易理解,不过这题比较难懂的地方就是递归,所以每种解法,都有一点点难以理解
      
      这个方法的优点是:getArray函数和calculateArray函数分开,前者用于实现数组名和数组大小的分离;
      后者用于计算string串的数值内容,其内部多次调用getArray将数组名分离出来并压栈,但在处理完所有括号以后,又采用LIFO的原则,不断找到某个下标对应的最靠近它的数组名,并判断下标是否超过这个数组的大小...循环往复
      
      同时,他的partition函数,用于实现等号左右两边字符串的分离。
      
      简单概括就是:这个博主的思路非常清晰,几乎每个功能都写出一个函数,看他的代码,不会有云里雾里的感觉
      
      我认为可以有改进的地方(较之法一):
      1.1 题目有说数组名从26个字母的大小写中选取,用string存数组名会影响速度,其实没有必要,map的前一键值用char就够了
      1.2 题目中列出的bug类型,没有括号不匹配这种,所以,用栈来去括号,总归还是没什么必要,而且使得左值或右值计算中,如果要算某个数组名对应的下标的整数值,需要不断对其里面的数组名压栈,直到里面没有数组名,最后却还要一一取出数组名,并结合其对应下标判断越界,使得代码复杂了些,比较容易出错。在括号配对方面,我觉得既然没说有bug,其实还是没必要用栈检查
      (虽然我一开始也想到用栈,但看完法一中博主的代码,我觉得用栈其实增加了很多不必要的代码量)
       
    */

    /*
      法三:
      来自:http://blog.csdn.net/m0_37253730/article/details/69942016
      
      这个方法比前两种容易理解许多,它和法二有些类似,除了没有用栈来进行处理
      
      但是,这串代码的思路,真的十分清晰,所以我自己借鉴了他的思路以后,手敲了一次
      
      边敲也边思考了一下,为什么这个博主的代码能写的如此简洁,浅见如下:
      对map多次使用count函数,检查确定了count函数返回值不为0时,就果断地用了下标来表示map里的元素,使得这部分的代码十分简洁明了
      
      而反观法二,法二就基本没有这样,先用count判断能否取下标,再直接用下标
      
      **********最后再附上博主自己对这种解法的解释,复制自该博主的博客**********
      主要思路:
    用map<string,int> 存放变量名和变量名对应的size
    map<string,map<int,int> > 来存放哪几个id是被初始化过的了,只有这里面的值可以出现在[]里面。
    check() 递归检查类似a[b[c[id]]] 这种类型。
    递归到最里面,然后返回到上一层来检查这个值是否存在和是否被初始化过。
    记得清空map, 以及substr(id,len)是从下标id开始,截取长度为len的串...而不是开始下标和结束下标。
    
    	以下是我借鉴博主的思路,对自己代码的改进:
    	****!有一点值得说明的地方是
    	题目有说数组名只在26个字母的大小写间选取,所以,有些本需要用 find_first_of('[')+1 的地方,我是直接用了2,因为仔细读了题,我可以确定对去中括号的操作,找到的第一个[,必定是字符串中下标为1的字符
    	
    	所以,认真读题还是很重要的,这样有些细节上的处理,就可以简化了...
    	
    */

    #include <iostream>
    #include <string>
    #include <map>
    #include <cctype> // isdigit函数 
    #include <sstream>
    using namespace std;
    
    map<string, int> size; //标记数组大小
    map<string, map<int, int> > num; //定义过的数组名,对int下标是否完成初始化
    int judge; // judge的含义:在check函数中,用来标记下标数据是否合法,合法为0,否则标记为1;主要是为了标记,code中是否有bug 
    
    
    int check(const string &s)
    {
    	if (isdigit(s[0])) //此处本来想return (int)s[0];的,后来想到不能这样,这个数字不一定只有一位,首位满足这个if里的条件,只能证明,该去的括号已经去了罢了
    	{
    		int temp;
    		stringstream ss(s);
    		ss >> temp;
    		return temp;	
    	} 
    	
    	string name = s.substr(0, s.find_first_of('[')); //去掉最外层中括号,并将最外层的数组名,和其对应的下标分离
    	string inside = s.substr(s.find_first_of('[') + 1, s.find_first_of(']') - s.find_first_of('[') - 1);
    	int in = check(inside);
    	
    	//判断nmae[in]中,name数组名是否经过定义,以及name[in]元素是否初始化
    	if (size.count(name) == 0)
    	judge = 1;
    	else
    	{
    		if (in < 0 || in >= size[name]) judge = 1; //下标不在合法范围
    		
    		if (num.count(name) == 0) judge = 1; //该数组所有值尚未被初始化
    		else if (num[name].count(in) == 0) judge = 1; //该数组又被初始化的元素,但是该下标对应的元素,尚未被初始化(同样非法) 
    	}
    	if (!judge) return num[name][in];
    	return -1;
    }
    
    void definition(const string& s)
    {
    	string name = s.substr(0, 1); //截取出数组名
    	string in = s.substr(2, s.find_first_of(']') - 1); //截取中括号内的内容,即指定的数组大小
    	size[name] = stoi(in); 	 
    }
    
    void assignment(const string& s, int &flag)
    {
    	string left = s.substr(0, s.find_first_of('=')); //分离等式左边和右边
    	string right = s.substr(s.find_first_of('=') + 1);
    	
    	string name_l = left.substr(0, 1); //截取等式左右的,最外层数组名
    	string name_r = right.substr(0, 1);
    	
    	string inner_l = left.substr(2, left.find_last_of(']') - 2);//等式左值去一层中括号,判断最外层数组对应的下标内容,是否有定义且初始化,并且没下标越界;而等式右值不需去一层中括号,而是右值整体必须时一个已初始化的整数(原因在法一,此处处理的附近,有注释解释)
    	
    	int l_val = check(inner_l), r_val = check(right);
    	if (judge == 1) flag = 1; //出错了
    	else
    	{
    		map<int, int>tp; //temp
    		if (size.count(name_l) == 0)  flag = 1; //左值的最外层数组名未定义
    		else
    		{
    			if (l_val < 0 || l_val >= size[name_l]) flag = 1;
    			else
    			{
    				if (num.count(name_l) == 0) //如果等式左边的值之前没被初始化,那么现在初始化
    				{
    					tp[l_val] = r_val;
    					num[name_l] = tp;
    				}
    				else num[name_l][l_val] = r_val; //如果之前已经初始化,可以直接用下标(因为map的下标操作,在下标不存在时,回自己插入键并初始化为默认值,可能带来错误,故要先将不能直接用下标符号的情况,单独处理)
    			}
    		}
    	 } 
    	 
    }
    
    int ReadCode()
    {
    	string s;
    	int cnt = 0; //计数,标记正在判断的,是读入的第几条code的正误,以便输出错误code在第几条
    	size.clear();
    	num.clear();
    	int flag = 0; //flag标记有没有代码出错误,如果所有代码都没错,输出0 
    	judge = 0;// 每次将表示是否出错的judge置0,表示还没出错
    	while (getline(cin, s))
    	{
    		if (s == "." && cnt == 0) return 0; //一条代码都没有,就已遇到点,是输入结束的情况
    		if (s == ".") //一组代码数据输入结束
    		{
    			if (!flag) cout << 0 << endl;
    			return 1; //继续下组代码的输入 
    		}
    		
    		cnt++; //表示当前输入的是普通代码
    		if(!flag) //该if语句里的内容,只有在之前没有任何错误代码时,才有必要执行,如果之前有错误,则第一次出现错误的信息,其实已经输出了,不必再对以后代码的正误坐判断
    		{
    			if (s.find('=') == string::npos) definition(s); //说明是定义语句,传入s即可,因为题目设定,bug是出在赋值语句的
    			else 
    			{
    				assignment(s, flag); //赋值语句时,传入s和判断code是否无bug的标记变量flag,注意函数中,flag应传入引用,使得其值可以被改变
    				if (flag) cout << cnt << endl;
    			}	 
    		} 
    	 } 
    	 
    }
    
    int main()
    {
    	while (ReadCode())
    	{
    	}
    	return 0;
    }


  • 相关阅读:
    CC.NET+SVN+Msbuild
    react服务端/客户端,同构代码心得
    为什么国人很难出高质量开源
    FKP,一套全栈框架,基于react、webpack、koa1、babel
    嵌入式工程师的发展路线
    浅谈学习单片机的一些职业规划
    关于嵌入式新手面试的一些小技巧
    几点心得送给学习嵌入式的新手
    新手学习嵌入式需要掌握的几点知识点
    从迷茫到转机,一个嵌入式工程师的经历
  • 原文地址:https://www.cnblogs.com/mofushaohua/p/7789420.html
Copyright © 2011-2022 走看看