关于字符串操作的一个小例子(递归实现)
字符串在.NET中项目中非常常用。关于String的介绍就不多说了。
背景:今天和同事讨论一个问题,去除括号的问题。
问题描述:一段字符串,去除字符串中小括号中的内容,小括号可能有嵌套情况。
解决思路:1、先去除最内层的小括号;2、进行完第一步之后得到新的字符串,再执行第一步。3、直到最后没有括号。
代码:
private static string DeleteTemp(string name) { for (int i = 0; i < name.Length; i++) { if (name[i].Equals(')')) { int firstIndex = name.IndexOf(name[i]); //替换掉一个内层括号 string subString = name.Substring(0, firstIndex + 1); int index = subString.LastIndexOf('('); string tempString = subString.Substring(index); subString = subString.Replace(tempString, ""); string dd = subString + name.Substring(firstIndex + 1); name = DeleteTemp(dd); } } return name; }
测试示例:
string name = "110kV1#母分开关由运行改热备用(110kV1#母分备自投由信号改跳闸(1区),110kV1#母分保护由跳闸改信号(1区))"; var s = DeleteTemp(name); Console.WriteLine(s);
输出结果:应该输出“110kV1#母分开关由运行改热备用”
结果确实输出了“110kV1#母分开关由运行改热备用”
总结:1、使用了String的IndexOf、LastIndexOf、SubString、Replace等函数;
2、运用了递归调用。
今天看《代码大全》,找到一个解决本问题的一个方法。
思路:因为“(”和“)”是成对出现的,所以可以利用这一特点。
步骤:1、声明变量,遇到“(”就加1;遇到“)”就减1。 2、最后所得值为0,则匹配成功。3、用SubString函数截取需要的部分。
代码:
private static string Delete(string name) { int flag = 0; int firstIndex = -1; foreach (char charTemp in name) { if (charTemp.Equals('(')) { flag++; if (firstIndex != -1) { continue; } firstIndex = name.IndexOf(charTemp); } if (charTemp.Equals(')')) { flag--; } } if (flag != 0) { Console.WriteLine("括号不!与配匹"); return ""; } else { return name.Substring(0, firstIndex); } }
晚上回到家之后(2014-04-23),突然想到Delete方法少考虑一种情况,例如当name=“110kV1#母分开关由运行改热备用(110kV1#母分备自投由信号改跳闸(1区),110kV1#母分保护由跳闸改信号(1区)),1111(cdhfhcdkj(cdsdf(dsd)fed)s)”就返回不了,想要的结果。
使用Delete函数输出为:“110kV1#母分开关由运行改热备用”
应该输出:“110kV1#母分开关由运行改热备用,1111”.
对Delete就行修改,名字改为IsLegal,IsLegal函数用于判断字符串中的“(”和“)”是否成对出现,即是否是合法的字符串:代码如下
private static bool IsLegal(string name) { int flag = 0; int index = 0; while (index < name.Length) { switch (name[index]) { case '(': flag++; break; case ')': flag--; break; } index++; } return flag == 0;
}
今天(2015-04-28)重新思考这个问题,发现原来的算法删除一对最内部的括号后,就要再次遍历整个字符串。所以今天改进该方法。
思路:与上面看代码大全的思路一样。
代码:
private static string DeleteTwo(string name) { List<int> list = new List<int>(); string temp = name; if (name.Contains("(") || name.Contains(")")) { GetIndex(name, ref list); temp = name.Substring(0, list[0]) + name.Substring(list[list.Count - 1] + 1, name.Length - list[list.Count - 1] - 1); return DeleteTwo(temp); } return temp; } private static void GetIndex(string name, ref List<int> list) { int index = 0; int flag = 0; while (index < name.Length) { switch (name[index]) { case '(': flag++; list.Add(index); break; case ')': flag--; list.Add(index); if (flag == 0) { return; } break; } index++; } }
图像示意:今天(2015-04-28)的思路示意图。
最开始时示意图:
总结:采用今天的方法,代码会稍微多了几行,但是递归调用的次数明显减少。对自己写过的代码不断优化,重构是一件幸福的事。如果以后发现更好的办法,我会在加在本博客后面的。
今天(2015-01-30)对我的算法重新审视了一下,发现算法中还有可优化的地方。本人文笔不太好,还是画图吧,直接了然。请看下图:
代码修改:主要修改DeleteTwo方法。代码如下:
private static string DeleteTwo(string name) { List<int> list = new List<int>(); string temp = name; if (name.Contains("(") || name.Contains(")")) { GetIndex(name, ref list); string first_half_name = name.Substring(0, list[0]); //前半部分 string second_half_name = name.Substring(list[list.Count - 1] + 1,
name.Length - list[list.Count - 1] - 1); //后半部分 temp = first_half_name +DeleteTwo(second_half_name); return DeleteTwo(temp); } return temp; }
对GetIndex方法的修改主要是为了防止,输入不合法的情况。当‘(’和‘)’不匹配时,提示报错。红色部分就是添加的代码。
private static void GetIndex(string name, ref List<int> list) { int index = 0; int flag = 0; while (index < name.Length) { switch (name[index]) { case '(': flag++; list.Add(index); break; case ')': flag--; list.Add(index); if (flag == 0) { return; } break; } index++; } if (list.Count % 2 != 0) //如果左右括号不匹配就跑出错误 { throw new Exception("名称出错了,‘(’与‘)’不匹配!"); } }
日期(2015-05-06)算法再思考。在函数DeleteTwo算法中使用了递归;有关递归的问题请看 C#中的递归APS和CPS模式详解(转载) 。
需要对递归进行优化,尾递归优化在于使堆栈可以不用保存上一次的返回地址/状态值,从而把递归函数当成一个普通的函数调用。
递归实际上是依赖上次的值,去求下次的值。 如果我们能把上次的值保存起来,在下次调用时传入,而不直接引用函数返回的值。 从而使堆栈释放,也就达到了尾递归优化的目的。
在算法中引入StringBuilder储存上次函数的值,从而使堆栈不用保存上一次返回的地址/状态值,达到优化的目的。代码如下:
private static string DeleteTwo(string name, ref StringBuilder sb) { List<int> list = new List<int>(); if (!(name.Contains("(") || name.Contains(")"))) return sb.ToString(); GetIndex(name, ref list); string first_half_name = name.Substring(0, list[0]); //前半部分 sb.Append(first_half_name); string last_half_name = name.Substring(list[list.Count - 1] + 1, name.Length - list[list.Count - 1] - 1); //后半部分 return DeleteTwo(last_half_name, ref sb); }
客户端代码:
StringBuilder sb = new StringBuilder(); string name = "110kV1#母分开关由运行改热备用(110kV1#母分备自投由信号改跳闸(1区),110kV1#母分保护由跳闸改信号(1区)),111(123(3)122)"; var sname = DeleteTwo(name, ref sb); Console.WriteLine(sname);