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

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

在这篇文章里,我向你介绍,我封装的两个类,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);
}
}
}
}

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

  List<string> list = new List<String>();

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

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
}
}

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=,Name="北京",PId=},
new Area(){Id=,Name="海淀区",PId=},
new Area(){Id=,Name="朝阳区",PId=},
new Area(){Id=,Name="东城区",PId=}
}; TreeMap<Area,TreeNode> treeMap=new TreeMap<Area,TreeNode>();
treeMap.Each(
()
);
//将数据从List<Node>_src加载到TreeView中。这里你不再需要写复杂的遍历算法
Area srcRoot = new Area() { Name = "china", Id = }; 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;
}
);
}

封装一个通用递归算法,使用TreeIterator和TreeMap来简化你的开发工作。的更多相关文章

  1. 封装一个通用的PopupWindow

    上篇文章是关于建造者设计模式的,今天顺便封装一个通用的 PopupWindow 来实践一下, 同时也方便以后使用 PopupWindow,本文将从下面几个方面来介绍 PopupWindow 及其封装, ...

  2. PHP封装一个通用好用的文件上传处理类

    封装一个文件上传类完成基本功能如下: 1.可上传多个或单个文件 2.上传成功返回一个或多个文件名 3.上传失败则返回每个失败文件的错误信息 上传类中的基本功能: 1.构造参数,用户可以自定义配置参数, ...

  3. C 封装一个通用链表 和 一个简单字符串开发库

    引言 这里需要分享的是一个 简单字符串库和 链表的基库,代码也许用到特定技巧.有时候回想一下, 如果我读书的时候有人告诉我这些关于C开发的积淀, 那么会走的多直啊.刚参加工作的时候做桌面开发, 服务是 ...

  4. 封装一个通用的正则,不再为test和replace所烦恼,eval很棒~

  5. C 封装一个简单二叉树基库

    引文 今天分享一个喜欢佩服的伟人,应该算人类文明极大突破者.收藏过一张纸币类型如下 那我们继续科普一段关于他的简介 '高斯有些孤傲,但令人惊奇的是,他春风得意地度过了中产阶级的一生,而  没有遭受到冷 ...

  6. 一个通用数据库访问类(C#,SqlClient)

    本文转自:http://www.7139.com/jsxy/cxsj/c/200607/114291.html使用ADO.NET时,每次数据库操作都要设置connection属性.建立connecti ...

  7. [js高手之路]javascript腾讯面试题学习封装一个简易的异步队列

    这道js的面试题,是这样的,页面上有一个按钮,一个ul,点击按钮的时候,每隔1秒钟向ul的后面追加一个li, 一共追加10个,li的内容从0开始技术( 0, 1, 2, ....9 ),首先我们用闭包 ...

  8. 封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil

    封装一个基于NLog+NLog.Mongo的日志记录工具类LogUtil,代码比较简单,主要是把MongoTarget的配置.FileTarget的配置集成到类中,同时利用缓存依赖来判断是否需要重新创 ...

  9. 如何为 Go 设计一个通用的日志包

    需求 一个通用的日志包,应该满足以下几个需求: 兼容 log.Logger,标准库大量使用了 log.Logger 作为其错误内容的输出通道,比如 net/http.Server.ErrorLog,所 ...

随机推荐

  1. C#回顾 - 3.NET的IO:字节流

    使用 Stream 类管理字节流 使用 FileStream 类管理文件数据 使用 MemoryStream 类管理内存数据 使用 BufferedSream 类提高流性能   3.1 FileStr ...

  2. Jquery自定义图片上传插件

    1 概述 编写后台网站程序大多数用到文件上传,可是传统的文件上传控件不是外观不够优雅,就是性能不太好看,翻阅众多文件上传控件的文章,发现可以这样去定义一个文件上传控件,实现的文件上传的效果图如下: 2 ...

  3. 无废话ExtJs 入门教程十二[下拉列表联动:Combobox_Two]

    无废话ExtJs 入门教程十二[下拉列表联动:Combobox_Two] extjs技术交流,欢迎加群(201926085) 不管是几级下拉列表的联动实现本质上都是根据某个下拉列表的变化,去动态加载其 ...

  4. Hihicoder 题目1 : Trie树(字典树,经典题)

    题目1 : Trie树 时间限制:10000ms 单点时限:1000ms 内存限制:256MB 描述 小Hi和小Ho是一对好朋友,出生在信息化社会的他们对编程产生了莫大的兴趣,他们约定好互相帮助,在编 ...

  5. HDU2546 饭卡(背包)

    开始写成01背包的形式,求m元可买物品价值的最大值 dp[j] = max(dp[j], dp[j - pri[i]] + pri[i]) 结果为m - dp[m] 但后来发现是有问题的, 比如这组过 ...

  6. Git学习笔记 git revert

    我们难免会因为种种原因执行一些错误的commit / push,git提供了revert命令帮助程序员修复这样的错误. 举个例子,下图是git commit 的历史记录 git revert 命令会通 ...

  7. git push 使用总结

    git push命令用于将本地分支的更新,推送到远程主机.它的格式与git pull命令相仿. $ git push <远程主机名> <本地分支名>:<远程分支名> ...

  8. 攻城狮在路上(壹) Hibernate(七)--- 通过Hibernate操纵对象(下)

    一.与触发器协同工作: 当Hibernate与数据库的触发器协同工作时,会出现以下两类问题: 1.触发器使Session缓存中的数据和数据库中的不一致: 出现此问题的原因是触发器运行在数据库内,它执行 ...

  9. centos6.4下安装php的imagick和imagemagick扩展教程

    imagick在centos6.4的安装方法: .安装ImageMagick 代码如下: wget http://soft.vpser.net/web/imagemagick/ImageMagick- ...

  10. (译)【Unity教程】使用Unity开发Windows Phone上的横版跑酷游戏

    译者注: 目前移动设备的跨平台游戏开发引擎基本都是采用Cocos2d-x或者Unity.一般而言2d用cocos2d-x 3d用unity,但是对于Windows Phone开发者, cocos2d- ...