惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

N
Netflix TechBlog - Medium
Blog — PlanetScale
Blog — PlanetScale
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
爱范儿
爱范儿
量子位
博客园 - 聂微东
Engineering at Meta
Engineering at Meta
WordPress大学
WordPress大学
GbyAI
GbyAI
MyScale Blog
MyScale Blog
IT之家
IT之家
P
Proofpoint News Feed
M
MIT News - Artificial intelligence
The Cloudflare Blog
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Hugging Face - Blog
Hugging Face - Blog
The Register - Security
The Register - Security
Microsoft Security Blog
Microsoft Security Blog
博客园_首页
MongoDB | Blog
MongoDB | Blog
F
Fortinet All Blogs
博客园 - 三生石上(FineUI控件)
Y
Y Combinator Blog
雷峰网
雷峰网
V
Visual Studio Blog
酷 壳 – CoolShell
酷 壳 – CoolShell
Last Week in AI
Last Week in AI
博客园 - 叶小钗
D
DataBreaches.Net
B
Blog
B
Blog RSS Feed
大猫的无限游戏
大猫的无限游戏
aimingoo的专栏
aimingoo的专栏
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
The GitHub Blog
The GitHub Blog
云风的 BLOG
云风的 BLOG
Recent Announcements
Recent Announcements
阮一峰的网络日志
阮一峰的网络日志
小众软件
小众软件
腾讯CDC
T
Threat Research - Cisco Blogs
SecWiki News
SecWiki News
Martin Fowler
Martin Fowler
D
Docker
Cisco Talos Blog
Cisco Talos Blog
T
Tenable Blog
Webroot Blog
Webroot Blog
宝玉的分享
宝玉的分享

博客园 - MSFT:waywa 韦恩卑鄙

[UWP]一种利用Behavior 将StateTrigger集中管理的方案 【干货】个人工作文档节选:XAML MVVM 框架易用性细节优化Tips MVVM Sidekick Win8 RT 轻量级MVVM框架开源 对资源加读写锁的容器 对不能用using的成对操作,快速扩展IDisposable的方法 intel I7平台Win7 x64 下wpf、silverlight 与aero特效动画缓慢故障排除一则 今天被Prism里面的一段代码雷到了 1080*1920 下看网站很爽 游戏大厅 从基础开始(8)--绕回来细说聊天室(下)垃圾列表 游戏大厅 从基础开始(7)--绕回来细说聊天室(中间偏下)之女仆编年史2 游戏大厅 从基础开始(6)--绕回来细说聊天室(中)之女仆编年史1 - MSFT:waywa 韦恩卑鄙 游戏大厅 从基础开始(5)--绕回来细说聊天室(上) 一时技痒 不用模拟第一印象的构造 通过三个观察得来的规律解决N^2个往返接力问题 游戏大厅 从基础开始(4)-通过L2X用配置文件反射组装程序(VB only) 关于silverlight depencyobject简单的clone silver light 2 客户端的伪DataTable for EF X360吉他英雄4鼓套装 &1 TB硬盘入手 游戏大厅 从基础开始(3.5)——最吸引眼球的部分 客户端与服务器的连接 的实现 游戏大厅 从基础开始(3)——最吸引眼球的部分 客户端与服务器的连接
简单的树遍历枚举器v0.2-挑战一个程序员到底能多懒- 添加广度优先遍历
MSFT:waywa 韦恩卑鄙 · 2009-08-16 · via 博客园 - MSFT:waywa 韦恩卑鄙

前一阵在递归算法相关回贴的讨论中 和某lz抱怨 现在的同志们连用自己的栈加循环模拟递归都不会做了。
如果自己实现递归栈  又怎么会在线程栈中储存过多无关信息?数据全部都在堆里 又怎会stackoverflow?
当时就有想法自己实现一个,造福一下群众,但是被坏心眼的某lz 阻止了。“让他们自己写。” 他大概是这么说滴,“他们自己写了才算懂得了。”

可是 最近自己在工程中越来越多必须实现“数据库树型菜单表”“对游戏大厅内所有子孙房间进行检查” “对两个子目录中所有文件进行比较”这样的树型遍历操作
用递归 自己觉得恶心, 用栈每次重写麻烦。 
又想起用linq to xml 的 System.Xml.Linq.Extensions.Descendants<T>是多么畅快 为啥xpath可以 linq to xml可以  咱就不能用一个扩展方法把所有想遍历的树统统解决掉呢?

 于是花了一个下午写了一个枚举器, 用循环来代替栈,让溢出见鬼去吧!

