封裝一個通用遞歸算法,使用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+ "\r\n";
            });
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

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM