关于字符串操作的一个小例子(递归实现)

  字符串在.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);

 

posted @ 2015-04-23 16:54  荣码一生  阅读(557)  评论(2编辑  收藏  举报