-----------------------------------------------思路分割线--------------------------------------------------------------
问题:什么是树的遍历。
回答:树的遍历是树的一种重要的运算。所谓

遍历是指对树中所有结点的系统的访问,即依次对树中每个结点访问一次且仅访问一次。
 
问题:树的遍历一般过程?
回答:
朱德庸某绝对小孩2 中有一幅漫画:
心理辅导师对 顽皮:“你的问题很严重 我想找你的父母谈谈”
心理辅导师对 顽皮父母:“你们的问题很严重 我想找你们的父母谈谈”
心理辅导师对 顽皮祖父母:“你们的问题很严重 我想找你们的父母谈谈”
。。。。

顽皮-
  父亲
      父亲的父亲
            父亲的父亲的父亲
            父亲的父亲的母亲
      父亲的母亲
  母亲
      母亲的父亲
      母亲的母亲
。。。。
。。。
 不解决坟墓里面的曾祖父母们  是没办法解决祖父母们 和 父母的问题 也就没办法解决顽皮的问题。

1 访问本节点
2 有子节点则 
   对每一个子节点
            {
                  1 访问本节点 
                  2 有子节点则 
                     对每一个子节点
                        {
                              ....
                        }
            }

问题:为什么树的遍历要用到递归
回答: 递归调用“处理到一半 先搁置”的特性 非常符合树的遍历访问。

问题:为什么你做树的遍历不希望用递归
回答: 递归的时候 经常会把不需要的东西压到栈里(为什么要尾递归)。这个栈毕竟是有限的,我们可以自己把处理一半的东放在自己在堆实现的栈 而不是线程分配的那可怜的1MB。我们可以专注在我们需要的数据上

问题:为什么要用枚举器
回答: 好处多多,  结果直接支持linq查询, 支持lazy方式提高枚举效率 想变list就list  想变dictionary 就dictionary 

问题:不同树的相同点是什么
回答:  所有的节点都实现相同的接口,基本上是同质的  而且他们都有子节点的集合
· 顽皮家所有成员都是人生父母养的
· 所有的目录都可能有子目录和文件
· 所有的XML节点都可以具有标记名 属性 和子节点

问题:不同树的不同点是什么
回答:如何取得子节点集合
· 联系一个人的父母要查通讯录
· 访问子目录要使用  io.directory.getdirectories()
· 访问XML子节点要访问childrennodes集合
-----------------------------------------------思路分割线--------------------------------------------------------------

理清思路 我们需要的是一个以根节点和从根节点选择子节点集合的方法为参数创建的范型的枚举器

    public class RecursionEnumerator<TItem> : System.Collections.Generic.IEnumerator<TItem>, System.Collections.IEnumerator ,IEnumerator <Stack<TItem >>
    
{


        
public RecursionEnumerator(IEnumerable<TItem> rootObjects, Func<TItem, IEnumerable<TItem>>
 childrenSelector)
        
{

            
        }

}

枚举器最重要的方法实现当然是MoveNext()了
整体工作流程和一般递归调用一致
 
   对每一个子节点
            {
                  (把当前子节点枚举器压入自定义Stack)
                  1 访问本节点 
                  2 有子节点则 
                     对每一个子节点
                        {
                              (把当前子节点枚举器压入自定义Stack)
                              ....
                              (把当前子节点枚举器从自定义Stack弹出)
                        }
                  (把当前子节点枚举器从自定义Stack弹出)
            }

这里提供了注释

     public bool MoveNext()
        
{
            
if (ColStack.Count > 0
)
            
{

                var cur 
=
 ColStack.Peek();

                
if (Started)  //已经开始

                {
                    
//主动找下一个节点

                    var en = ChildrenSelector(cur.Current).GetEnumerator(); //先看有没有子节点

                    
if (en.MoveNext()) //有子节点  
                    {
                        
// 把这个层加入堆栈 访问第一个节点

                        ColStack.Push(en);
                        
return true
;
                    }

                    
else //没有子节点 
                    {
                        
//进入下一个同层节点


                        
while (ColStack.Count > 0)
                        
{
                            en 
=
 ColStack.Peek();
                            
if (en.MoveNext()) //有同层节点

                            {
                                
return true;//本次访问这个节点;


                            }

                            
else//没有同层节点
                            {
                                ColStack.Pop().Dispose();
// 取消本层堆栈  

                            }


                        }

                        
return false//完全没有子节点 也没有下一个节点  就无法继续了
                    }




                }

                
else //第一次访问 直接返回
                {
                    Started 
= true
;
                    
return
 cur.MoveNext();
                }


            }

            
return false;
        }

