zoukankan      html  css  js  c++  java
  • 封装一个通用递归算法,使用TreeIterator和TreeMap来简化你的开发工作。

    在实际工作中,你肯定会经常的对树进行遍历,并在树和集合之间相互转换,你会频繁的使用递归。

    事实上,这些算法在逻辑上都是一样的,因此可以抽象出一个通用的算法来简化工作。

     

    在这篇文章里,我向你介绍,我封装的两个类,TreeIterator和TreeMap,使用他们,你不必再写递归就可以在任意的树和任意的集合之间相互装换。

    一 TreeIterator
    1.1 TreeIterator功能描述:
    TreeIterator封装了对树的遍历算法,他提供了如下功能:
    1)遍历树
    2)将任意一颗树转换为一个任意集合。
    使用TreeIterator只需要一个方法调用就可以完成树的遍历,或者将树映射到一个集合,譬如,你可以使用TreeIterator方便的对一个dom进行遍历,或者将其导出到一个集合中,期间你可以将dom节点自由的映射到任意一个自定义对象。

    1.2 TreeIterator优点:

    不必再手写递归来遍历树,在实际工作中,这可以极大的节省你的时间,因为在写递归的时候,会经常忽略递归条件而造成死递归。


    1.3 TreeIterator缺点:
    TreeIterator内部使用了递归,所以会影响到性能。其次,第一次使用TreeIterator时,需要适应一下,一旦使用了几次,你会发现他确实可以减少你的工作量。

    1.4 TreeIterator的设计思路:
    在实际项目中,可能经常的要对一颗树进行遍历,然后将其导出到一个集合中,通常的做法是手写一个递归。然而,细心一点就会发现,这些递归操作的逻辑大部分都是相同的, 首先,都要获取根节点的所有子节点,然后递归的遍历每一个子节点,并将每一个子节点映射到一个自定义对象中。伪代码如下:

         public void Each(currentNode){
             childNodesOfCurrentNode=get child nodes of currentNode;//获取当前节点的所有子节点。
             foreach(var eachElement in childNodesOfCurrentNode){
                Map(eachElement,target);//将当前子节点映射到target对象中
                Each(eachElement);//然后对每一个子节点递归遍历。
             }
         }


    事实上,完全可以将这些重复的逻辑封装起来,然而要想做到一个通用的封住算法,需要解决如下几个问题:
    1.如何获取当前节点的子节点。
    2.将这个子节点映射到什么类型的对象。
    这两个问题都是一个变化点,需要将他们留给使用者来做,因此可以使用委托来将这两个变化点提取成一个参数,这样,就可以让使用者来返回子节点以及将子节点映射到什么类型。

     

    1.5 TreeIterator的完整代码:

    using System;
    using System.Collections.Generic;
    using System.Collections;
    using System.Text;
    
    namespace Francis {
        public class TreeIteractor {
            /// <summary>
            /// 遍历一个树
            /// </summary>
            /// <typeparam name="TSrc">树节点的类型</typeparam>
            /// <param name="root">树的根节点</param>
            /// <param name="childNodesFilter">返回当前节点的子节点</param>
            /// <param name="callbackThatMapsElementFromSrcToDes">这个节点返回树的当前节点</param>
            public static void Each<TSrc>(TSrc root, Func<TSrc, IEnumerable> childNodesFilter, Action<TSrc> callbackThatMapsElementFromSrcToDes) {
                SelfEach(root, childNodesFilter, callbackThatMapsElementFromSrcToDes);
            }
    
    
            public static void Each<TSrc>(TSrc root, Func<TSrc, IEnumerable<TSrc>> childNodesFilter, Action<TSrc> callbackThatMapsElementFromSrcToDes) {
                SelfEach(root, childNodesFilter, callbackThatMapsElementFromSrcToDes);
            }
    
           
            protected static void SelfEach<TSrc>(TSrc root, Func<TSrc, IEnumerable> childNodesFilter, Action<TSrc> callbackThatMapsElementFromSrcToDes) {
                TSrc specifiedRootWithinSrc = root;
    
                IEnumerable childElements = childNodesFilter(specifiedRootWithinSrc);
                foreach(var eachElement in childElements as IEnumerable<TSrc> ?? childElements) {
                    callbackThatMapsElementFromSrcToDes((TSrc)eachElement);
                    SelfEach((TSrc)eachElement, childNodesFilter, callbackThatMapsElementFromSrcToDes);
                }
            }
        }
    }
    View Code

    1.6 一个例子
    下面这个例子演示了如何遍历一个TreeView菜单:

      List<string> list = new List<String>();
    
                Francis.TreeIteractor.Each<TreeNode>(
                    treeView1.Nodes[0],//treeView1是Winform中的TreeView树菜单
                    root => {
                        return root.Nodes;//返回当前节点的子节点
                    },
                    root => {
                        //root是当前节点,因此,这里你可以将其映射到任意和对象,并将这个对象加入到你的集合中。
                        list.Add(root.Text);
                    }
                    );
    
                list.ForEach(element => {
                    textBox1.Text +=element+ "
    ";
                });
    View Code

    2.1 TreeMap功能描述


    TreeMap提供了和和TreeIterator相反的功能,他可以将一个任意的集合映射到一个树,这听起来很不可思议:


      1)以树的方式遍历一个集合
      2)将一个集合映射到一颗树。


    举个例子,List集合中存储了Area对象,而Area对象里面存储了省市的id,name,pid,此时你可能想要遍历List<Area>并将其转换为一个dom存储到xml文件中,或者,你要将它显示到一个TreeView菜单中。完成这项工作,你需要写不少的代码,而使用TreeMap只需要一个方法的调用就OK了。


    2.2 TreeMap优点:
    同样,他可以减少你的工作量,这也是使用它的最大原因。


    2.3 TreeMap缺点:
    他比TreeIterator更加复杂,需要多使用几次从而适应他。
    在使用TreeMap时需要自己制定一个条件用于退出递归。


    2.4 TreeMap设计思路:
    怎么将一个任意的集合转换为一个任意的树呢,我想首先应该在集合中指定一个根,然后还要指定一个筛选条件,用来筛选当前根的子节点,在筛选的过程中还需要构造树,这听起来很复杂。


    2.5 TreeMap完整代码:

    using System;
    using System.Collections.Generic;
    using System.Collections;
    
    namespace Francis.Common{
       public class TreeMap<TSrc> {
            #region fields
            IEnumerable<TSrc> _src;
            IEnumerable _unGenericSrc;
            TSrc _specifiedSrcRoot;
            Func<TSrc,bool> _rootFilter;
           #endregion
    
            #region property
            protected TSrc SpecifiedRoot {
                get {
                    if(_specifiedSrcRoot == null) {
                        bool finded = false;
                        foreach(var eachElement in _src) {
                            if(_rootFilter(eachElement)) {
                                _specifiedSrcRoot = eachElement;
                                finded = true;
                                break;
                            }
                        }
                        if(!finded)
                            throw new ArgumentException("没有找到指定的源根节点,请仔细检查rootFilter的内部逻辑!");
                    }
                    return _specifiedSrcRoot;
                }
            }
    
            #endregion property 
           
            #region initialization
            public TreeMap(IEnumerable<TSrc> src,Func<TSrc,bool>rootFilter)
                :this(src, default(TSrc), rootFilter){
            }
    
            public TreeMap(IEnumerable<TSrc> src, TSrc specifiedSrcRoot)
                :this(src, specifiedSrcRoot, null){
            }
    
            public TreeMap(IEnumerable src, TSrc specifiedSrcRoot)
                :this(src, specifiedSrcRoot, null){
            }
            
            protected TreeMap(IEnumerable src, TSrc specifiedSrcRoot,Func<TSrc,bool>rootFilter) {
                if(src == null)
                    throw new ArgumentException("src不能为空");
                if(specifiedSrcRoot == null&&rootFilter==null)
                    throw new ArgumentException("specifiedSrcRoot和rootFilter不能同时为空");
    
                _src = src as IEnumerable<TSrc>;
                _unGenericSrc = src;
                _specifiedSrcRoot = specifiedSrcRoot;
                _rootFilter = rootFilter;
            }
    
            #endregion
    
            #region OtherToTree
    
            /// <summary>
            /// 将一个源集合映射到一棵树
            /// </summary>
            /// <param name="isChildNodeOfSpecifiedRoot">判断源集合中的节点是否为指根节点的子节点</param>
            /// <param name="callbackThatMapsElementFromSrcToDes">将源集合中的元素映射到一个指定的元素</param>
            public void Each<TDes>(Func<TSrc, TSrc, bool> isChildNodeOfSpecifiedRoot, Func<TSrc, TDes,TDes> callbackThatMapsElementFromSrcToDes) {
                OtherToTree(SpecifiedRoot, default(TDes), isChildNodeOfSpecifiedRoot, callbackThatMapsElementFromSrcToDes);
            }
    
    
            protected virtual void OtherToTree<TDes>(TSrc srcRoot, TDes des, Func<TSrc, TSrc, bool> isChildNodeOfSpecifiedRoot, Func<TSrc, TDes, TDes> callbackThatMapsElementFromSrcToDes) {
                des= callbackThatMapsElementFromSrcToDes(srcRoot,des);
           
                foreach(var currentElement in _src != null ? _src : _unGenericSrc) {
                    if(isChildNodeOfSpecifiedRoot((TSrc)currentElement, srcRoot)) {
                        OtherToTree((TSrc)currentElement, des, isChildNodeOfSpecifiedRoot, callbackThatMapsElementFromSrcToDes);
                    }
                }
            }
    
            /// <summary>
            /// 对源集合以树的方式进行遍历。
            /// </summary>
            /// <param name="isChildNodeOfSpecifiedRoot">删选子节点</param>
            /// <param name="callbackThatMapsElementFromSrcToDes">为你返回根节点</param>
            public void Each(Func<TSrc, TSrc, bool> isChildNodeOfSpecifiedRoot, Action<TSrc> callbackThatMapsElementFromSrcToDes) {
                OtherToTree(SpecifiedRoot, isChildNodeOfSpecifiedRoot, callbackThatMapsElementFromSrcToDes);
            }
    
            private void OtherToTree(TSrc SpecifiedRoot, Func<TSrc, TSrc, bool> isChildNodeOfSpecifiedRoot, Action<TSrc> callbackThatMapsElementFromSrcToDes) {
                callbackThatMapsElementFromSrcToDes(SpecifiedRoot);
    
                foreach(var currentElement in _src != null ? _src : _unGenericSrc) {
                    if(isChildNodeOfSpecifiedRoot((TSrc)currentElement, SpecifiedRoot)) {
                        OtherToTree((TSrc)currentElement, isChildNodeOfSpecifiedRoot, callbackThatMapsElementFromSrcToDes);
                    }
                }
            }
            #endregion   
        }
    }
    View Code

    2.6 一个例子:
    下面这个例子演示了如何将一个List<Area>对象显示到TreeView树菜单中。

    class Area{
        public int Id{get;set;}
        public string Name{get;set;}
        public int PId{get;set;}
    }
    //Winform窗体的Load事件。
    public void Form1_Load(object sender,EventArgs e){
        List<Area> areas=new List<Area>(){
            new Area(){Id=1,Name="北京",PId=0},
            new Area(){Id=2,Name="海淀区",PId=1},
            new Area(){Id=3,Name="朝阳区",PId=1},
            new Area(){Id=4,Name="东城区",PId=1}
        };
        
        TreeMap<Area,TreeNode> treeMap=new TreeMap<Area,TreeNode>();
        treeMap.Each(
            ()
        );
          //将数据从List<Node>_src加载到TreeView中。这里你不再需要写复杂的遍历算法
                Area srcRoot = new Area() { Name = "china", Id = 0 };
    
                TreeMap<Area> areaToTreeNode = new TreeMap<Area>(areas, srcRoot);
    
                areaToTreeNode.Each<TreeNode>(
                    //筛选条件用于筛选根节点,需要的是,要自己指定一个返回false的值,用于退出递归,否则将会出现死递归。
                    (eachElementWithinSrc, specifiedRoot) => {
                        //递归条件。
                        if(eachElementWithinSrc.Id == specifiedRoot.Id)
                            return false;
                        return eachElementWithinSrc.PId == specifiedRoot.Id;
                    },
                    //将Area映射到TreeNode
                    (srcElement, root) => {
                        //首先判断,如果根节点等于null,就创建一个根节点。
                        if(root == null) {
                            root = treeView1.Nodes.Add("china");
                            return root;
                        }
    
                        //创建一个新节点
                        TreeNode newNodeAsRoot = new TreeNode(srcElement.Name);
                        newNodeAsRoot.Tag = srcElement;
    
                        //将新节点添加到root下,使其成为root的子节点。
                        root.Nodes.Add(newNodeAsRoot);
    
                        //返回新创建的节点。
                        return newNodeAsRoot;
                    }
                 );
    }
    View Code
  • 相关阅读:
    libevent学习总结
    C#结构体的使用
    函数常用类
    C#函数的基础应用
    数组的应用:冒泡排序,折半查找及二维数组的应用
    作业
    复习break、continue、while、do-while的运用
    编程常用英语单词
    作业:for循环,迭代法和穷举法
    循环语句
  • 原文地址:https://www.cnblogs.com/francisYoung/p/3383178.html
Copyright © 2011-2022 走看看