稍微包装一下 也实现一个IEumeratable 和扩展方法

外包装

就可以在任何你想要的对象上遍历了~

当然一个下午的工作一定会有很多纰漏 有什么意见不妨提出来 我修改修改

对于真正的懒人来说 以上内容全是乐色!怎样名正言顺的偷懒才是重要的!

Sample  察看一个目录中所有子目录的文件列表

        static void Main(string[] args)
        
{
            
string RootDir= @"J:\Emule"
;
            

            
foreach (string str in RootDir.GetRecursionEnumeratable ( path=>
System.IO.Directory.GetDirectories (path) ))
            
{
                Console .WriteLine(str) ;
                System.IO.Directory.GetFiles (str).ToList().ForEach (s
=>
Console.WriteLine (s));
            
            }


            Console.ReadKey();
        }

或者干脆点

Code

是不是比原来递归来递归去好一些呢?

有的时候  根节点不是一个对象 而是一个集合   比如treeview 的items
对于多个根节点  我也提供了支持


          (
new string[] @"J:\Emule" ,@"I:\cdimages"})
              
              .GetRecursionEnumeratableAsRootCollection (path 
=> System.IO.Directory.GetDirectories(path))
                .ToList
<string>()
                .ForEach
                (
                    str 
=>
                    
{
                        Console.WriteLine(str);
                        System.IO.Directory.GetFiles(str).ToList().ForEach(s 
=> Console.WriteLine(s));
                    }

                );

更复杂的状况  我们有时候不但要遍历节点  也要返回他们每一层的祖先列表   这里我同时实现了 IEnumeratable<Stack<TItem>>
Sample 2 从平面表中生成树


Table
ID  Name FatherID


            ItemEnt[] source
=new ItemEnt[0]; // 从orm中读取表

            var roots 
= source.Where(itm => itm.FatherID == 0);
            
//多个根节点入口
            roots.GetRecursionEnumeratableAsRootCollection
                (
                
//提供子节点方法
                    itm => source.Where   
                        (
                            itmchild 
=> itmchild.FatherID == itm.ID
                        )
                )
                
//返回每个节点及其祖先节点列表的方法
                .ToList <Stack  < ItemEnt> >()
                .ForEach 
                (stack
=>Console.WriteLine (  string.Join (@"\" ,stack.Select (i=>i.Name ).ToArray ()  )));

                    

            Console.ReadKey();

多简单~!

补充  鹤冲天同学提出了一个性能相关的很重要的问题  就是关于遍历的条件   老赵将其明确化 
“不过方法加predicate可以在中端就跳过一些节点,不用继续递归下去。”
如果仅仅对IEnumeratable 进行where 约束   一些本来不需要深入的子节点还是会继续深入下去的。 这样的确会带来性能的损失

这种where 约束分两部分 
1 是否选择本节点
2 是否对本节点的子节点今进行深入遍历

对于第一种  我在前面为什么用枚举器中提到了  IEnumerable 的where 扩展是可以进行lazy filter的
对于第二中 我得补充下, 这个逻辑其实是 Children Selector 相关的。   可以在Children Selector中进行有效清晰的控制

(new string[] @"J:\Emule" ,@"I:\cdimages"}
             
.GetRecursionEnumeratableAsRootCollection (path 
=> (path.Length <80)?System.IO.Directory.GetDirectories(path):new string[0] ) 
             
.ToList
<string>() 
              
.ForEach 
              

                    
str 
=> 
                   

                       
Console.WriteLine(str); 
                      
System.IO.Directory.GetFiles(str).ToList().ForEach(s 
=> Console.WriteLine(s)); 
                  
}
 
               
);

//如果路径太长 超过80 那么就不进行深层遍历了

特别感谢 鹤冲天同学和老赵同学的质疑精神   例子写得太少  Use Case做到覆盖  不好意思!

请大家继续提意见~~

更新广度优先(感谢装配脑袋提供的思路)

广度优先本身不存在访问栈 所以在枚举祖先列表的时候有一定消耗
于是在这里 分别写了两个枚举器

祖先枚举器

不取祖先的枚举器

外包装也作了小小的变化

外包装

用法

用法

这个版本估计近期不会动了 :D


代码地址

RecursionEnumerator
https://files.cnblogs.com/waynebaby/RecursionEnumerator.zip